PyFtpServer

From PNLUG
Revision as of 20:00, 4 April 2019 by Claudiodriussi (talk | contribs) (Created page with "Category:Python Category:Guide = Un server FTP facile in Python = == Perché FTP? == Il protocollo FTP è un dei più vecchi protocolli internet, ma è tuttora mol...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search


Un server FTP facile in Python

Perché FTP?

Il protocollo FTP è un dei più vecchi protocolli internet, ma è tuttora molto usato perché è utile e semplice.

Quando avete bisogno di condividere files con altri, oppure desiderate attivare un sistrema di backup on line, FTP potrebbe essere la soluzione giusta.

Con Linux il server più comune è vsftpd - Secure, fast FTP server for UNIX-like systems che è largamente usato anche per siti ftp di grandi dimensioni.

La sua configurazione richiede un certo numero di operazioni amministrative e di default richiede la creazione di un utente unix per ogni utente ftp. Anche se sembra che permetta la creazione di utenti virtuali. In ogni caso è la tipica applicazione unix con avvio di demone e configurazione nella cartella /etc

In alternativa propongo pyftpdlib che è una libreria python consolidata, versatile, snella e facile da utilizzare. I link sono:

GitHub - giampaolo/pyftpdlib: Extremely fast and scalable Python FTP server library Welcome to pyftpdlib’s documentation — pyftpdlib 1.5.4 documentation

Il bello di questa libreria è che con gli appositi accorgimenti, può essere installata ovunque sia presente un interprete python, non solo in Linux, ma anche in Windows e Mac, ed è molto facile da configurare.

Se vi ho già convinti potete smettere di leggere ed andare subito ad installarla. Se invece desiderate, posso darvi qualche dritta su come utilizzarla.

Premetto che a me serviva per un uso semplice, quindi ho utilizzato solo una minima parte delle sue potenzialità e sono solo queste che ora vado a documentare.

Premetto anche che quello che segue non è un tutorial che si può seguire passo passo, ma solo una serie di isturzioni che vanno adattate alla propria situazione.

Prepariamo una vps linux

La prima cosa di cui abbiamo bisogno è una macchina esposta ad internet in cui installare il server. Per le vostre esigenze potreste usare il vostro pc di casa o ufficio e raggiungere il server attraverso il numero IP assegnato dal vostro provider. Oppure potete acquistare un server VPS dai uno dei tanti provider disponibili al costo di pochi euro al mese su cui potete installare il sistema operativo che preferite, ad esempio Ubuntu 18.04 Server.

Se avete scelto la seconda opzione magari potete anche acquistare un dominio e dirigere i DNS al indirizzo IP della vostra macchina. Come fare questo non è oggetto di questo tutorial. Diamo per scontato che abbiate una vostra VPS con linux Ubuntu 18.04 e che risponde ad un dominio, ad esempio myftpserver.it.

Ora potete entrare nella vostra macchina con ssh e siete pronti.

$ ssh root@myftpserver.it

Su tutte le macchine linux oggi l'interprete Python si trova installata di default, nel momento in cui sto scrivendo queste note le versioni di python sono la 2.7 e la 3.x, ma la 2.7 stà rapidamente diventando obsoleta ed a breve non sarà più supportata, quindi è molto probabile che se scrivete:

$ python --version

in risposta otterrete:

Python 3.x.x

Altrimenti potete usare la 2.7 o rendere predefinita la 3.x con il comando update-alternatives

A questo punto siete pronti, ecco un po' di comandi, che vi servono:

$ apt install python-pip
$ apt install screen
$ pip install pyftpdlib
$ useradd -m ftpuser
$ passwd ftpuser
$ chfn ftpuser
$ usermod -s /bin/false ftpuser

Per installare le librerie di Python serve il comando pip e screen ci serve per avere shell multiple con una singola connessione.

Con pip installiamo la nostra libreria python pyftpdlib e poi aggiungiamo l'utente che gestirà il server ftp. Per morivi di sicurezza togliamo la shell all'utente in modo da prevenire accessi illeciti.

Ora apriamo uno screen virtuale per l'utente ftpuser ed incominciamo a preparare l'ambiente:

$ screen su - ftpuser -s/bin/bash
$ touch ftpserver.py
$ chmod +x ftpserver.py
$ touch ftp_cron.sh
$ chmod +x ftp_cron.sh
$ mkdir data_folder
$ touch data_folder/myfile.txt
$ touch data_folder/myfile2.txt

Creiamo il file per scrivere il server ftp e lo rendiamo eseguibile, creiamo anche uno script che useremo per configurare l'avvio del server in caso di caduta o di riavvio della macchina. Infine creiamo la cartella per i dati e ci scriviamo qualcosa dentro.

Il server FTP

È giunto il momento di scrivere il nostro server:

$ nano ftpserver.py

il contenuto del file sarà:

#!/usr/bin/env python
from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer
authorizer = DummyAuthorizer()
authorizer.add_user("ftptest", "ftptest", "/home/ftpuser/data_folder", perm="elradfmw")
handler = FTPHandler
handler.authorizer = authorizer
handler.passive_ports = range(60000, 61000)
server = FTPServer(("", 2121), handler)
server.serve_forever()

Questo è veramente un server minimale, copiato direttamente dagli esempi presenti nella documentazione della libreria a cui rimandiamo per i dettagli.

con:

authorizer.add_user("ftptest", "ftptest", "/home/ftpuser/data_folder", perm="elradfmw")

abbiamo aggiunto l'utente ftp ftptest per aggiungere altri utenti è sufficiente duplicare e configurare la riga.

La porta di default del servizio ftp è la 21, ma noi lo abbiamo avviato alla porta 2121 perché linux per motivi di sicurezza assegna le porte al di sotto di 1024 all'utente root. Se proprio abbiamo necessità di usare la porta 21 dovremo avviare il server come utente root, oppure fare un nat della porta. Ma in molti casi l'uso di una porta diversa è accettabile ed anzi aumenta la sicurezza.

Abbiamo anche indicato che le porte usate per le comunicazioni in modo passivo vanno da 60000 a 60999, per i dettagli sulle comunicazioni attive e passive invitiamo a fare qualche ricerca.

Proviamo il server

È giunto il momento di provare il server. lo avviamo con:

$ ./ftpserver.py

che ci scrive qualcosa del tipo:

[I 2000-01-01 08:00:00] >>> starting FTP server on 0.0.0.0:2121, pid=17207 <<<
[I 2000-01-01 08:00:00] concurrency model: async
[I 2000-01-01 08:00:00] masquerade (NAT) address: None
[I 2000-01-01 08:00:00] passive ports: 60000->60999

Significa che il server si è avviato e possiamo testarlo. Per fare questo usciamo temporaneamente dal nostro screen virtuale digitando la sequenza di tasti Ctrl+A D per i dettagli consultare la guida di screen. In seguito per rientrare nello screen virtuale sarà sufficiente digitare:

$ screen -r

facciamo qualche prova e da poi root digitiamo:

$ ftp localhost 2121

entrati nel server digitiamo utente e password che erano: ftptest e ftptest e proviamo un po' di comandi

ftp> help
ftp> passive
ftp> ls
ftp> bye

al comando ls dovrebbe rispondere con:

227 Entering passive mode (127,0,0,1,236,68).
125 Data connection already open. Transfer starting.
-rw-rw-r--   1 ftpuser  ftpuser         0 Jan 01 08:00 myfile.txt
-rw-rw-r--   1 ftpuser  ftpuser         0 Jan 01 08:00 myfile2.txt
226 Transfer complete.

Sembra che il server funzioni! :)

