Kilka dni temu moja lepsza połówka zaczęła temat “jak udostępnić/przesłać komuś duży plik bez rejestracji na dropboxach, rapidsharach i innych”. Oczywiście miała to być opcja dla osób nietechnicznych, dla których FTP to rodzaj Voodoo, i jak najbardziej prywatna. Wszystko miało się sprowadzać do wejścia na stronę, kliknięcia “wybierz plik” i potem “wgraj plik”.
Mogłem do tego wykorzystać dowolnego gotowca, których jest milionpincet w sieci, do uploadowania plików. Był tylko jeden problem z takimi gotowcami: albo wymagały rejestracji albo były publicznie dostępne. “Rejestracja jest be” a na publicznie dostępną apke nie miałem ochoty.
No cóż, “bateria” do łapy, uruchomienie mózgownicy i… eureka! Apacz wpadł na genialny pomysł. System typu challenge-response…
Założenia:
- Kowalski puka do systemu
- System odpowiada Kowalskiemu unikatowym linkiem
- Kowalski wchodzi na podany link i wgrywa pliki
- Link Kowalskiego ma mieć ograniczony czas życia
- Bez indywidualnego linka strona do wgrywania plików ma być niedostępna
- Wgrywane pliki mają mieć unikatowe linki
- Bez odpowiedniego linka, wygenerowanego przez system, do pliku nie można się dostać
- Wgrane pliki mają być przechowywane na serwerze przez określony czas, a potem mają być kasowane
Wszystko brzmi banalnie – ale jak zaprojektować “pukanie” tak żeby Kowalski umiał to zrobić? Przy kolejnym “ładowaniu akumulatora” zaświtał mi pewien pomysł. Maila umie wysłać każdy, czemu więc nie zapukać do systemu mailowo? Ot wysyłamy maila, z określonym tematem, na podany adres i w wiadomości zwrotnej otrzymujemy swój prywatny link. Wgrywa plik(i) a system generuje unikatowe linki do nich.
Skoro już wiemy jak to ma działać i jakie mechanizmy wykorzystamy to przed przystąpieniem do działania trzeba by zerknąć w głąb żeby się zastanowić jak to zrobić.
Pukanie i odpowiadanie
Pukamy mailowo i odpowiadamy mailowo. Ale jak? Z pomocą przychodzi nam sam serwer pocztowy. U mnie działa Postfix więc opiszę to na jego przykładzie. Exim czy Qmail również będą coś takiego potrafiły.
- Zakładamy konto pocztowe, np
knock-knock@example.com
- W
/etc/postfix/main.cf
dodajemy linijkętransport_maps = hash:/etc/postfix/transport - Do
/etc/postfix/transport
dodajemy nasz transportecho "knock-knock@example.com nazwa-transportu:" >> /etc/postfix/transportoraz generujemy mapę transportów
postmap /etc/postfix/transport - W
/etc/postfix/master.cf
ustawiamy transportnazwa-transportu unix - n n - - pipe
user=USER:GRUPA argv=/ścieżka/do/skryptu
Kilka słów “pojaśnienia”. user=USER:GRUPA
jest wymagane przez postfixa przy tworzeniu transportów i określa z jakiego użytkownika będzie wykonany skrypt z argv=/ścieżka/do/skryptu
Transport działa dokładnie jak *nixowy pipe (np tail -f plik | grep fraza
), czyli nasz skrypt będzie musiał poradzić sobie z napływającym mailem linijka po linijce. U mnie skrypt dla transportu został napisany, a jakżeby inaczej ;), w perlu. Skrypt działa w kilku etapach:
- wczytanie wejścia do zmiennej
- zbudowanie ze zmiennej maila
- wyciągnięcie z nagłówków tematu i nadawcy
- przygotowanie treści odpowiedzi i unikatowego linku
- wysłanie odpowiedzi
- zapisanie ID i TTL w pliku (zaprzęganie bazy do kilkunastu czy nawet kilkudziesięciu linków mija się z celem)
WWW – walidacja wejść, wgrywanie plików i generowanie linków
Pierwsza część “kulis” systemu już za nami. Teraz trzeba zająć się forntendem. Całość to aż 2 skrypty w PHP (z domieszką HTML’a): index.php i upload.php. Index.php odpowiedzialny jest za walidację linka “wejściowego”:
- jeśli nie podamy obu parametrów (ID i TTL) – dostaniemy błąd 403/404
- jeśli ID nie będzie istniało w pliku/bazie – dostaniemy błąd 403/404
- jeśli TTL “jest w przeszłości” – dostaniemy błąd 403/404
Jeżeli ID i TTL się zgadzają to wyświetlana jest reszta strony, czyli formularz z 2 przyciskami: “przeglądaj” i “wyślij”. Drugi skrypt, upload.php, odpowiedzialny jest za:
- sprawdzenie listy plików i ich rozmiarów
- usunięcie z nazw plików wszystkich znaków specjalnych i spacji i zastąpienie ich “_”
- zapisanie plików w odpowiednim katalogu
- wygenerowanie linków i wyświetlenie ich
Back to the future – czyli druga część backendu
Mamy już prawie wszystko. Pozostała jeszcze kwestia walidacji linków do pobierania i zabezpieczenie przed dobraniem się do plików bez podawania specjalnie przygotowanego linka. Tu z pomocą przychodzi nam NGiNX i jego moduł “secure_link“. Zamiast bawić się w PHP i kombinować z X-SendFile czy innymi headerami i do tego walidacją parametrów linka zrzucamy to wszystko na serwer WWW. W Gentoo secure_link nie jest domyślnie włączony w NGiNX. Aby go właczyć do /etc/portage/make.conf
dodajemy linijkę:
a następnie przebudowujemy NGiNX
Do vhosta, który obsługuje nasza domenę dodajemy wpis:
secure_link $arg_h,$arg_e;
secure_link_md5 tajne-hasło$uri$arg_e;
if ($secure_link = "") { return 404; }
if ($secure_link = "0") { return 404; }
}
Tym oto sposobem NGiNX dowiaduje się, że odwołania do katalogu /tajne/
muszą mieć 2 parametry
Dodatkowo na podstawie argumentów z linka budowany jest hash MD5
Do budowy hasha wykorzystujemy “tajne-hasło” (to samo hasło musi zostać użyte w skrypcie generującym link inaczej nie dostaniemy się do pliku!), link (ale bez parametrów!) czyli fragment “/tajne/plik.pdf” i argument odpowiedzialny za TTL (czyli wartość drugiego argumentu z linka). Jeśli któreś nie będzie się zgadzać (hash md5 lub ttl) to serwer zwróci nam błąd/stronę podaną jako argument opcji return. Jeśli wszystko będzie OK to NGiNX zaserwuje nam plik, o który go poprosiliśmy.
TODO
Na dniach wrzucę projekt do SVNa i zaktualizuję wpis, poniżej lista rzeczy, które można/trzeba dorobić/poprawić
- style CSS
- walidacja listy plików przed wysłaniem żądania POST (jQuery/AJAX)
- przerobienie uploadu na HTML5/jQuery lub dodanie do index.php XMLHttpRequest (upload bez przeładowywania strony)