Własna bramka pocztowa www

Grzegorz Jemielity

Po co bramka www ?

Poczta elektroniczna stała się powszechnym narzędziem nie ograniczonym do zamkniętych środowisk naukowych i informatycznych. W związku z tym programy typu mutt czy pine nie spełniają oczekiwań wielu użytkowników. Również ich okienkowi następcy (np. Outlook Express lub Netscape Mail) są dla wielu osób niewystarczające. Wystarczy choćby wymienić tych, którzy korzystają z różnych stacji roboczych lub nie posiadają w ogóle komputera odbierając pocztę okazjonalnie. Spotykam też coraz więcej osób, które nawet nie mają zamiaru spróbować skonfigurować własnego programu pocztowego uważając to za zajęcie zbyt trudne (lub zbędne). I mają do tego prawo - abnegacja informatyczna już nie musi oznaczać niemożności korzystania z dobrodziejstw poczty elektronicznej.

Rozwiązaniem dla tych wszystkich osób staje się dostęp do poczty poprzez witryny www (tzw. bramki pocztowe). W tej chwili żaden poważny ISP nie może sobie pozwolić na dostarczenie klientowi konta e-mail bez możliwości odbioru poczty poprzez własne strony www. Takie bramki znajdziemy zarówno na znanych portalach jak i na stronach lokalnych ISP i nikogo one nie dziwią. Raczej zaskakujący byłby ich brak.

Czemu nie gotowe rozwiązanie ?

No właśnie - czemu ? Freshmeat po zapytaniu zasypie nas gotowymi darmowymi rozwiązaniami typu At-Dot. Są też komercyjne programy oferowane wraz ze wsparciem technicznym i pomocą w instalacji. Nie ma jednak róży bez kolców ...

Zacznijmy od komercyjnego oprogramowania. Główna wada - koszt. ;) Wiele serwerów pocztowych to zwykłe PC + Linux, a wielu niedużych dostawców kont poczty elektronicznej liczy się z każdym wydatkiem. Rozwiązanie komercyjne jest dla nich nie do przyjęcia.

Oprogramowanie darmowe też nie jest pozbawione wad. Nie każdy administrator jest na tyle dobrym programistą by przeanalizować (ach te dziury w CGI ...) i przerobić cudzy kod do własnych potrzeb. A jeśli nawet nie będzie problemów z instalacją, zmianą wyglądu i ustawień to np. okaże się że nie ma obsługi MIME, lub jest jakiś mały błąd którego nie potrafimy naprawić.

Na szczęście jest jeszcze jedna alternatywa. Takie narzędzia jak Perl pozwalają nawet początkującym programistom i administratorom na stworzenie skonsolidowanego systemu dającego możliwość samodzielnego zakładania kont użytkownikom, wglądu i edycji danych, zmiany hasła i odbioru poczty poprzez strony www. W niniejszym artykule opiszę te ostatnie zagadnienie - często przez początkujących programistów uznawane za zbyt dla nich trudne (a zapewniam iż takie nie jest).

Co nam będzie potrzebne ?

Przede wszystkim Linux jako system operacyjny (nazwa dystrybucji pominięta celowo), Apache (lub inny serwer www obsługujący skrypty CGI) oraz Perl. Zarówno Apache jak i Perl wchodzą w skład większości dystrybucji, więc nie musimy się o to martwić. Zakładam też że czytelnik posiada podstawową wiedzę z zakresu programowania w języku Perl oraz tworzenia w nim skryptów CGI. Konieczne też będzie doinstalowanie kilku modułów opisanych poniżej. Wszystkie są dostępne w archiwach CPAN, np. pod adresem ftp://sunsite.icm.edu.pl/pub/CPAN/ .

Moduł Mail::POP3Client

Cytując perldoc Mail::POP3Client: "Jest to moduł klienta POP3 dla perl5. Dostarcza zorientowanego obiektowo interfejsu do serwera POP3." Niektórzy z czytelników mogą w tym momencie zapytać: czemu nie korzystać z bezpośredniego dostępu do plików z pocztą ? Zamiast łączyć się poprzez port jako klient można przecież "zajrzeć" do /var/spool/mail/user i skopiować zawartość skrzynki, jednak nie jest to idealne rozwiązanie. Korzystając z serwera POP3 uniezależniamy się od platformy. Unikamy kłopotów w przypadku gdy używany jest inny sposób przechowywania poczty (np. dla Netscape Messaging Server), uzyskujemy dostęp do poleceń obsługiwanych przez serwer POP3, no i dodatkowo mamy możliwość pobierania poczty z dowolnego serwera. Wbrew pozorom nie występuje też problem z nadmiernym obciążeniem. Napisany przeze mnie program oparty na tym module udostępniał pocztę ponad 15000 użytkownikom na zwykłym PC (Pentium II 300 MHz, 128 MB RAM, Linux Slackware) nie obciążając zbytnio systemu.

Instalacja modułu przebiega bezproblemowo. Sprowadza się ona właściwie do czterech komend

