Ein selbstgebauter Mailserver bohrt einen SSH-Tunnel zu einem SMTP-Server, falls lokale Clients versuchen, Mails zu senden.
Das Hin- und Herschaufeln von rohen Datenpaketen erledigt mein Internetprovider AT&T normalerweise zufriedenstellend. Fällt allerdings etwas aus, sieht man sich an der Hotline mit textablesenden Nichtswissern konfrontiert, die unter Umgegung elementarster logischer Prinzipien die Schuld auf den Anwender zu schieben versuchen, anstatt in dem Höllenladen ebenfalls arbeitenden geschulten Systemadministratoren Bescheid zu stoßen, die offensichtlich auf AT&T-Seite verursachten Serverprobleme zu beheben. Als ich einmal anrief, um die Trägheit des DNS-Servers zu bemängeln, fragte mich der freundliche Herr am anderen Ende der Leitung doch tatsächlich, ob mein DSL-Modem auf dem Boden oder im Regal stünde.
Nun scheint der mir von AT&T zugewiesene SMTP-Server fast alle an ihn gesendeten Emails wegzuwerfen, und einen erneuten Anruf wollte ich mir ersparen. Viel Email sendet mein Desktop nicht, aber wenn, dann sollte sie schon rausgehen: Fällt zum Beispiel der Strom aus, springt das UPS an, was Nagios bemerkt und schnell noch eine Email schickt, bevor dem System endgültig der Saft abgedreht wird.
Statt dem AT&T-eigenen SMTP-Server könnte ich natürlich den SMTP-Server meines relativ zuverlässigen Hosting-Providers nutzen, doch nimmt der im Spam-Zeitalter natürlich keine Mails von wildfremden IPs an. Aber da der Hoster einen SSH-Zugang anbietet, könnte man mit
ssh -L 1025:localhost:25 mschilli@host.provider.com
zum Beispiel einen Tunnel vom lokalen Port 1025 zum SMTP-Port 25 des Hosters bohren. Ein lokaler SMTP-Client ist auch schnell auf 1025 eingestellt, und für den Hoster sähe es dann so aus, als käme der Request vom gemieteten Shared-Host-Webserver und nicht vom Desktop daheim.
Da ein Billig-Hoster eventuell nicht möchte, dass irgendwelche
Geizhälse Tag und Nacht ssh-Tunnel offenhalten, ohne auf ihren gemieteten
Webseiten herumzutippen, bietet sich eine dynamische Lösung an: Ein
in Perl selbstgebauter Dämon minimail
lauscht auf dem lokalen SMTP-Port 25
auf Anfragen lokaler Mail-Clients, die nichts von der dahinter
steckenden Komplexität ahnen.
Der Dämon nimmt den Request entgegen, baut den Tunnel zum Hoster auf und trödelt anschließend solange herum, bis die Verbindung steht. Für den lokalen Mailclient sieht dass so aus, als hätte er nur einen etwas langsameren Mailserver vor sich. Der Dämon schaufelt dann die Request-Zeilen des Clients (lokaler Port 25) auf den lokalen Port 1025 weiter, also den Eingang des Tunnels, auf dessen anderem Ende Port 25 des Hosters liegt. Aus dem Tunnel zurückkommende Protokollzeilen gibt der Dämon weiter an den lokalen Client, für den es aussieht, als spräche er mit lokalen einem SMTP-Server.
Kommen mehrere Requests zum Email-Senden schnell hintereinander, wäre ein Ab- und wieder Aufbauen des Tunnels wenig effektiv, und so lässt der Dämon den Tunnel nach dem Abkoppeln des letzten Clients noch 10 Sekunden bestehen, in der Hoffnung, den nächsten Client schneller bedienen zu können. Nach etwa 10 Sekunden Inaktivität fährt der Dämon den Tunnel dann herunter. Damit das Ganze in den Hosterlogs menschlich aussieht, addiert das Skript eine Zufallszahl zwischen 0 und 25.
01 #!/usr/local/bin/perl -w 02 use strict; 03 use Sysadm::Install qw(:all); 04 use App::Daemon qw(daemonize); 05 use Log::Log4perl qw(:easy); 06 07 BEGIN { 08 sudo_me(); 09 $App::Daemon::as_user = "root"; 10 $App::Daemon::logfile = 11 "/var/log/minimail.log"; 12 $App::Daemon::loglevel = $INFO; 13 daemonize(); 14 }; 15 16 use POE; 17 use PoCoForwarder; 18 use PoCoTimedProcess; 19 20 my $port_from = 25; 21 my $port_to = 25; 22 my $tunnel_port = 1025; 23 my $real_smtp_host = 'host.provider.com'; 24 25 my $process = PoCoTimedProcess->new( 26 heartbeat => 10, 27 timeout => int(rand(25)) + 10, 28 command => ["ssh", '-N', '-L', 29 "$tunnel_port:localhost:$port_to", 30 $real_smtp_host], 31 ); 32 33 my $forwarder = PoCoForwarder->new( 34 port_from => $port_from, 35 port_to => $tunnel_port, 36 port_bound => sub { 37 INFO "Dropping privileges"; 38 $< = $> = getpwnam($ENV{SUDO_USER}); 39 }, 40 client_connect => sub { 41 $process->launch(); 42 }, 43 ); 44 45 $process->spawn(); 46 $poe_kernel->run();
Damit der Dämon den SMTP-Port 25 mit bind()
an sich reißen kann, muss
er unter root
laufen, und um das damit verbundene Sicherheitsrisiko
zu mindern, gibt er diese Privilegien später wieder ab. Ein mit
sudo
also root
gestartetes Programm führt in der
Environment-Variablen SUDO_USER
den Nutzer, der den Sudo-Befehl
ausführte, und auf diesen unpriviligierten User stutzt das Skript
später seine Rechte zurück. Der Befehl sudo_me()
in Zeile 8 aus dem Modul
Sysadm::Install vom CPAN prüft, ob das Skript unter root
läuft und startet sich selbst noch einmal mit sudo
, falls
dies nicht der Fall war.
Das Dämonen-Werkzeug App::Daemon vom CPAN exportiert die Funktion
daemonize()
, die dafür sorgt, dass der Dämon die Befehle
minimail start|stop
kennt und sang- und klanglos im
Hintergrund arbeitet, sobald er die Startsequenz durchlaufen hat, und
nur die Logdatei zeigt an, was der Dämon gerade treibt.
Das Log4perl-Logfile setzt -l (oder die Variable App::Daemon::logfile)
und wird der Dämon mit -X im Vordergrund gestartet, erscheint die
Logausgabe auf STDERR.
Der BEGIN-Block ab Zeile 7 sorgt dafür, dass das POE Modul (Zeile 16) erst nach dem Dämonisieren des Prozesses (Zeile 13) geladen wird. Das ist wichtig, denn so wurde mir von einer hilfreichen Seele auf der POE-Mailingliste erklärt, denn sonst schießt POE abgefeuerte Kindprozesse nicht richtig ab.
Da App::Daemon selbst ein Feature zum Abgeben der Root-Privilegien
bietet, stellt Zeile 9 ``root'' in der Variablen $as_user
des
Moduls ein und überlässt damit den Sicherheits-Umschwung dem Skript,
das ihn direkt nach dem Binden des Dämons an Port 25 ab Zeile
37 ausführt.
Ein selbstgeschriebener Netzwerkdämon kostet normalerweise
beträchtlich Schweiß und Tränen, doch zum Glück bietet das
CPAN einige POE-Komponenten, die man nur wie Legosteine
aneinanderstecken muss. So formt minimail
aus den Komponenten
POE::Component::Client::TCP und ::Server::TCP den
Port-Forwarder PoCoForwarder. Er hängt sich an den lokalen
$port_from und leitet alles dort ankommende an den Tunnelport
$tunnel_port weiter und umgekehrt. Das ist keine triviale Angelegenheit,
denn am lokalen Port können mehrere Mailclients gleichzeitig hängen
und müssen parallel bedient werden.
Die zweite Komponente, PoCoTimedProcess, fährt einen Prozess wie den
Tunnel mit der Methode launch()
für eine vorbestimmte Zeit
hoch oder verlängert seine Lebenszeit, falls er schon oben ist.
Jedes Mal, wenn der Forwarder einen neu angedockten Client feststellt,
ruft er im client_connect()
-Callback hierzu die Methode launch()
auf.
Diese schnappt sich das ssh-Kommando
ssh -N -L 1025:localhost:25 host.provider.com
in Zeile 28 und verbindet sich damit mit dem Host host.provider.com
über das verschlüsselte ssh-Protokoll, loggt sich dort ein, spannt
aber dank der Option -N
keine interaktive Shell auf sondern
hängt nur so da und leitet die Datenströme vor- und zurück. Der
Port 1025 ist das Desktop-seitige Ende des Tunnels, der
``localhost'' im ssh-Befehl
bezieht sich allerdings auf host.provider.com, da ssh sich jetzt
dort befindet. Der dem Doppelpunkt folgende
Port 25 ist der SMTP-Port des Hosters.
Falls der Username auf dem Hosting-Rechner nicht derselbe ist wie auf
dem Desktop, darf ihn der Aufruf mit mschilli@host.provider.com
voranstellen, damit ssh Bescheid weiß.
Was nun geht hinter den Kulissen in den beiden POE-Komponenten vor? Abbildung 1 zeigt das Diagramm mit den Server- und Clientkomponenten, sowie den jeweils genutzten Portnummern. Der im Port-Forwarder auf Port 25 lauschende TCP-Server spannt pro Client jeweils eine TCP-Client-Komponente auf, um diesen mit dem Tunnel zu verbinden.
Als Parameter erwartet die Klasse den Port
``port_from'' (auf den der Server lauscht),
``port_to'' (der Tunnelport), sowie zwei Callbackroutinen.
Die unter port_bound
abgelegte Subroutinenreferenz springt die
Komponente an, sobald
der Server sich mit Port 25 verbunden hat und damit keine Root-Rechte
mehr benötigt. Beim Aufgeben der Root-Rechte ist darauf zu achten, dass dies
in der richtigen Reihenfolge für effektive und reale User-ID geschieht,
sonst kann der Dämon hinterher die Root-Rechte wieder herstellen
([2]). Wären hier mehrere Threads gleichzeitig zugange, müsste
PoCoTimedProcess intern aufpassen, dass keine Race-Condition den
Tunnel zweimal startet, aber in der von POE bereitgestellten
Ein-Prozess-ein-Thread-Umgebung genügt ein einfacher Variablen-Check
ohne jegliches Locking. Robust, einfach hinzuschreiben und später
leicht zu begreifen!
Der zweite Callback des Forwarders, client_connect
, wird jedesmal
angesprungen, wenn ein Mailclient an Port 25 andockt. Die im
Callback ausgeführte Methode launch()
der Komponente PoCoTimedProcess
fährt daraufhin den Tunnel hoch, falls dieser noch nicht besteht.
Intern stellt PoCoForwarder jeder Client-Verbindung eine eigene
POE-Komponente vom Typ PoCo::Client::TCP zur Seite, die mit dem
Tunnelport Kontakt aufnimmt. Während also PoCo::Server::TCP beliebig viele
Clients betreut, nutzt es für jede Client-Verbindung eine
separate PoCo::Client::TCP-Komponente. Die Ursache hierfür liegt
im Design der letzteren, da sie explizit auf einen Client ausgelegt ist,
während der Server naturgemäß mit vielen Clients quasi-gleichzeitig
kommuniziert.
Abbildung 1: Der Mail-Client kommuniziert mit Port 25 des Forwarders, dessen Client wiederum mit dem Tunnel kommuniziert. |
01 package PoCoForwarder; 02 use strict; 03 use Log::Log4perl qw(:easy); 04 use POE::Component::Server::TCP; 05 use POE::Component::Client::TCP; 06 use POE; 07 08 ########################################### 09 sub new { 10 ########################################### 11 my($class, %options) = @_; 12 13 my $self = { %options }; 14 15 my $server_session = 16 POE::Component::Server::TCP->new( 17 ClientArgs => [$self], 18 Port => $self->{port_from}, 19 ClientConnected => \&client_connect, 20 ClientInput => \&client_request, 21 Started => sub { 22 $self->{port_bound}->(@_) if 23 defined $self->{port_bound}; 24 }, 25 ); 26 27 return bless $self, $class; 28 } 29 30 ########################################### 31 sub client_connect { 32 ########################################### 33 my ($kernel, $heap, $session, $self) = 34 @_[ KERNEL, HEAP, SESSION, ARG0]; 35 36 $self->{client_connect}->(@_) if 37 defined $self->{client_connect}; 38 39 my $client_session = 40 POE::Component::Client::TCP->new( 41 RemoteAddress => "localhost", 42 RemotePort => $self->{port_to}, 43 ServerInput => sub { 44 my $input = $_[ARG0]; 45 # $heap is the tcpserver's (!) heap 46 $heap->{client}->put( $_[ARG0] ); 47 }, 48 Connected => sub { 49 $_[HEAP]->{connected} = 1; }, 50 Disconnected => sub { 51 $kernel->post( $session, "shutdown" ); 52 }, 53 ConnectError => sub { 54 $_[HEAP]->{connected} = 0; 55 $kernel->delay('reconnect', 1); 56 }, 57 ServerError => sub { 58 ERROR $_[ARG0] if $_[ARG1]; 59 $kernel->post( $session, "shutdown" ); 60 }, 61 ); 62 63 $heap->{client_heap} = $kernel-> 64 ID_id_to_session( $client_session )-> 65 get_heap(); 66 } 67 68 ########################################### 69 sub client_request { 70 ########################################### 71 my ($kernel, $heap, $request) = 72 @_[ KERNEL, HEAP, ARG0 ]; 73 74 return if # tunnel not up yet, discard 75 ! $heap->{client_heap}->{connected}; 76 77 $heap->{client_heap}-> 78 {server}->put( $request ); 79 } 80 81 1;
Zeile 22 in PoCoForwarder.pm zeigt, wie die Komponente den Callback
port_bound
implementiert. Der in Zeile 16 erzeugte
POE-TCP-Server springt nach einem erfolgreichen Start den Zustand
Started
an, und dort kramt PoCoForwarder die von minimail
definierte Subroutinenreferenz aus dem Objekthash $self hervor und
springt sie an. Der in minimail definierte Callback-Code tut den Rest.
Zu beachten ist, dass $self sich gar nicht im Scope der dem
Zustand Started
zugewiesenen Handlers befindet. Vielmehr stammt
sie aus dem Konstruktor new()
der Klasse PoCoForwarder, aber die
Subroutine verwandelt sich dadurch in eine sogenannte Closure, die
die lexikalische Variable $self
umschließt und die darum auch
nach dem Verlassen des Konstruktor-Scopes (aber nur innerhalb des
Callbacks) gültig bleibt.
Andererseits sorgt der Parameter ClientArgs
in Zeile 17 dafür,
dass die Server-Komponente den Objekthash $self auch als Argument
ARG0 mitliefert, falls sie die Callback-Funktion client_connect
anspringt. Dort ruft Zeile 36 den von minimail gesetzten
Callback client_connect
auf, der den Tunnel hochfährt. Nun ergibt
sich allerdings ein Timing-Problem, denn es ist schwer vorherzusagen,
wann der Tunnel steht, und deswegen versucht der Client eventuell,
sich mit einem Port zu verbinden, auf dem niemand lauscht. Doch das
ist kein Problem, denn in diesem Fall springt der TCP-Client den
Zustand ConnectError
ab Zeile 53 an, der mittels der
POE-Kernelfunktion delay()
einfach einen ``reconnect''-Event
für eine Sekunde später in POEs Terminkalender einträgt. Dieses Spielchen
setzt sich eventuell ein paar Runden fort, doch irgendwann steht
der Tunnel, der TCP-Client verbindet sich erfolgreich mit dem nun
betriebenen Port und springt deshalb den Zustand Connected
ab
Zeile 48 an.
Falls minimail ein Kommando sendet, springt der TCP-Server den
Zustand client_request
und damit den gleichnamigen Handler
ab Zeile 69 an. Dieser prüft, ob der Tunnel bereits hochgefahren
wurde, und verwirft den Client-Text, falls die Verbindung noch nicht
steht. Im SMTP-Protokoll meldet sich der Server zuerst mit einer
Grußmeldung, und deswegen wird ein wohlerzogener Client nicht losplappern,
bevor der Tunnel steht. Bei anderen Protokollen (wie z.B. http) wäre
dies anders, und in
diesem Fall müsste die Forwarder-Komponenente die Client-Kommandos
puffern bis die Verbindung steht, und sie dann im Client-Auftrag gebündelt
an den Server weiterleiten.
Ist der Tunnel hingegen betriebsbereit, wurde vorher im Zustand
Connected
die Heap-Variable connected
auf 1 gesetzt.
Um die Nachricht an den Tunnel weiterzuleiten, kramt Zeile 77
aus dem in client_heap
zwischengespeicherten Heap des
TCP-Clients die Tunnelreferenz hervor und schickt ihr mit
put()
den eingegangenen Text. Zu beachten ist hier, dass
client_request ein Callback der Server-Session ist, die vom
Client, der in einer anderen Session tobt, und seinem Heap, nichts weiß.
Die Heap-Variable client_heap
in der Server-Session löst das Problem.
Kommen Nachrichten aus dem Tunnel zurück, tritt der TCP-Client in
den Zustand ServerInput
ab Zeile 43, der den Text mit der im
Heap gespeicherten client-Referenz und der Methode put()
an
minimail zurückgibt.
Falls minimail sich vom TCP-Server abkoppelt, springt dieser in den
Zustand Disconnected
, worauf der Handler dort einen
``shutdown''-Event an die laufende Session schickt, was die
Client-Server-Verbindung trennt.
Die Komponente PoCoTimedProcess.pm hingegen kümmert sich ausschließlich
um den Auf- und Abbau des Tunnels. Startet minimail
in Zeile
45 mit spawn
die zugehörige POE-Session, springt es zunächst
den ab Zeile 33
in PoCoTimedProcess.pm definierten _start-Handler an. Dieser
extrahiert (wiederum durch eine Closure) alle wichtigen Parameter
wie heartbeat
(Check-Frequenz für den Timeout), timeout
(Anzahl
der Sekunden bis zum Tunnelabbruch) und command
(das ssh-Kommando zum
Aufbau des Tunnels)
aus dem Objekt-Hash self
und legt sie im Session-eigenen Heap ab.
Anschließend setzt er zwei Events zur späteren Abbarbeitung durch
den POE-Kernel ab, ``keep-alive'' und ``heartbeat''. Ersterer setzt
die Heap-Variable countdown
auf das Maximum zurück: die in
timeout
liegende Maximalzahl der Sekunden, die ein Tunnel offen bleibt.
Den Zustand ``heartbeat'' hingegen ruft POE wegen der delay-Methode in
Zeile 65 regelmäßig auf, immer wenn
die in der Heap-Variablen heartbeat
liegende
Sekundenzahl verstrichen ist.
Der Tunnel ist zu diesem Zeitpunkt noch geschlossen, doch sobald die
launch()
-Methode den Event up
absetzt und POE den zugehörigen
Handler up
(Zeile 84) aktiviert, startet ein POE-Rädchen vom
Typ POE::Wheel::Run
(Zeile 97) den ssh-Prozess. Die in den Zeilen
109 und 110 definierten Handler für die Unix-Signale TERM und INT
sorgen dafür, dass ein abgeschossener minimail
-Prozess auch einen
eventuell geöffneten Tunnel mit einreißt.
Erreicht der Tunnel seine maximale Lebensdauer, setzt Zeile 77 den
Event ``down'' ab und der gleichnamige Handler ab Zeile 117 schickt dem
ssh-Prozess ein kill-Signal. Damit andere Handler darüber Bescheid wissen,
dass es den Tunnel nun nicht mehr gibt, setzt down()
die Variable
is_up
auf 0. Das auslösende Signal ist nun fertig bearbeitet, und
würde Zeile 129 den POE-Kernel nicht über sig_handled()
darüber
informieren, zöge dieser die Notbremse und beendete den Dämon.
Damit aus dem abgeschossenen Prozess kein Zombie entsteht, der sich
mit Artgenossen zusammenschließt und im Lauf der Zeit den Rechner lahmlegt,
definiert Zeile 108 einen sig_child
-Handler, der den sterbenden
Prozess abfängt und den ab Zeile 44 definierten Handler sig_child
anspringt. Damit verpasst POE dem abnippelnden Tunnel
mit waitpid()
die letzte Ölung und bewahrt ihn damit vor dem ewig
lodernden Höllenfeuer. Der Handler löscht die letzte Referenz auf
das POE::Wheel, worauf POE den Kernel ordnungsgemäß zusammenfaltet.
001 package PoCoTimedProcess; 002 use strict; 003 use warnings; 004 use POE; 005 use POE::Wheel::Run; 006 use Log::Log4perl qw(:easy); 007 008 ########################################### 009 sub new { 010 ########################################### 011 my($class, %options) = @_; 012 013 my $self = { %options }; 014 bless $self, $class; 015 } 016 017 ########################################### 018 sub launch { 019 ########################################### 020 my($self) = @_; 021 022 $poe_kernel->post($self->{session},'up'); 023 } 024 025 ########################################### 026 sub spawn { 027 ########################################### 028 my($self) = @_; 029 030 $self->{session} = 031 POE::Session->create( 032 inline_states => { 033 _start => sub { 034 my($h,$kernel) = @_[HEAP, KERNEL]; 035 036 $h->{is_up} = 0; 037 $h->{command} = $self->{command}; 038 $h->{timeout} = $self->{timeout}; 039 $h->{heartbeat} = 040 $self->{heartbeat}; 041 $kernel->yield('keep_alive'); 042 $kernel->yield('heartbeat'); 043 }, 044 sig_child => sub { 045 delete $_[HEAP]->{wheel}; 046 }, 047 heartbeat => \&heartbeat, 048 up => \&up, 049 down => \&down, 050 keep_alive => sub { 051 $_[HEAP]->{countdown} = 052 $_[HEAP]->{timeout}; 053 }, 054 closing => sub { 055 $_[HEAP]->{is_up} = 0; 056 }, 057 })->ID(); 058 } 059 060 ########################################### 061 sub heartbeat { 062 ########################################### 063 my($kernel, $heap) = @_[KERNEL, HEAP]; 064 065 $kernel->delay( "heartbeat", 066 $heap->{heartbeat}); 067 068 if( $heap->{is_up} ) { 069 INFO "Process is up for another ", 070 $heap->{countdown}, " seconds"; 071 072 $heap->{countdown} -= 073 $heap->{heartbeat}; 074 075 if($heap->{countdown} <= 0) { 076 INFO "Time's up. Shutting down"; 077 $kernel->yield("down"); 078 return; 079 } 080 } 081 } 082 083 ########################################### 084 sub up { 085 ########################################### 086 my ($heap, $kernel) = @_[ HEAP, KERNEL ]; 087 088 if($heap->{is_up}) { 089 INFO "Is already up"; 090 $_[KERNEL]->yield('keep_alive'); 091 return 1; 092 } 093 094 my($prog, @args) = @{ $heap->{command} }; 095 096 $heap->{wheel} = 097 POE::Wheel::Run->new( 098 Program => $prog, 099 ProgramArgs => [@args], 100 CloseEvent => "closing", 101 ErrorEvent => "closing", 102 StderrEvent => "ignore", 103 ); 104 105 my $pid = $heap->{wheel}->PID(); 106 INFO "Started process $pid"; 107 108 $kernel->sig_child($pid, "sig_child"); 109 $kernel->sig( "INT" => "down" ); 110 $kernel->sig( "TERM" => "down" ); 111 112 $_[KERNEL]->yield('keep_alive'); 113 $heap->{is_up} = 1; 114 } 115 116 ########################################### 117 sub down { 118 ########################################### 119 my ($heap, $kernel) = @_[ HEAP, KERNEL ]; 120 121 if(! $heap->{is_up}) { 122 INFO "Process already down"; 123 return 1; 124 } 125 126 INFO "Killing pid ", $heap->{wheel}->PID; 127 $heap->{wheel}->kill(); 128 $heap->{is_up} = 0; 129 $kernel->sig_handled(); 130 } 131 132 1;
Da ein Dämon sich nicht mit einem interaktiv eingegebenen Passwort identifizieren kann, erfordert das obige ssh-Kommando, dass der User vorher mit
ssh-keygen -t rsa
ein Schlüsselpaar erzeugt, dessen Daten typischerweise in den
Dateien id_rsa
(private key) und id_rsa.pub
(public key)
im Directory .ssh
unter dem Home-Verzeichnis des ausführenden
Users landen. Damit der Hoster den Dämon hereinlässt, muss der
User den mit der Option ``no passphrase'' erzeugten Public Key
auf den Host-Server spielen. Hierzu kopiert er einfach den lokalen
Inhalt der Datei id_rsa.pub
in die Datei .ssh/authorized_keys
auf dem Hosting-Server. Das von Hand eingebene ssh-Kommando oben
(ohne die Option -N) sollte sich nun ohne Rückfragen auf dem
Hostingserver einloggen.
Abbildung 2: Bei der ersten Anfrage muß Minimail erst den Tunnel öffnen ... |
Ob der mit sudo minimail start
gestartete Mailserver tatsächlich
funktioniert, findet der
Telnet-Aufruf aus Abbildung 2 auf localhost
und Port 25 heraus.
Ist der Tunnel des Dämons ordnungsgemäß heruntergefahren, verzögert
Minimail die Antwort etwa ein, zwei Sekunden, bis der Mailserver des
Providers antwortet und stellt dann durch (Abbildung 3). Wer ein
paar Sätze SMTP spricht, kann so gleich (selbstverständlich nur
zu Testzwecken) allerhand Schabernack treiben (Abbildung 4).
In der Logdatei /var/log/minimail.log
protokolliert der Dämon mit,
was gerade geschieht (Abbildung 5). Aus Datenschutzgründen unterbleibt
das Mitschneiden der Mailheader oder -texte.
Abbildung 3: ... und nach ein bis zwei Sekunden antwortet der SMTP-Server am anderen Ende des Tunnels ... |
Das telnet
-Kommando bleibt übrigens stecken, falls der Server die
Verbindung nicht irgendwann abbricht. Die Kombination CTRL-] hilft aus
der Breduille, in dem sie telnet
in eine Shell fallen lässt, aus
der dann das Kommando q
herausführt.
Abbildung 4: ... mit dem der Client dann ganz normal SMTP-Kommandos austauscht. Moment mal: Versucht hier etwa ein Schlingel, sich als Steve Jobs auszugeben? |
Abbildung 5: In der Logdatei protokolliert der Dämon die wichtigsten Ergeignisse mit. |
Um den Minimailserver schon beim Booten hochzufahren (der nächste Stromausfall kommt bestimmt), ist die folgende Zeile
SUDO_USER=mschilli /path/to/minimail
unter Ubuntu in eine eventuell neu anzulegende Datei /etc/init.d/minimail
einzuspeisen, diese mit chmod +x ausführbar zu machen und anschließend
sudo update-rc.d minimail defaults 80
aufzurufen, damit Ubuntu das Skript in den Bootvorgang einhängt. Kommt dann der Strom wieder, fährt der Mailserver automatisch hoch und ein vorher aufgesetzter Nagios-Plugin meldet dem erfreuten Besitzer per Email die überstandene Katastrophe.
Listings zu diesem Artikel: ftp://www.linux-magazin.de/pub/listings/magazin/2010/07/Perl
Privilegien abgeben, aber richtig: http://perlmonks.com/?node_id=833950
Michael Schilliarbeitet als Software-Engineer bei Yahoo! in Sunnyvale, Kalifornien. Er hat "Goto Perl 5" (deutsch) und "Perl Power" (englisch) für Addison-Wesley geschrieben und ist unter mschilli@perlmeister.com zu erreichen. Seine Homepage: http://perlmeister.com. |