Aus Linux-Magazin 07/2010

Perl-Skript tunnelt Mailverkehr auf Zuruf

© Thomas Max Müller, Pixelio.de

Das Hin- und Herschaufeln von Datenpaketen erledigt mein Internetprovider normalerweise zufriedenstellend. Fällt allerdings etwas aus, dann sehe ich mich in der Hotline oft mit Text-ablesenden Nichtswissern konfrontiert, die unter Umgehung elementarer logischer Prinzipien die Schuld auf den Anwender zu schieben versuchen. Sie sollten lieber den in dem Höllenladen ebenfalls arbeitenden geschulten Systemadministratoren Bescheid sagen, damit sie die offensichtlich auf ihrer Seite liegenden Probleme 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.

Im Spam-Zeitalter

Kürzlich hatte ich Probleme mit dem SMTP-Server und wollte mir einen erneuten Anruf ersparen. Viel E-Mail sendet mein heimischer Desktop-Rechner zwar nicht, aber wenn, dann sollte sie schon ankommen. Fällt etwa der Strom aus, springt meine USV an, was Nagios bemerkt und mir schnell noch eine E-Mail schickt.

Ich könnte natürlich den SMTP-Server meines relativ zuverlässigen Hostingproviders nutzen, doch nimmt der im Spam-Zeitalter natürlich keine Mails von wildfremden IPs an. Aber da der Hoster einen SSH-Zugang anbietet, ließe sich zum Beispiel mit

ssh -L 1025:localhost:25 mschilli@host.U
provider.com

ein Tunnel vom lokalen Port 1025 zum SMTP-Port 25 des gehosteten Rechners bohren. Für den beim Hoster stationierten Rechner sähe es dann so aus, als käme der Request vom gemieteten Shared-Host-Webserver.

Dynamisch bohren

Da ein Billighoster 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 selbst gebauter Daemon »minimail« lauscht auf dem lokalen SMTP-Port 25 auf Anfragen lokaler Mailclients, die nichts von der dahinter steckenden Komplexität ahnen.

Der Daemon nimmt den Request entgegen, baut den Tunnel zum Hoster auf und trödelt anschließend so lange 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 Daemon schaufelt dann die Request-Zeilen des Clients (lokaler Port 25) auf den lokalen Port 1025 weiter, also den Eingang des Tunnels, an dessen anderem Ende Port 25 beim Hoster liegt (Abbildung 1). Aus dem Tunnel zurückkommende Protokollzeilen gibt der Daemon weiter an den lokalen Client, für den es so aussieht, als spräche er mit einem lokalen SMTP-Server.

Kommen mehrere Requests zum E-Mail-Senden schnell hintereinander, wäre ein Ab- und wieder Aufbauen des Tunnels wenig effizient, und so lässt der Daemon den Tunnel nach dem Abkoppeln des letzten Clients noch 10 Sekunden bestehen. Damit das Ganze in den Hosterlogs menschlich aussieht, addiert das Skript zur Wartezeit in Sekunden eine Zufallszahl zwischen 0 und 25.

Root oder nicht

Damit der Daemon in Listing 1 den SMTP-Port 25 mit »bind()« an sich reißen kann, muss er unter der Benutzerkennung »root« laufen, und um das damit verbundene Sicherheitsrisiko zu mindern, gibt er diese Privilegien später wieder ab. Ein mit »sudo« gestartetes Programm führt in der Environment-Variablen »SUDO_USER« den Nutzer, der den Sudo-Befehl ausführte. Auf die Rechte dieses unprivilegierten Users stutzt das Skript später seine Rechte zurück. Der Befehl »sudo_me()« in Zeile 8 aus dem CPAN-Modul Sysadm::Install prüft, ob »root« das Skript gestartet hat, und holt dies im Bedarfsfall mit »sudo« nach.

Listing 1:
»minimail«
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();

Das Dämonen-Werkzeug App::Daemon vom CPAN exportiert die Funktion »daemonize()«, die dafür sorgt, dass der Daemon die Befehle »minimail start|stop« kennt und sang- und klanglos im Hintergrund arbeitet, sobald er die Startsequenz durchlaufen hat – nur die Logdatei zeigt an, was er gerade treibt. Das Log4perl-Logfile setzt »-l« (oder die Variable »App::Daemon::logfile«). Wird der Daemon mit »-X« im Vordergrund gestartet, erscheint die Logausgabe auf »Stderr«.