$ perl Makefile.PL
$ make
$ make test
# make install
Testowałem instalowanie modułu na Slackware 3.6, Slackware 7.1 oraz RH 7.0 i nie napotkałem żadnych problemów.

Moduł POP3Client dostarcza nam wiele użytecznych funkcji. Postaram się omówić je po kolei. Zaczniemy od zadeklarowania modułu i połączenia się z serwerem:

#!/usr/bin/perl
use POP3Client;
$pop=new POP3Client(LOGIN, HASLO, ADRES_SERWERA_POP);
Można też podać numer portu, jeśli używany jest nietypowy. Poprawność połączenia można sprawdzić funkcją State, lub poprzez pobranie liczby wiadomości - jeśli jest ujemna to połączenie nie powiodło się. A oto lista najważniejszych funkcji modułu:
$pop->Count		- liczba wiadomości
$pop->Size		- łączny rozmiar poczty
$pop->List($n)		- rozmiar n-tej wiadomości
$pop->Head($n)		- nagłówek n-tej wiadomości
$pop->Body($n)		- treść n-tej wiadomości
$pop->HeadAndBody($n)	- nagłówek i treść n-tej wiadomości
$pop->Delete($n)	- kasuje n-tą wiadomość
$pop->Close		- kończy połaczenie z serwerem
Pozostałe dostępne są w dokumentacji, ale jako mniej przydatne nie zostały tu wymienione.

Wykorzystując powyższe funkcje bez problemu możemy uzyskać listę wiadomości, pobrać ich nagłówki, wydostać z nagłówków nadawcę, temat i datę (wystarczy znaleźć odpowiednie przypasowania, np. /^(From): /), a następnie wyświetlić treść żądanych wiadomości. Wystarczy do tego odrobina cierpliwości i podstawowa wiedza o pisaniu skryptów CGI w perlu. Może nas jednak zaskoczyć nieczytelny temat wiadomości, umieszczony w treści załącznik lub wiadomość w formacie HTML. Z tymi problemami można sobie poradzić w prosty sposób.

Zakodowany tytuł wiadomości

