Mechanizmy antyspamowe dla Postfixa

Na czym polega greylisting?

Ostatnimi czasy zdecydowana większość spamu rozsyłana jest nie, jak dawniej, przez open relaye, ale przez specjalnie spreparowane trojany lub wirusy. Takie spamowarki występują zbyt licznie (przyczyny tego stanu to osobna kwestia), aby miało sens ich ręczne blacklistowanie; również systemy RBL typu SpamCop nie zdają egzaminu, ponieważ zwykle w momencie zgłoszenia i odnotowania w RBL jest już za późno - spamowarka zdążyła wysłać tysiące sztuk spamu. Programy wyłapujące spam przez analizę treści maila (SpamAssassin, bogofilter) nie zawsze sobie radzą z nowszymi formami spamu, w których często dodawane są losowe słowa, aby zaburzyć działanie statystyczno-heurystycznych analizatorów.

Wdrożyłem ostatnio na AGH rozwiązanie, które w znacznym stopniu - o niemal 99% - zredukowało problem spamu (a przy okazji także zredukowało o ok. 90% liczbę wirusów, odciążając skaner antywirusowy). Jest to tzw. greylisting - metoda pozwalająca na wyeliminowanie spamowarek bez potrzeby korzystania z zewnętrznych źródeł informacji typu RBL.

Greylisting polega na wykorzystaniu podstawowej różnicy pomiędzy spamowarkami a serwerami pocztowymi: te ostatnie, jeśli przy próbie dostarczenia maila do serwera docelowego otrzymają odpowiedź "4xx" (kod błędu tymczasowego), powtórzą próbę dostarczenia poczty po jakimś czasie i będą powtarzać do skutku lub do upłynięcia limitu czasu (zwykle kilka dni), po którym odeślą informację o niedostarczeniu (zwrotkę) do nadawcy. Spamowarki, jak dotąd, tego nie robią - błąd tymczasowy traktują na równi z permanentnym i po prostu rezygnują z wysłania spamu, przechodząc do następnego adresata.

Wystarczy zatem działać zgodnie z następującym schematem:

  1. Gdy do naszego serwera łączy się host X i mówi, że ma mail od Y do Z, sprawdzamy w lokalnej bazie, czy X próbował już kiedyś przesłać mail od Y do Z (tzn. czy trójka X/Y/Z jest obecna w bazie);
  2. Jeżeli tak, to sprawdzamy, jak dawno - jeśli ponad np. 10 minut temu, to wpuszczamy;
  3. Jeżeli nie, to zapisujemy X/Y/Z do bazy wraz z aktualnym czasem, i odpowiadamy X-owi np.: 450 Service temporarily unavailable; serwer za chwilę spróbuje znowu i zostanie wpuszczony.

Oczywiście, istnieje ryzyko, że za jakiś czas spamowarki zostaną udoskonalone i będą powtarzać próby np. co godzinę - wtedy metoda przestanie być skuteczna. Wtedy będziemy się martwili, co dalej. Na razie zamiast dotychczasowych ok. 300 spamów dziennie dostaję góra 3.

Jak zaimplementować greylisting w Postfiksie?

Po pierwsze, potrzebny jest program, który będzie pracował jako tzw. policy server (Uwaga: policy servers są obsługiwane począwszy od wersji 2.1 Postfixa, dla starszych wersji należy zacząć od upgrade...) - poniżej załączam taki program napisany przeze mnie w Perlu na podstawie przykładu dołączonego do dystrybucji Postfixa (Uwaga: przykład nie nadaje się do bezpośredniego użytku ze względu na zastosowany mechanizm filelockingu - jeśli kogoś to interesuje, to tu jest objaśnione, co w nim złego); w razie potrzeby należy w nim pozmieniać parametry $database_name i $greylist_delay. Program korzysta z modułów DB_File::Lock, Fcntl i Sys::Syslog, z których pierwszy nie jest częścią standardowej dystrybucji Perla i trzeba go sobie doinstalować.