Der »BEGIN«-Block ab Zeile 7 sorgt dafür, dass das POE-Modul in Zeile 16 erst nach dem Dämonisieren des Prozesses (Zeile 13) geladen wird. Das sei wichtig, so erklärte mir eine hilfreiche Seele auf der POE-Mailingliste, denn sonst schießt POE von ihm erzeugte Kindprozesse nicht richtig ab.

Da App::Daemon selbst ein Feature zum Abgeben der Root-Privilegien bietet, weist Zeile 9 des Moduls der Variablen »$as_user« den Wert »root« zu und überlässt damit den Sicherheits-Umschwung dem Skript, das ihn direkt nach dem Binden des Daemon an Port 25 ab Zeile 37 ausführt.

POE zur Hilfe

Ein selbst geschriebener Netzwerkdaemon kostet normalerweise mächtig Schweiß und Tränen, doch zum Glück bietet das CPAN einige POE-Komponenten, die ich nur wie Legosteine aneinanderstecken muss. So formt »minimail« aus den Komponenten POE::Component::Client::TCP und POE::Component::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 ebenso. Das ist keine triviale Angelegenheit, denn am lokalen Port können mehrere Mailclients gleichzeitig hängen. Die sind dann parallel zu bedienen.

Die zweite Komponente, also »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 läuft. Jedes Mal, wenn der Forwarder einen neu angedockten Client feststellt, ruft er im »client_connect()«-Callback die Methode »launch()« auf. Diese schnappt sich das SSH-Kommando

ssh -N -L 1025:localhost:25 U
host.provider.com

in Zeile 28 und verbindet sich so mit dem Host »host.provider.com« über das verschlüsselte SSH-Protokoll, loggt sich dort ein, startet aber dank der Option »-N« keine interaktive Shell, 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. Die dem Doppelpunkt folgende »25« ist der SMTP-Port des gehosteten Rechners. Falls der Username auf dem Hosting-Rechner nicht der gleiche ist wie auf dem Desktop, darf ihn der Aufruf mit »mschilli@host.provider.com« voranstellen, damit SSH Bescheid weiß.

Komponentenkleber

Was geht nun hinter den Kulissen in den beiden POE-Komponenten vor? Abbildung 1 zeigt das Diagramm mit den Server- und Client-Komponenten 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 sie mit dem Tunnel zu verbinden.

Abbildung 1: Der Mailclient unterhält sich mit dem Port 25 des Forwarders, dessen Client wiederum mit dem Tunnel kommuniziert.

Abbildung 1: Der Mailclient unterhält sich mit dem Port 25 des Forwarders, dessen Client wiederum mit dem Tunnel kommuniziert.