Tytuł wiadomości zawierający inne znaki niż [A-Za-z0-9+/=] powinien być zakodowany zgodnie z zaleceniem RFC 822. W praktyce wpierw musimy określić, jaka metoda kodowania została użyta: base64 czy quoted-printable. Wystarczy sprawdzić czy temat wiadomości odpowiada poniższym wzorom:
/^\=\?iso-8859-2\?B\?/ - base64
/^\=\?iso-8859-2\?Q\?/ - quoted-printable
Teraz do rozkodowania możemy użyć modułów MIME::Base64 i MIME::QuotedPrint. Instalacja wygląda identycznie jak dla POP3Client a użycie jest bardzo proste:
use MIME::Base64;
$rozkodowane = encode_base64('zakodowane');
lub
use MIME::QuotedPrint;
$rozkodowane = encode_qp($zakodowane);
Można też rozkodować to samemu. Wystarczy usunąć z początku sekwencję opisującą kodowanie i skorzystać z którejś z poniższych procedur:
sub usun {			#usuwa zbędne znaki z tytułu
	$tem{$i} =~ /^\=\?iso-8859-2\?[BQ]\?/ ){   
	$tem{$i} =~ s/^\=\?iso-8859-2\?[BQ]\?//; 
	$tem{$i} =~ s/\?\=$//;
}
sub qp {   			# odkodowanie quoted-printable 
	my $a=$_[0];
	my $b="";
	$_ = $a;
	while ($a =~ /=\w\w/g) {
		$b=chr(hex(substr($&, -2)));
		$a =~ s/$&/$b/
	}
	return $a;
}
sub base64 {   		# odkodowanie base64
	my $a=$_[0];
	my $w="";
	my @tabl=split(//, $a);
	foreach (@tabl){
		my $aa=unpack("C*", $_);
		if ( $aa >= 65 and $aa <=90 ) {$aa=$aa-65}
		elsif ( $aa >= 97 and $aa <=122 ) {$aa=$aa-71}
		elsif ( $aa >= 48 and $aa <=57 ) {$aa=$aa+4}
		elsif ( $aa eq 43 ) {$aa=62}
		elsif ( $aa eq 47 ) {$aa=63}
		elsif ( $aa eq 61 ) {$aa=255}
		$aa=unpack("B32", pack("N", $aa));
		$aa = substr($aa, -6);
		$w=$w.$aa;
	}
	my $bb=pack("B*", $w);
	if ( substr($a, -2) eq "==" ){ substr($bb, -2)=""}
	elsif ( substr($a, -1) eq "=" ){ substr($bb, -1)=""}
	return $bb;
}
Oczywiście kod procedur można skrócić, zależało mi jednak na pokazaniu w czytelny sposób ich działania. Ogólnie jednak polecam użycie modułów MIME:Base64 i MIME:QuotedPrint.

Moduły MIME-tools

Jeśli poczta jest wysłana w formacie HTML, lub zawiera załącznik musi to wszystko być zawarte w jednym pliku tekstowym. Używany jest do tego format MIME (Multipurpose Internet Mail Extensions) opisany w dokumentach RFC 1521-1522, oraz RFC 2045-2049. Można samemu napisać procedury rozkodowujące format MIME (wystarczy sporo cierpliwości i samozaparcia - wiem bo spróbowałem), ale dużo prostszym rozwiązaniem jest skorzystanie z zestawu modułów kryjących się pod nazwą MIME-tools.

Do poprawnego działania MIME-tools potrzebna jest wcześniejsza instalacja kilku modułów. Oto ich lista:

File::Patch
File::Spec
IO::Scalar (część pakietu IO-Stringy)
MIME::Base64
MIME::QuotedPrint
Net::SMTP
Mail::Internet (część pakietu MailTools)
Proszę się nie zrażać długością listy. Wszystkie te moduły są dostępne w archiwach CPAN, a ich instalacja jest szybka i bezproblemowa.

Nas najbardziej interesuje moduł MIME::Parser. Jest on (podobnie jak pozostałe) bardzo dokładnie opisany w dokumentacji dostarczonej wraz z modułem. Poniżej zamieszczam przykładowy program rozkodowujący pojedynczą wiadomość na poszczególne składowe MIME. Uruchamia się go poleceniem nazwa_programu < wiadomosc.msg.

#!/usr/bin/perl
use MIME::Parser;
#------------------------------
# rekursywna procedura pobierania części MIME
sub dump_entity {
    my ($entity, $name) = @_;
    defined($name) or $name = "'brak nazwy'";
    my $IO;

    # Wyświetla nagłówki:
    print "\n", '=' x 60, "\n";
    print "Message $name: ";
    print "\n", '=' x 60, "\n\n";
    print $entity->head->original_text;
    print "\n";

    # Wyświetla treść:
    my @parts = $entity->parts;
    if (@parts) {                     # jeśli jest wiele części...
	my $i;
	foreach $i (0 .. $#parts) {       # wywołaj tę procedurę dla każdej z nich ...
	    dump_entity($parts[$i], ("$name, part ".(1+$i)));
	}
    }
    else {                            # jeśli pojedyncza...	

	# Sprawdź typ MIME i odpowiednie wyświetl...
	my ($type, $subtype) = split('/', $entity->head->mime_type);
	my $body = $entity->bodyhandle;
	if ($type =~ /^(text|message)$/) {     # wyświetl tekst...
	    if ($IO = $body->open("r")) {
		print $_ while (defined($_ = $IO->getline));
		$IO->close;
	    }
	    else {
		print "$0: nie mogę znaleźć/otworzyć '$name': $!";
	    }
	}
	else {                                 # typ binarny...
	    my $path = $body->path;
	    my $size = ($path ? (-s $path) : '???');
	    print ">>> Typ binarny wiadomości, wielkość $size bitów.\n";
	    print ">>> Przechowywana w ", ($path ? "'$path'" : 'core'),".\n\n";
	}
    }
    1;
}

#------------------------------
# procedura główna
sub main {
    print STDERR "(czytam z stdin)\n" if (-t STDIN);
    my $parser = new MIME::Parser;
    
    # Utwórz katalog danych wyjściowych:
    (-d "mimedump-tmp") or mkdir "mimedump-tmp",0755 or die "mkdir: $!";
    (-w "mimedump-tmp") or die "błąd zapisu do katalogu";
    $parser->output_dir("mimedump-tmp");	#pliki tmp na dysku
    # lub pomiń 3 powyższe linie i użyj komendy poniżej
    #$parser->output_to_core(1);		#pliki tmp w RAM
    
    # Czytaj wiadomość MIME:
    $entity = $parser->read(\*STDIN) or die "nie mogę odkodować formatu mime";

    dump_entity($entity);
}
exit(&main ? 0 : -1);

#------------------------------
1;

Jest to program wzorowany na przykładach z dokumentacji modułu, pokazujący jak w praktyce działa rozkodowywanie formatu MIME. Można go z niewielkimi zmianami wykorzystać do własnej bramki www. Polecam jednak zapis plików tymczasowych do pamięci RAM, a nie na dysku twardym.

Podsumowanie

Artykuł miał za zadanie udowodnić, że napisanie własnej bramki www w perlu nie jest wcale trudnie nawet dla początkujących programistów. Jednocześnie nie prezentowałem gotowego programu, a jedynie dostarczałem jak najwięcej wskazówek do napisania własnego. Spowodowane jest to tym, że na podstawie powyższych modułów stworzyłem kilka aplikacji różniących się stopniem komplikacji wraz ze wzrostem moich umiejętności posługiwania się tak doskonałym narzędziem jakim jest Perl. Wszystkich zaintereswanych tematem zachęcam do własnych prób i do zapoznania się z dokumentacją modułów. Sądzę, że warto.