Ora proviamolo da fuori della macchina.

I client FTP

Ora apriamo un terminale nella nostra macchina locale e proviamo gli stessi comandi. La sola differenza è che l'indirizzo del server non sarà più localhost, ma la URL della macchina remota.

$ ftp myftpserver.it 2121

Il client FTP più semplice è testuale ed è integrato in tutte le macchine linux, ma è anche disponibile per il prompt dei comandi di windows. Naturalmente l'uso di un client in modo testo è poco comodo, molto meglio utilizzare uno dei tanti clients disponibili. Ne elenchiamo alcuni.

FtpZilla

Il più diffuso client FTP è senz'altro FileZilla - The free FTP solution è disponibile su Linx, Windows e Mac, per molti è sicuramente la soluzione migliore.

WinSCP

In ambiente Windows un ottima alternativa è WinSCP :: Official Site :: Free SFTP and FTP client for Windows dal punto di vista della interfaccia utente, le funzionalità sono simili a FtpZilla, ma la cosa interessante è che si possono scrivere script di automazione a riga di comando, questo lo rende particolarmente utile se serve automatizzare procedure di backup o di sincronizzazione.

lftp

Meno conosciuto degli altri è LFTP - sophisticated file transfer program che è a riga di comando come il client standard, ma offre molte funzionalità in più come ad esempio la possibilità di fare il mirroring tra una cartella locale ed una cartella remota. Quindi è usabile per backup e sincronizzazione. È disponibile in ambiente linux e probabilmente esistono dei porting in windows.