Als Parameter erwartet die Klasse den Port »port_from« (auf dem der Server lauscht), »port_to« (den Tunnelport) sowie zwei Callback-Routinen. Die Komponente springt die unter »port_bound« abgelegte Subroutinen-Referenz 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-IDs geschieht, sonst kann der Daemon hinterher die Root-Rechte wiederherstellen [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 Variablencheck ohne jedes Locking – robust, einfach hinzuschreiben und später leicht zu begreifen!

Der zweite Callback des Forwarders, »client_connect«, wird jedes Mal 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 Clientverbindung 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 Clientverbindung eine separate PoCo::Client::TCP-Komponente.

Closures: Verwirrend schön

Zeile 23 in Listing 2 zeigt, wie die Komponente den Callback »port_bound« implementiert. Der in Zeile 17 erzeugte POE-TCP-Server springt nach erfolgreichem Start den Zustand »Started« an. Dort kramt »PoCoForwarder« die von »minimail« definierte Subroutinen-Referenz aus dem Objekthash »$self« und verzweigt dorthin. Der in Minimail definierte Callback-Code tut den Rest.

Listing 2:
»PoCoForwarder.pm«
01 ###########################################
02 package PoCoForwarder;
03 use strict;
04 use Log::Log4perl qw(:easy);
05 use POE::Component::Server::TCP;
06 use POE::Component::Client::TCP;
07 use POE;
08
09 ###########################################
10 sub new {
11 ###########################################
12   my($class, %options) = @_;
13
14   my $self = { %options };
15
16   my $server_session =
17   POE::Component::Server::TCP->new(
18     ClientArgs     => [$self],
19     Port           => $self->{port_from},
20     ClientConnected  => &client_connect,
21     ClientInput      => &client_request,
22     Started          => sub {
23       $self->{port_bound}->(@_) if
24         defined $self->{port_bound};
25     },
26   );
27
28   return bless $self, $class;
29 }
30
31 ###########################################
32 sub client_connect {
33 ###########################################
34   my ($kernel, $heap, $session, $self) =
35           @_[ KERNEL, HEAP, SESSION, ARG0];
36
37   $self->{client_connect}->(@_) if
38     defined $self->{client_connect};
39
40   my $client_session =
41   POE::Component::Client::TCP->new(
42     RemoteAddress => "localhost",
43     RemotePort    => $self->{port_to},
44     ServerInput   => sub {
45       my $input = $_[ARG0];
46         # $heap is the tcpserver's (!) heap
47       $heap->{client}->put( $_[ARG0] );
48     },
49     Connected => sub {
50         $_[HEAP]->{connected} = 1; },
51     Disconnected => sub {
52      $kernel->post( $session, "shutdown" );
53     },
54     ConnectError => sub {
55      $_[HEAP]->{connected} = 0;
56      $kernel->delay('reconnect', 1);
57     },
58     ServerError => sub {
59      ERROR $_[ARG0] if $_[ARG1];
60      $kernel->post( $session, "shutdown" );
61     },
62   );
63
64   $heap->{client_heap} = $kernel->
65     ID_id_to_session( $client_session )->
66     get_heap();
67 }
68
69 ###########################################
70 sub client_request {
71 ###########################################
72   my ($kernel, $heap, $request) =
73      @_[ KERNEL, HEAP, ARG0 ];
74
75   return if  # tunnel not up yet, discard
76     ! $heap->{client_heap}->{connected};
77
78   $heap->{client_heap}->
79          {server}->put( $request );
80 }
81
82 1;

Zu beachten ist, dass »$self« sich gar nicht im Scope des dem Zustand »Started« zugewiesenen Handlers befindet. Vielmehr stammt sie aus dem Konstruktor »new()« der Klasse »PoCoForwarder«, aber die Subroutine verwandelt sich dadurch in eine so genannte Closure, die die lexikalische Variable »$self« umschließt und die daher auch nach dem Verlassen des Konstruktor-Scope (aber nur innerhalb des Callback) gültig bleibt.

Andererseits sorgt der Parameter »Client-Args« in Zeile 18 dafür, dass die Serverkomponente den Objekthash »$self« auch als Argument »ARG0« mitliefert, falls sie die Callback-Funktion »client_connect()« anspringt. Dort ruft Zeile 37 den von Minimail gesetzten Callback »client_connect()« auf, der den Tunnel hochfährt. Nun ergibt sich allerdings ein Timingproblem, denn es ist schwer vorherzusagen, wann der Tunnel steht. 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 54 an, der mit 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 arbeitenden Port und wechselt deshalb in den Zustand »Connected« (ab Zeile 49).

Falls Minimail ein Kommando sendet, verzweigt der TCP-Server zum Zustand »client_request« und damit zum gleichnamigen Handler ab Zeile 70. Der prüft, ob der Tunnel bereits steht, und verwirft den Client-Text, falls die Verbindung down ist. Im SMTP-Protokoll meldet sich der Server zuerst mit einer Grußmeldung – deswegen wird ein wohlerzogener Client nicht losplappern, bevor der Tunnel steht. Bei anderen Protokollen (zum Beispiel HTTP) wäre dies anders, in diesem Fall müsste die Forwarder-Komponente die Clientkommandos puffern, bis die Verbindung steht, und sie dann im Auftrag des Clients 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 78 aus dem in »client_heap« zwischengespeicherten Heap des TCP-Clients die Tunnelreferenz hervor und schickt ihr mit »put()« den eingegangenen Text. An dieser Stelle ist allerdings zu beachten, dass die Funktion »client_request()« ein Callback der Serversession ist, die vom Client, der in einer anderen Session arbeitet, und seinem Heap nichts weiß. Die Heap-Variable »client_heap« in der Serversession löst das Problem.

Kommen jetzt Nachrichten aus dem Tunnel zurück, dann wechselt der TCP-Client in den Zustand »ServerInput« ab Zeile 43, der daraufhin seinerseits den Text mit der im Heap gespeicherten Clientreferenz und der Methode »put()« an Minimail zurückgibt.

Für den Fall, dass Minimail sich von dem TCP-Server abkoppeln sollte, springt dieser daraufhin in den Zustand »Disconnected«. Das wiederum bewirkt, dass der Handler dort einen Shutdown-Event an die laufende Session schickt, was die Client-Server-Verbindung schließlich trennt. Die Komponente »PoCoTimedProcess.pm« in Listing 3 hingegen kümmert sich um nichts anderes als um den Auf- und Abbau des Tunnels.

Listing 3:
»PoCoTimedProcess.pm«
001 ###########################################
002 package PoCoTimedProcess;
003 use strict;
004 use warnings;
005 use POE;
006 use POE::Wheel::Run;
007 use Log::Log4perl qw(:easy);
008
009 ###########################################
010 sub new {
011 ###########################################
012   my($class, %options) = @_;
013
014   my $self = { %options };
015   bless $self, $class;
016 }
017
018 ###########################################
019 sub launch {
020 ###########################################
021   my($self) = @_;
022
023   $poe_kernel->post($self->{session},'up');
024 }
025
026 ###########################################
027 sub spawn {
028 ###########################################
029   my($self) = @_;
030
031   $self->{session} =
032   POE::Session->create(
033     inline_states => {
034       _start => sub {
035         my($h,$kernel) = @_[HEAP, KERNEL];
036
037         $h->{is_up} = 0;
038         $h->{command} = $self->{command};
039         $h->{timeout} = $self->{timeout};
040         $h->{heartbeat} =
041                        $self->{heartbeat};
042         $kernel->yield('keep_alive');
043         $kernel->yield('heartbeat');
044       },
045       sig_child => sub {
046           delete $_[HEAP]->{wheel};
047       },
048       heartbeat => &heartbeat,
049       up   => &up,
050       down => &down,
051       keep_alive => sub {
052         $_[HEAP]->{countdown} =
053           $_[HEAP]->{timeout};
054       },
055       closing => sub {
056         $_[HEAP]->{is_up} = 0;
057       },
058    })->ID();
059 }
060
061 ###########################################
062 sub heartbeat {
063 ###########################################
064   my($kernel, $heap) = @_[KERNEL, HEAP];
065
066   $kernel->delay( "heartbeat",
067                   $heap->{heartbeat});
068
069   if( $heap->{is_up} ) {
070     INFO "Process is up for another ",
071       $heap->{countdown}, " seconds";
072
073     $heap->{countdown} -=
074       $heap->{heartbeat};
075
076     if($heap->{countdown} <= 0) {
077         INFO "Time's up. Shutting down";
078         $kernel->yield("down");
079         return;
080     }
081   }
082 }
083
084 ###########################################
085 sub up {
086 ###########################################
087   my ($heap, $kernel) = @_[ HEAP, KERNEL ];
088
089   if($heap->{is_up}) {
090       INFO "Is already up";
091       $_[KERNEL]->yield('keep_alive');
092       return 1;
093   }
094
095   my($prog, @args) = @{ $heap->{command} };
096
097   $heap->{wheel} =
098     POE::Wheel::Run->new(
099       Program     => $prog,
100       ProgramArgs => [@args],
101       CloseEvent  => "closing",
102       ErrorEvent  => "closing",
103       StderrEvent => "ignore",
104   );
105
106   my $pid = $heap->{wheel}->PID();
107   INFO "Started process $pid";
108
109   $kernel->sig_child($pid, "sig_child");
110   $kernel->sig( "INT"  => "down" );
111   $kernel->sig( "TERM" => "down" );
112
113   $_[KERNEL]->yield('keep_alive');
114   $heap->{is_up} = 1;
115 }
116
117 ###########################################
118 sub down {
119 ###########################################
120   my ($heap, $kernel) = @_[ HEAP, KERNEL ];
121
122   if(! $heap->{is_up}) {
123       INFO "Process already down";
124       return 1;
125   }
126
127   INFO "Killing pid ", $heap->{wheel}->PID;
128   $heap->{wheel}->kill();
129   $heap->{is_up} = 0;
130   $kernel->sig_handled();
131 }
132
133 1;

Prozesse auf Zeit

Startet »minimail« in Zeile 45 mit »spawn« die zugehörige POE-Session, springt es zunächst den ab Zeile 34 in »PoCoTimedProcess.pm« definierten »_start «-Handler an. Dieser extrahiert (wiederum durch eine Closure) alle wichtigen Parameter wie »heartbeat« (Checkfrequenz für den Timeout), »timeout« (Anzahl der Sekunden bis zum Tunnelabbruch) und »command« (das SSH-Kommando zum Aufbau des Tunnels) aus dem Objekthash »self« und legt sie im Session-eigenen Heap ab. Anschließend setzt er zwei Events zur späteren Abarbeitung 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 66 regelmäßig auf, 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 98) den SSH-Prozess. Die in den Zeilen 110 und 111 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 78 den Event »down« ab und der gleichnamige Handler ab Zeile 118 schickt dem SSH-Prozess ein Kill-Signal. Damit andere Handler darüber Bescheid wissen, dass es den Tunnel nun nicht mehr gibt, setzt »down()« wiederum die Variable »is_up« auf den Wert »0«. Das auslösende Signal ist damit fertig bearbeitet, würde Zeile 130 den POE-Kernel nicht über »sig_handled()« darüber informieren, dann zöge dieser jetzt die Notbremse und beendete den Daemon.

