Dărăbăneanu Liviu-Eugen 2A1
Abstract. FTP proxy pentru acces controlat și ușor la fişiere, cu conturi individuale pentru utilizatori
Keywords: FTP, proxy, concurent, TCP, DB
Aplicația are ca scop crearea unui proxy pentru un server FTP pentru a facilita un acces controlat şi uşor la fişiere.
Utilizatorii au conturi private, ce sunt stocate într-o baza de date.
Proxy-ul vine cu un fişier de configurare care permite: modificarea dimensiunii maxime permise pentru descărcare, blocarea unor anumite formate pe care utilizatorii nu le pot accesa și banarea utilizatorilor (acest fişier de configurare poate fi modificat doar de către admin)
- login: permite utilizatorului să se conecteze printr-un username și o parolă
- register: permite utilizatorului să se înregistreze cu un username și o parolă
- ls: listează directorul curent din serverul FTP
- cd dir: schimbă directorul curent în dir
- get filename: descarcă fişierul filename din./dir unde este clientul utilizatorului
- modify trait value: modifică fişierul de configurare (dacă utilizatorul are rol de admin)
- modify size dim: modifca size-ul admis in dim bytes
- modify extensions add/rem: adauga sau sterge extensia din registrul de extensii banate
- modify ips add/rem: adauga sau sterge ip-ul din registrul de ip-uri banate
- show-config: afiseaza configuratia curenta (daca utilizatorul are rol de admin)
- pwd: afiseaza path-ul absolut unde se afla utilizatorul
- exit: opreste conexiunea utilizatorul
- logout: delogheaza utilizatorul
Natura aplicației se focusează pe transfer de fişiere, deci implicit pe exactitatea informației primite. Astfel TCP este cea mai bună opțiune pentru a transfera date.
Pentru acces la server-ul FTP, am ales să folosesc vsftpd pentru securitatea, performanța şi stabilitatea pe care le oferă.
Baza de date este gestionată de MongoDB pentru performanță, siguranță și suport pentru
Nota: prin copil înţelegem thread-ul care se ocupă de execuția comenzii
- Convenția generală de comunicare a unui mesaj între server şi client se face prin trimiterea lungimii mesajului, urmată de mesajul propriu-zis.
- Pentru a nu fi necesar ca utilizatorul să se logheze la fiecare comandă, am implementat token de sesiune:
payload.signature.
Payload-ul va include: username-ul utilizatorului, path-ul unde se află la momentul curent utilizatorul în server-ul FTP si flag-ul de admin.
payload {"user": username, "path": path, "admin": true/false}
signature md5 (payload + secret)- Concurența este realizată prin multiplexarea comenzilor prin TCP şi pasarea instantă a comenzii într-unul dintre copii (cel care are cele mai puține task-uri în coadă).
- Soluționarea request-ului va fi făcută de copil integral și trimisă tot de către copil către client, neblocând server-ul din a primi comenzi noi.
- În momentul când un thread termină un task, acesta va trimite către sine semnalul
SIGRTMINcu informația suplimentară: id-ul thread-ului care a trimis semnalul. - Inițial semnalul va fi blocat pentru toate thread-urile, urmând ca în thread-ul principal să fie deblocat după ce se realizează
select().
union sigval data;
data.sival_int = tid;
sigqueue (pid, SIGRTMIN, data);După ce semnalul a fost deblocat şi primit, thread-ul părinte va decrementa numărul de task-uri alocate thread-ului din semal (id).
void handler (int sig, siginfo_t *info, void *context)
{
printf("[server] request completed by %d\n", info->si_int);
--pending_requests [info->si_int];
fflush(stdout);
}- Pentru a evita data race în modificarea simultană a fişierului de configurare, am folosit un mutex guard:
long long get_config_max_size()
{
pthread_mutex_lock(&config_guard);
ifstream fin("config.json");
json config; fin >> config;
long long size = config["size"];
pthread_mutex_unlock(&config_guard);
return size;
}- Legătura cu server-ul FTP pentru comanda ls este făcută printr-o comandă exec și suprascrierea descriptorului de scriere:
close (fd[0]);
dup2 (fd[1], 1);
execlp ("curl", "curl", "-s", "ftp://anonymous:@141.144.228.200", NULL);Copilul va parsa linie cu linie ce primește în urma execuției și va trimite către client câte o linie (pentru a nu aglomera memoria RAM)
Legătura cu server-ul FTP pentru comanda get este făcută printr-o comandă system:
int result = system("curl --max-filesize 1000 ftp://anonymous:@141.144.228.200/name --create-dirs --output ./temp/name12342 > /dev/null 2>&1");Numele fişierului va fi ID-ul copilului care execută comanda (pentru a nu interfera 2 download-uri distincte pentru acelasi fişier, de la 2 copii diferiti).
După descărcare și trimitere, fişierul va fi şters.
Trimiterea fişierului către client se face pe bucăți (pentru a nu memora tot fişierul în RAM) de 4096 bytes și se reasamblează în client.
Trimiterea datelor de autentificare (login/register) este trimisă în mod securizat prin trimiterea parolei trecută printr-o funcție hash.
Înainte de a aplica funcția hash, apendam parola cu un secret key de lungime variabila, cu caractere random, ştiut doar de client pentru a evita atacuri rainbow tables.
Funcția hash aleasă este md5. Baza de date va consta în user entry-uri de forma:
{"user": username, "pass": md5(password + secret), "admin":true/false}Acceptăm convenția: trimitere de date înseamnă trimiterea lungimii mesajului și apoi mesajul.
Clientul urmăreşte protocolul: command-response. După ce clientul citește comanda, acesta trimite către server: comanda + token-ul de identificare al sesiunii.
Ca răspuns, clientul aşteaptă în ordine: status-ul (200 dacă a fost servit cu succes request-ul, 400 dacă este un request forged / nepermis, 404 dacă nu a putut fi găsit fişierul / directorul, 503 dacă server-ul FTP/BD nu răspunde, 401 date de logare invalide, 403 acces neautorizat, 409 conflict în baza de date), data și noul token de identificare updatat (daca este cazul).
Clientul are rolul de a verifica sintaxa comenzilor și de a formata răspunsul primit de către server corespunzător (de exemplu: afişarea în format citibil a fişierului de configurare)
Server-ul primește comanda + token-ul şi le pasează catre un copil pentru procesare.
Verificarea dacă user-ul este banat se face direct în server. Dacă user-ul este banat atunci request-ul nu va fi pasat mai departe și va trimite către client status-ul 403.
Copilul primește comanda + token-ul. Validează token-ul (după caz). Dacă token-ul este invalid oprește execuția comenzii şi trimite către client statusul 400. Altfel se continuă execuția comenzii și se iau informațiile necesare din token.
Pentru:
- ls: copilul trimite către FTP request-ul pentu a afişa directorul curent și așteaptă drept răspuns entry-urile din director. Copilul trimite către client status-ul 200 și entry-urile primite, chiar daca server-ul nu a putut fi accesat. Token-ul ramane neschimbat deci nu va trimis din nou.
- get filename: copilul trimite către FTP request-ul pentru a descărca pe server fişierul filename. Dacă descărcarea reuşeşte, copilul trimite către client status-ul 200 și pachetele corespunzătore pentru a reasambla fişierul în client. Altfel, va trimite 400 dacă fişierul nu corespunde configurării / nu există. Token-ul rămâne neschimbat deci nu va trimis din nou.
- cd dir: copilul trimite către FTP request-ul pentru a verifica existența directorului dir. Dacă acesta există, atunci copilul va întoarce clientului status-ul 200 şi token-ul updatat cu path-ul absolut ce include dir. Altfel, va întoarce clientului status-ul 404 Token-ul rămâne nemodificat.
- login: copilul trimite request către baza de date pentru a valida login-ul. Dacă login-ul reuşeşte, atunci copilul creează un token de sesiune și va trimite clientului status-ul 200 şi token-ul. Altfel, va trimite status-ul 401. Dacă baza de date nu răspunde atunci se va trimite 503.
- register: copilul trimite request către baza de date pentru a înregistra user-ul cu flag-ul de admin setat pe false în baza de date (făcând verificarea ca user-ul să nu existe). Dacă register-ul reușește, atunci copilul va trimite clientului status-ul 200 și token-ul de sesiune nou creat. Altfel, va trimite status-ul 409. Dacă baza de date nu răspunde atunci se va trimite 503.
- modify trait value: copilul verifică token-ul dacă user-ul este admin. Dacă răspunsul este negativ, atunci copilul va trimite către client statusul 403 și va termina execuția comenzii. Altfel, se vor face modificările în fişierul de configurare și se va returna status-ul 200 către client. Token-ul rămâne neschimbat
- pwd: preia din token path-ul. Dacă acesta este invalid, va întoarce către client 400. Altfel 200, alături de path.
- show-config: copilul verifică token-ul dacă user-ul este admin. Dacă răspunsul este negativ, atunci copilul va trimite către client statusul 403 și va termina execuția comenzii. Altfel, va trimite status-ul 200 urmat de fişierul de configurare
- logout: dă clear la token în client
- exit: server-ul scoate descriptorul alocat clientulului din monitorizare şi trimite catre client status-ul 200, urmând ca clientul să oprească definitiv execuția.
| Succes | Eşec |
|---|---|
| Utilizatorul dorește să descarce fişiere de pe un server FTP care este pe alt continent. | Timpi mari de așteptare dacă foarte mulți utilizatori doresc descărcarea unui fişier mai mare de un 1gb. |
| Proxy-ul este aflat la jumătatea distanţei dintre client și server. Astfel proxy-ul ajută descărcarea fişierului, oferind viteze bune. | Utilizatorul încearcă să acceseze un director care are foarte multe fişiere (> 1000). |
| Administratorii server-ului FTP nu doresc ca unele IP-uri să aibă access, din motive de securitate. | Acesta nu poate trece prin toate fişierele pentru că memoria terminalului se termină. |
| Așa că proxy-ul poate bana din start aceste request-uri, fară a mai ajunge la server-ul FTP. | Utilizatorul încearcă descărcarea unui fişier pentru care tipul este banat. Proxy-ul detectează acest scenariu și transmite clientului status-ul 400. |
| Server-ul FTP este folosit la comun în 2 facultăți și nu se dorește ca cineva de la facultatea 1 să aibă access la fişierele facultății 2. Astfel se pot impune restricții prin 2 proxy-uri, pentru fiecare facultate în parte. |
Table 1. Scenarii de utilizare
- O îmbunătățire ar fi neblocarea copiilor în momentul când se descarcă un fişier mare din FTP (nu necesita blocare).
- În contextul ăsta ar trebui ca să existe un mecanism de semnalare când este gata download-ul / verificări regulate ale copilului dacă
$s-a$ terminat descărcarea. - Schimbarea algoritmului de signature în HS256 pentru a evita session spoofing
- Algoritm mai bun pentru asignarea comenzilor către copii
- Trimiterea parolei folosind RSA
- Obligarea utilizatorului să nu pună o parola comună (cifre consecutive, repetitive etc)
- Linux Manual
- Retele de calculatoare