altri

Molti programmi, soprattutto File Managers e gestori di backup hanno integrata la gestione del protocollo ftp. Soprattutto la gestione è integrata nella gestione files dei principali sistemi operativi.

Il firewall

Non funziona?

Potrebbe essere colpa del firewall.

Se la connessione funziona quando viene provata da dentro la vps con localhost ma non da una posizione esterna, la colpa più probabile è che ci sia un firewall che blocca le porte. Il firewall è una buona cosa perché fa in modo che le comunicazione avvengano solo nelle porte in cui abbiamo effettivamente servizi attivi e quindi previene accessi non autorizzati che magari sfruttano falle del sistema su cui non abbiamo il controllo.

Un ottimo firewall per linux è ufw. Per installarlo ed attivarlo digitiamo quanto segue:

$ apt install ufw
$ ufw allow 22
$ ufw allow 80
$ ufw allow 443
$ ufw enable
$ ufw status

in risposta all'ultimo comando dovremmo ottenere:

A                          Azione      Da
-                          ------      --
22                         ALLOW       Anywhere                  
80                         ALLOW       Anywhere                  
443                        ALLOW       Anywhere                  
22 (v6)                    ALLOW       Anywhere (v6)             
80 (v6)                    ALLOW       Anywhere (v6)             
443 (v6)                   ALLOW       Anywhere (v6)             

abbiamo aperto le principali porte, soprattutto abbiamo aperto la porta 22 che ci permette di accedere alla macchina con ssh, se non apriamo questa porta al prossimo riavvio non saremo più in grado di entrare. Praticamente è come se chiudessimo la porta di casa lasciando le chiavi dentro. Le altre porte sono quelle per il server web.

Ora attiviamo le porte per il server ftp:

$ ufw allow 2121
$ ufw allow 60000:61000/tcp
$ ufw status

e questa volta la risposta sarà:

A                          Azione      Da
-                          ------      --
22                         ALLOW       Anywhere                  
80                         ALLOW       Anywhere                  
443                        ALLOW       Anywhere                  
2121                       ALLOW       Anywhere                  
60000:61000/tcp            ALLOW       Anywhere                  
22 (v6)                    ALLOW       Anywhere (v6)             
80 (v6)                    ALLOW       Anywhere (v6)             
443 (v6)                   ALLOW       Anywhere (v6)             
2121 (v6)                  ALLOW       Anywhere (v6)             
60000:61000/tcp (v6)       ALLOW       Anywhere (v6)           

Alla fine per attivare il firewall riavviamo il server con:

$ reboot

Poi rientriamo e riattiviamo il server.

Se ancora non funziona non saprei come fare.

Attiviamo cron

Ora il nostro server è attivo. Ma cosa succede se la macchina viene riavviata o se per qualsiasi motivo si ferma?

Il servizio FTP non funzionarà più e saremo costretti a riavviarlo a mano.