Po drugie, trzeba kazać Postfixowi uruchamiać ten program jako usługę - w tym celu należy wpisać do master.cf taką linię:

policy    unix  -       n       n       -       0       spawn user=nobody argv=/usr/bin/perl /usr/lib/postfix/greylist -v

(ścieżki zmienić w razie potrzeby; opcja -v może wystąpić od 0 do 3 razy, co wpływa na poziom szczegółowości logów; alternatywnie można napisać np. -v3).

Po trzecie, w main.cf trzeba się jakoś odwołać do policy servera, najprawdopodobniej w smtpd_recipient_restrictions, np. jakoś tak:

smtpd_recipient_restrictions =
# pozwalamy wysyłać z sieci lokalnej lub uwierzytelnionym użytkownikom
        permit_mynetworks
        permit_sasl_authenticated
# poza tym nikomu
        reject_unauth_destination
# dodatkowe usprawnienie - odrzucamy pocztę do nieistniejących użytkowników
# lub od na pewno nieistniejących nadawców (z nieistniejących domen)
# jeszcze przed greylistingiem
        reject_unlisted_recipient
        reject_unknown_sender_domain
# greylisting
        check_policy_service unix:private/policy
# jeśli greylisting nie odrzucił, to wpuszczamy
        permit

No i jeszcze trzeba się upewnić, że wszystko ma właściwe prawa - u mnie to wygląda tak:

drwxr-xr-x 2 nobody nogroup 4096 Jan  6 14:46 /var/mta
-rw------- 1 nobody nogroup 258048 Jan 11 19:12 /var/mta/greylist.db
-rwxr-xr-x 1 root root 7049 Jan  7 15:31 /usr/lib/postfix/greylist

Mam nadzieję, że nie zapomniałem o niczym istotnym... aha, raz na jakiś czas można oczyścić bazę ze śmieci za pomocą skryptu greylist_expire.

UWAGA: Ten program nie jest już rozwijany. Z przyczyn wydajnościowych zdecydowałem się na migrację do postgrey (zob. niżej). Pozostawiam jednak mój kod dostępny dla zainteresowanych, raczej w celach edukacyjnych niż exploatacyjnych.

Download:

Alternatywy

Warto też rzucić okiem na rozwiązanie konkurencyjne: postgrey Davida Schweikerta. W odróżnieniu od mojego kodu, jest porządnie udokumentowane; poza tym ma parę dodatkowych możliwości, które jednak uważam za nie tylko zbędne, ale wręcz szkodliwe - konkretnie chodzi mi o to, że domyślnie ma ustawione:

--auto-whitelist-clients=1 --lookup-by-subnet

Pierwsza z tych opcji powoduje, że każdy host, z którego choć raz został dostarczony mail (tzn. przeszedł greylisting) zostaje automatycznie uznany za serwer SMTP i od tej pory nie podlega greylistingowi; miałoby to głęboki sens, gdyby nie fakt, że w małych sieciach serwer SMTP często równocześnie jest routerem zapewniającym NAT (maskaradę) dla sporej liczby klientów; wyłączając z greylistingu serwer, równocześnie wyłączamy wszystkich NAT-owanych klientów, którzy mogą teraz spamować do woli...

Druga opcja oznacza, że jeśli adres IP powiedzmy x.y.z.n został uznany za OK, to automatycznie OK jest cała podsieć x.y.z/24, a przecież w tej sieci poza x.y.z.n (serwerem SMTP) może być jeszcze mnóstwo klientów - potencjalnych spamowarek.

Tym niemniej, po ustawieniu:

--auto-whitelist-clients=0 --lookup-by-host

postgrey powinien działać sensownie.

Valid XHTML 1.0! Szymon Sokół <szymon@uci.agh.edu.pl>, 11.01.2006 (last modified 12.04.2010)