Damit aus dem abgeschossenen Prozess kein Zombie entstehen kann, der sich mit Artgenossen zusammenschließt und im Lauf der Zeit den Rechner lahmzulegten droht, definiert Zeile 109 einen »sig_child«-Handler, der den sterbenden Prozess abfängt und den ab Zeile 45 definierten Handler »sig_child« startet. Mit dessen Hilfe verpasst POE dem abnippelnden Tunnel mit »waitpid()« noch die letzte Ölung und bewahrt ihn damit vor dem ewig lodernden Höllenfeuer. Der Handler löscht schließlich die letzte noch verbliebene Referenz auf das POE::Wheel, worauf POE den Kernel ordnungsgemäß zusammenfaltet.

Da ein Daemon 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 Homeverzeichnis des ausführenden Users landen.

Schlüssel statt Passwort

Damit der Hoster den Daemon hereinlässt, muss der User den mit der Option »no passphrase« erzeugten Public Key auf den Server spielen. Hierzu kopiert er einfach den lokalen Inhalt der Datei »id_rsa.pub« in die Datei ».ssh/authorized_keys« auf dem Hostingserver. Das von Hand eingebene SSH-Kommando oben (ohne die Option -N) sollte sich nun ohne Rückfragen auf dem Hostingserver einloggen.