La soluzione per evitare che succeda è avviare un servizio cron che ad intervalli regolari vada a controllare che il server sia attivo e che in caso non lo sia lo riavvi.

Torniamo nell'utente backuser con screen -r o con screen su - ftpuser -s/bin/bash ed andiamo modificare il il file ftp_cron.sh che avevamo preparato in precedenza.

$ screen su - ftpuser -s/bin/bash
$ nano ftp_cron.sh

il contenuto del file sarà:

#!/bin/sh

# send mail configuration flag
domail=1 

# max number of retries
maxtries=10

# file name used to count retries
fname=/home/ftpuser/try$1.txt 

# the command nc test if a port is responding
if ! nc -z localhost 2121 ; then

    # the file $fname store the number of attempts to restart the server
    # if the server is up the file doesn't exists
    if [ -e $fname ] ; then 
        read count < $fname
    else
        count=0
    fi

    # after max tries to restart, the server is broken and we stop spamming 
    if [ $count -eq 10 ] ; then exit ; fi
    count=`expr $count + 1`
    
    # try to restart the server
    /home/ftpuser/ftpserver.py &

    # and send warning mail
    if [ $domail -ne 0 ] ; then 
        printf "FTP server is down!\nRestarting...\n" | mail -s "FTP server restart # $count " info@myftpserver.it
    fi

else
    # if the server is up delete the file of retries
    if [ -e $fname ] ; then 
        rm $fname
    fi
    
fi

Questo testo necessita di qualche spiegazione.

È un programma scritto con il linguaggio shell. Il suo compito è testare se la porta 2121 è attiva e se non lo è riavvia il server e manda una mail di avviso. La mail di avviso la invia fino ad un massimo di 10 volte per evitare mi mandare troppa spam.

La mail la invia con il comando mail, che deve essere installato a parte così come deve essere configurato l'invio delle mail tramite il protocollo smtp. Se non lo avete fatto non importa. Semplicemente il programma non sarà in grado di mandare la mail, ma il monitoraggio lo farà ugualmente.

vediamo se funziona:

$ ./ftp_cron.sh

dovrebbe risponderci qualcosa del tipo:

./ftp_cron.sh: 32: ./ftp_cron.sh: mail: not found
[I 2000-01-01 08:00:00] >>> starting FTP server on 0.0.0.0:2121, pid=890 <<<
[I 2000-01-01 08:00:00] concurrency model: async
[I 2000-01-01 08:00:00] masquerade (NAT) address: None
[I 2000-01-01 08:00:00] passive ports: 60000->60999

La prima riga indica che non è stato configurato il comando mail, e se rifacciamo:

$ ./ftp_cron.sh

Otteniamo:

[I 2000-01-01 08:00:00] 127.0.0.1:54916-[] FTP session opened (connect)
[I 2000-01-01 08:00:00] 127.0.0.1:54916-[] FTP session closed (disconnect).

La prima volta il server non era presente e quindi è stato avviato, la seconda volta invece era attivo ed ha fatto un tentativo di connessione che ha avuto successo.

Il cuore del sistema è il comando nc -z localhost 2121 che fa il test della porta e restituisce vero o falso a seconda del risultato e se non ha successo esegue il comando /home/ftpuser/ftpserver.py & che fa partire il server e lo mette in background.

Con il comando:

$ ps -e

vediamo la lista del task attivi, ad esempio:

  PID TTY          TIME CMD
    1 ?        00:00:00 systemd
   47 ?        00:00:00 systemd-journal
...
  826 pts/3    00:00:00 bash
  890 pts/3    00:00:00 python
  897 pts/3    00:00:00 ps

vediamo che il PID 890 sta eseguendo un programma python che è esattamente il nostro, come d'altra parte possiamo leggere anche nella riga emessa quando abbiamo avviato il server. Per fermarlo possiamo scrivere:

$ kill 890

finalmente cron

Tutta questa premessa serviva per preparare il sistema per cron che è un servizio standard di Linux che ad un tempo regolare esegue un comando che possiamo programmare.

Nel nostro caso desideriamo che ogni tot secondi venga lanciato il programma ftp_cron.sh in modo da riavviare il server in caso di cadute. per configurare cron scriviamo:

$ crontab -e

ed alla fine del testo aggiungiamo la riga:

*/1 *  *   *   *     /home/ftpuser/ftp_cron.sh >/dev/null

provate ora a riavviare il server. Al riavvio il servizo FTP sarà di nuovo disponibile come potete controllare con il comando ps -e e se lo buttate giù con kill, entro un minuto verrà nuovamente avviato.

FTPS (FTP over TLS/SSL)

Il nostro serve funziona bene ed è abbastanza sicuro perché viene gestito da un utente isolato, che non ha accesso alla shell e risponde ad una porta non standard, inoltre abbiamo un firewall che protegge le altre porte.

Ma in ogni caso, se la porta viene conosciuta, potrebbe essere soggetta ad attacchi di tipo "Men in the middle" perché in ogni caso la comunicazione avviene in chiaro. E siccome i problemi di sicurezza non vanno assolutamente ignorati, proviamo ad aggiugenre la connessione cifrata tramite TLS/SSL

Da utente root installiamo la libreria python per la gestione dei certificati SSL

$ pip install pyOpenSSL 

Ora entriamo nell'utente ftpuser, creiamo un certificato "self signed" e modifichiamo il server.

$ screen su - ftpuser -s/bin/bash
$ openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout ftpserver.key -out ftpserver.crt
$ nano ftpserver.py

Sulla gestione dei certificati rimandiamo ai tanti tutorial disponibili in rete.

Il testo del nuovo server sarà:

#!/usr/bin/env python
from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer
from pyftpdlib.handlers import TLS_FTPHandler
authorizer = DummyAuthorizer()
authorizer.add_user("ftptest", "ftptest", "/home/ftpuser/data_folder", perm="elradfmw")
handler = TLS_FTPHandler
handler.certfile = 'ftpserver.crt'
handler.keyfile = 'ftpserver.key'
handler.authorizer = authorizer
# requires SSL for both control and data channel
#handler.tls_control_required = True
#handler.tls_data_required = True
handler.passive_ports = range(60000, 61000)
server = FTPServer(("", 2121), handler)
server.serve_forever()

Ora per attivare le modifiche fermiamo il server che sta girando con i soliti comandi ps -e e kill ed aspettiamo un minuto per dare tempo a cron di riavviare quello nuovo.

Dopo che il server è partito, possiamo provare la connessione sicura con FtpZilla.

In "Gestione Siti" facciamo una nuova connessione e come Criptazione scegliamo "Richiedi FTP esplicito su TLS", avviamo la connessione e la prima volta ci chiede di accettare il certificato. Questo indica che da questo momento in poi la connessione avverrà in modo criptato.

Conclusione

All'inizio avevamo parlato di "FTP facile". In effetti la soluzione proposta non è proprio banale, ma c'è da considerara che abbiamo toccato argomenti come firewall, cron e SSL che di per se meriterebbero tutorials a parte, perciò vale la pena di fare un piccolo sforzo aggiuntivo.

Una cosa sicura, invece, è che la soluzione proposta è molto versatile. Se si padroneggiano le tecnologiè è facile fare configurazioni diverse da quella proposta, come attivare il server sulla porta 21 che è lo standard, oppure attivare più server su una singola macchina.

Altro aspetto da non trascurare è che è una soluzione che può essere adattata a tutte le piattaforme supportate da Python ad esempio Windows o Macintosh. Si potrebbe ad esempio installare un piccolo server ftp in un Raspberry PI, oppure mettere a punto un sistema di comunicazione aziendale su piattaforme Windows.

Da ultimo va anche considerato che bastano poche righe python per farlo funzionare. Questo significa che è facile da integrare in applicazioni Python come ad esempio server web fatti in Django o Flask.

Autore

Mi chiamo Claudio Driussi, mi occupo da molti anni di sviluppo di software gestionale.

Per commenti e suggerimenti mi potete mandare una mail a claudio.driussi@gmail.com