Testlauf mit Telnet

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 Daemon ordnungsgemäß heruntergefahren, verzögert Minimail die Antwort um etwa 1, 2 Sekunden, bis der Mailserver des Providers antwortet und stellt dann durch (Abbildung 3).

Abbildung 2: Sobald eine Mail zu verschicken ist, muss Minimail bei der ersten Anfrage den Tunnel zunächst öffnen ...

Abbildung 2: Sobald eine Mail zu verschicken ist, muss Minimail bei der ersten Anfrage den Tunnel zunächst öffnen …

Abbildung 3: ... und nach etwa 1 bis 2 Sekunden antwortet dann der SMTP-Server am anderen Ende des Tunnels ...

Abbildung 3: … und nach etwa 1 bis 2 Sekunden antwortet dann der SMTP-Server am anderen Ende des Tunnels …

Abbildung 4: ... mit dem der Client ganz normal SMTP-Kommandos austauscht. Der Server glaubt, dass er mit einem lokalen Partner spricht.

Abbildung 4: … mit dem der Client ganz normal SMTP-Kommandos austauscht. Der Server glaubt, dass er mit einem lokalen Partner spricht.

Wer ein paar Sätze SMTP spricht, kann damit gleich (selbstverständlich nur zu Testzwecken) allerhand Schabernack treiben (Abbildung 4). In der Logdatei »/var/log/minimail.log« protokolliert der Daemon mit, was gerade geschieht (Abbildung 5). Aus Datenschutzgründen unterlässt er das Mitschneiden der Mailheader oder -texte.

Abbildung 5: In der Logdatei protokolliert der Daemon die wichtigsten Ereignisse.

Abbildung 5: In der Logdatei protokolliert der Daemon die wichtigsten Ereignisse.

Das Telnet-Kommando bleibt übrigens stecken, falls der Server die Verbindung nicht irgendwann abbricht. Die Tastenkombination [Ctrl]+[]] hilft aus der Bredouille, indem sie »telnet« in eine Shell fallen lässt, aus der dann das Kommando »q« herausführt.

Stromausfall kommt bestimmt

Um den Minimail-Server schon beim Booten hochzufahren, ist die Zeile

SUDO_USER=mschilli /path/to/minimail

unter Ubuntu in eine eventuell neu anzulegende Datei »/etc/init.d/minimail« einzuspeisen, diese ist 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 aufgesetztes Nagios-Plugin meldet dem erfreuten Besitzer per E-Mail die überstandene Katastrophe. (jcb)

Infos
[1] Listings zu diesem Artikel: [ftp://www.linux-magazin.de/pub/listings/magazin/2010/07/Perl]

[2] Privilegien abgeben, aber richtig: [http://perlmonks.com/?node_id=833950]

Der Autor
Michael Schilli arbeitet als Software-Engineer bei Yahoo in Sunnyvale, Kalifornien. Er hat die Bücher “Goto Perl 5” (deutsch) und “Perl Power” (englisch) für Addison-Wesley geschrieben und ist unter [mschilli@perlmeister.com] zu erreichen.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 6 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben