|
Komplexes Beispiel: Steuern per InternetDieses Beispiel soll in mehreren Schritten zeigen, wie das Ein- und Ausschalten von Geräten über das Internet realisiert werden kann. Es beginnt mitdem Entwurf eines sicheren Protokolls zwischen Client und Server (der dann dieSteuerungsaufgabe übernimmt).Im weiteren Ausbau wird der Client zu einem CGI-Skript erweitert, das dann über ein Webformular mit dem Benutzer kommuniziert. Die gesamte Konfiguration stellt sich folgendermaßen dar: HardwareTechnische Daten der Relaiskarte:- Hersteller: Conrad Electronic
- Bestellnummer: 967720
- Betriebsspannung: 11 ... 15 V DC
- Maximale Stromaufnahme: 200mA bei 12 V (8 Relais eingeschaltet)
- Relais Schaltleistung: 230 V AC/ 4 A, ohne gesonderte Funktions- und Sicherheitsprüfung der Gesamtapplikation sind maximal 24 V/ 2 A zulässig!
- Außenmaße: 160 x 100 mm (Europlatine)
- Serielle Schnittstelle: RS232, 19200 Baud, 8 Datenbits, 1 Stopbit, kein Paritätsbit, kein Handshake, Nullmodemkabel zum Anschluß an den PC verwenden
Es können bis zu 255 Relaiskarten kaskadiert werden (255 Relaiskarten * 8 Relais = 2040
schaltbare Relais insgesamt). Wer sich weiter informieren will, kann sich
ansehen.
Anschluß des Netzteiles: Zuerst die richtige Spannung am Netzteil
einstellen (9 V sind trotz anders angegebener technischer Daten
völlig ausreichend). Dan den am Netzteil vorhandenen Klinkenstecker
abschneiden und beide Adern des Kabels abisolieren. Nun das Kabel an
Klemme K9 anschliessen (richtige Polung beachten).
Nun wird das Nullmodemkabel angeschlossen. Man kann auch selbst ein Seriellkabel
löten. Dies bietet sich an, wenn die Relaiskarte in ein Gehäse eingebaut
wird, da dort kein Platz mehr für die Steckverbindung ist. Die Steckerbelegung
zeigt das nebenstehende Bild. Die Klemmen auf der Relaiskarte befinden sich neben der
seriellen Buchse.
Möchte man mehrere Karten anschließen, so muß man den Anschluß
TXb der ersten Relaiskarte mit dem Anschluß RXa der zweiten Karte, und den
Anschluß RXb der ersten Karte mit dem Anschluß TXa der zweiten Karte
verbinden. Dann werden noch die GND-Anschüsse verbunden. Beachten Sie die
Einstellung von Jumper JP3. Beim Einzelkartenbetrieb muß sich der Jumper
in der Position 1-2 befinden. Beim Mehrkartenbetrieb müssen sich die Jumper
aller Karten bis auf den der letzten in Position 2-3 befinden und nur auf der
letzten Karte darf er sich in Position 1-2 befinden. Das folgende Bild zeigt
das Anschluß-Schema:
Die Karte wurde in den Conrad Power-Manager (Best. Nr. 998575) eingebaut. Das Gerät
besteht aus einem Stahlblechgehäuse, das auf der Vorderseite sieben Schalter und auf
der Rückseite sieben korrespondierende Schuko-Steckdosen besitzt.
Die Relaiskontakte 1 - 7 der Relaiskarte sind in Reihe zu den Schaltern verdrahtet, so
daß die im Schalter eingebauten Glimmlampen als Betriebsanzeige wirken (Schalter
eingeschaltet). Ist ein Schalter in Stellung "aus", bleibt die entsprechende Steckdose
unabhängig von der Relais-Stellung stromlos. Für die Versorgung der Relais-Karte
wurde noch ein passendes 12-V-Netzteil eingebaut. Der Power Manager erfüllt so gleichzeitig
drei Funktionen: Anzeige, Berührschutz und Gehäse für alle Komponenten. Da nur
sieben Steckdosen vorhanden sind, wurde das achte Relais zum Schalten eines Gleichstromsummers
verwendet.
Achtung: Die Abschaltung im Power Manager erfolgt nur einpolig. Je nachdem, wie der
Netzstecker des Power Managers eingesteckt wird, schaltet er Phase oder Nulleiter. Lediglich der
Hauptschalter ist zweipolig ausgeführt. Bei Arbeiten an angeschlossenen Geräten ist
auf alle Fälle deren Netzstecker zu ziehen.
Software
Das Programm relais
Syntax der Parameter von relais
Das Ansteuerprogramm für die serielle Relaiskarte von Conrad wird mit dem Kommando
relais <Parameter> aufgerufen. Als Parameter sind folgende Eingaben möglich:
Parameter | Bedeutung |
---|
-stat | Status der Relais als Dezimalzahl. Bit=1: Relais an,
Bit=0: Relais aus. Es sind keine weiteren Parameter möglich |
-off | alle Relais aus |
-on | alle Relais an |
-sx | Relais x einschalten (1 <= x <= 8) |
-rx | Relais x ausschalten (1 <= x <= 8) |
Beispiele:
relais -off -s1 -s3: Relais 1 und 3 einschalten
relais -s4 -r3: Relais 4 ein- und 3 ausschalten
relais -on -r8: alle Relais ausser 8 einschalten
Rückgabewert:
Normalerweise wird OK: <Kontaktstellung dezimal>
z. B. 'OK: 5' --> Relais 1 und 3 on auf der Standardausgabe zurückgegeben.
Bei Fehler wird FAIL: und der komplette Status
zurückgegeben (Antwortcode Adresse Daten/Info).
Quellcode
Der C-Quellcode des Programms basiert auf dem
Serial-HOWTO
von Linux. Das Programm sollte auf allen Linux-Versionen lauffähig sein.
Über #define PORT 0 wird die erste serielle Schnittstelle (ttyS0)
ausgewählt; hier sind die Werte 0 bis 3 für ttyS0 bis ttyS3
möglich.
Entwurf eines Protokolls
Es wird ein Protokoll für die Anwendungsschicht entworfen und beschrieben,
das zwischen einem Server mit angeschlossener Relais-Steuereinheit und
einem Client benutzt werden soll. Da die Relais nicht von Jedermann
geschaltet werden sollen, müssen sich Client und Server gegenseitig authentisieren.
Das Protokoll soll natürlich abhörsicher sein. Eine Übertragung eines
Passworts im Klartext scheidet daher aus. Aber auch die Übertragung eines
verschlüsselten Passworts führt diesmal nicht weiter (warum?).
Daher wird diesmal ein Challenge-Response-Protokoll verwendet. Das bedeutet,
daß sich Client und Server im Dialog gegenseitig authentisieren. Damit
die Übertragung abhörsicher wird, verschlüsselt man die Daten mit einer
sogenannten Einweg-Funktion.
Eine Einwegfunktion ist eine mathematische Funktion, die (vorwärts) deutlich
leichter zu berechnen ist, als die zugehörige Umkehrfunktion (rückwärts). Ein
Rechner braucht beispielsweise nur einige Sekunden, um die Funktion für
einen Wert zu berechnen, für die Umkehrung braucht er jedoch
möglicherweise Monate oder sogar Jahre.
Je größer die Eingabedaten der Einwegfunktion (und damit der Schlüssel)
gewählt werden, desto größer ist auch der Unterschied in der Rechenzeit für
die Hin- und Rückrichtung. Alle praktisch verwendbaren asymmetrischen Kryptosysteme
basieren auf angenommenen Einwegfunktionen, d.h. Funktionen, von denen man glaubt,
daß es Einwegfunktionen sind, dieses jedoch bisher nicht bewiesen wurde.
Einwegfunktionen
Einwegfunktionen dienen in der Kryptografie zur Generierung von nicht manipulierbaren
Fingerabdrücken aus Nachrichten. Sie erzeugen aus einer Nachricht mit beliebiger
Länge nach einem vorbestimmten Verfahren ein Komprimat (gewissermaßen
eine kryptografische Prüfziffer). Man nennt sie auch Hashfunktionen.
Eine Hashfunktion, welche die Eigenschaft hat, daß es sehr lange dauert eine Nachricht
zu finden für die hash(m) = hash(n) gilt, nennt man kryptografische
Hashfunktion. Anders als bei Prüfsummenverfahren müssen dabei nicht nur zufällig
auftretende Fehler, sondern auch vorsätzliche Manipulationen sicher erkannt werden.
Die wichtigsten Anforderungen an solche Funktionen lauten:
- Effizienz
Ein Hashwert muss "einfach", d.h. leicht und schnell, zu berechnen sein.
- Kollisionsfreiheit
Eine Hashfunktion muss "kollisionsfrei" sein, das heißt es darf nicht
möglich sein, zwei Nachrichten zu konstruieren, die den gleichen Hashwert haben,
da es sonst möglich wäre, eine Originalnachricht unerkannt durch eine Fälschung
auszutauschen (Geburtstagsangriff).
- Repräsentativität
Jedes Bit eines Textes muss den Hashwert beeinflussen, d. h. die Änderung auch nur
eines Buchstaben eines Textes muss zu einem anderen Hashwert führen. Daher müssen
auch ähnliche Texte vollkommen unterschiedliche Hashwerte ergeben.
- Kryptografische Sicherheit
Für einen gegebenen Hashwert muss es praktisch unmöglich sein den zugehörigen
Text herzustellen bzw. zurückzugewinnen.
Hashfunktionen die diese Eigenschaften erfüllen werden im Englischen als
"Message Digest" (MD) bezeichnet. (Digest: Auszug, Zusammenfassung) Ein Message
Digest ist der (digitale) Fingerabdruck einer Nachricht, bei der mit Hilfe
von einfach berechenbaren Funktionen ein Wert ermittelt wird, der kürzer ist als die
Originalnachricht. Die verwendete Funktion muss so beschaffen sein, dass es relativ
schwierig ist eine zweite Nachricht zu erzeugen, die den gleichen Fingerabdruck hat.
Die Chance, aus zwei unterschiedlichen Texten einen identischen Fingerabdruck zu
generieren, sollte eins zu unendlich sein, kann aber nie völlig ausgeschlossen werden.
Ron Rivest entwickelte - zusammen mit anderen Mitarbeitern der RSA Data Security - eine
Reihe von Hashfunktionen MD1(?), MD2, MD3, MD4 bis MD5, die gemeinhin auch als Synonym
für den Message Digest gelten. Die Algorithmen akzeptieren als Eingabe eine Botschaft
beliebiger Länge und erzeugen einen "digitalen Fingerabdruck" von 128 Bit
Länge als Ausgabe. Die Chance, aus zwei unterschiedlichen Texten einen identischen
Fingerabdruck zu generieren, ist beinahe unendlich, kann aber nicht völlig ausgeschlossen
werden.
MD5 ist wohl zur Zeit die am weitesten verbreitete Hashfunktion. Sie ist aus MD4
entstanden und dabei in erster Linie um deren Unsicherheiten auszuräumen.
Wie bei MD4 wird zu Beginn die Länge der Nachricht auf ein Vielfaches von 512 Bit
gebracht, indem eine 1 und entsprechend Nullen sowie die Länge der Ursprungsnachricht
- im 64 Bit Format - angehängt werden. Auch der Puffer und dessen Initialisierung
sind gleich.
Eine genaue Beschreibung finden Sie in RFC 1321:
MD5 Message Digest Algorithm; R. Rivest, April 1992. In Perl steht MD5 im Modul
Digest::MD5 bereit (use Digest::MD5 qw(md5_hex). Erzeugt wird ein Hash mit
$digest = md5_hex($string);.
Es werden nun ein Server und ein Client in Perl vorgestellt. Die Authentisierung
läuft folgendermaßen ab:
- Nach dem Connect meldet sich der Server mit der Zeichenkette "AUTH", gefolgt von Newline.
Danach folgen T und Z1 in jeweils einer neuen Zeile. T ist die aktuelle Zeit (GMT), die in Perl
mit der Funktion gmtime() zu bekommen ist. Z1 ist eine vierstellige Zufallszahl (z. B.
mit substr(rand(),2,4) zu finden). Dies ist die "Challenge".
- Nachdem er die drei Zeilen vom Server erhalten hat, sendet der Client zwei Zeilen. Zuerst
einen MD5-Hash von (T,Z1,P), wobei P das Passwort ist und authentisiert sich damit gegenüber
dem Server. In der zweiten Zeile sendet er seine Zufallszahl Z2.
- Der Server antwortet mit einem MD5-Hash von (T,Z2,P) und authentisiert sich so gegenüber
dem Client.
- Tritt im Verlauf dieser Authentisierung ein Fehler auf (nicht übereinstimmende Hashes,
etc.) wird die Verbindung mit einer entsprechenden Meldung beendet.
- Sind beide Partner glücklich, sendet der Client eine Zeile mit Relais-Steuerkommandos,
die den Parametervereinbarungen des Programms relais entsprechen. Dieses Programm
wird aus dem Perl-Server als Systemaufruf (Backquotes) gestartet.
- Der Server sendet die Ausgaben von relais an den Client zurück, worauf
beide die Verbindung beenden.
Das Client-Programm in Perl
#!/usr/bin/perl
use strict;
use IO::Socket;
use Digest::MD5 qw(md5 md5_hex md5_base64);
my $SERVER = "blackhole.ee.fhm.edu";
my $PORT = 666;
my $TIMEOUT = 50;
my $PASS= "geheim";
my ($eingabe, $ausgabe, $rs, $com);
my $sock = new IO::Socket::INET(PeerAddr => $SERVER,
PeerPort => $PORT,
Proto => 'tcp',
Timeout => $TIMEOUT)
|| die "Can't connect to server: $@\n";
$eingabe = <$sock>; chomp($eingabe);
if ($eingabe ne "AUTH")
{ print "Authentication failed\n"; close($sock); exit(1); }
$eingabe = <$sock>; chomp($eingabe);
# print "Got: $eingabe\n";
$rs = <$sock>; chomp($rs);
# print "Got: $rs\n";
$ausgabe = md5_hex($eingabe, $rs, $PASS);
print $sock "$ausgabe\n";
# print "Sent: $ausgabe\n";
$ausgabe = substr(rand(),2,4);
print $sock "$ausgabe\n";
# print "Sent: $ausgabe\n";
$ausgabe = md5_hex($eingabe, $ausgabe, $PASS);
$eingabe = <$sock>; chomp($eingabe);
# print "Got: $eingabe\n";
if($eingabe ne $ausgabe)
{ print "Authentication failed\n"; close($sock); exit(1); }
print $sock "@ARGV\n";
$com = <$sock>;
print "Returned $com\n";
Das Server-Programm
#!/usr/bin/perl -w
# Server fuer Relaissteuerung
use strict;
use IO::Socket;
use Digest::MD5 qw(md5 md5_hex md5_base64);
my $PORT = 666;
my $PASS= "geheim";
my ($rc, $sock, $client, $tim, $rs, $hs, $p, $com, $res);
$sock = new IO::Socket::INET(LocalPort => $PORT,
Reuse => 1,
Listen => 5)
|| die "Can't create local socket : $@\n";
print "Accepting connections on port ",$PORT, "...\n";
while ($client = $sock->accept())
{
print "Accepted connection from ",
$client->peerhost(), ":", $client->peerport(), "\n";
$tim = gmtime();
$rs = substr(rand(),2,4);
print $client "AUTH\n$tim\n$rs\n";
next if (<$client> ne (md5_hex($tim, $rs, $PASS))."\n");
$rc = <$client>; chomp ($rc);
print $client md5_hex($tim, $rc, $PASS)."\n";
next if (! defined($com = <$client>));
chomp ($com);
print "Commandline: $com\n"; # Fuer den Test
$res = `/usr/local/bin/relais $com`;
print $client "$res\n";
}
continue { $client->close(); }
CGI-Schnittstelle
Die Übertragung zwischen Relais-Server und Client ist nun möglich
(sogar mit gesichertem Protokoll). Das CGI-Programm enthält
natürlich den Client und wertet zusätzlich die Eingaben eines
Formulars aus. Wenn zwischen HTTP-Client (nicht zu verwechseln mit unserem
Relais-Client) und Wevserver das HTTPS-Protokoll verwendet wird, ist die
gesamte Übertragungsstrecke gesichert.
Das Formular hat folgenden Quellcode:
<TABLE BGCOLOR="#FFFFFF" ALIGN=CENTER BORDER=0 CELLPADDING=0 CELLSPACING=0 WIDTH="80%">
<TR><TD VALIGN=TOP ALIGN=LEFT><IMG SRC="relais1.jpg"></TD>
<TD>
<CENTER>
<H2>Relais-Steuerung</H2>
<FORM ACTION="/cgi-bin/relais.cgi" METHOD="POST">
<TABLE BGCOLOR="#EEEEEE" BORDER=0 CELLSPACING=0 CELLPADDING=5>
<TR>
<TD BGCOLOR="#EEFF99" VALIGN=TOP>Relais 1</TD>
<TD BGCOLOR="#EEFF99" VALIGN=TOP>
<INPUT TYPE="radio" NAME="1" VALUE="1">On
<INPUT TYPE="radio" NAME="1" VALUE="0">Off </TD>
</TR><TR>
<TD BGCOLOR="#EEEEEE" VALIGN=TOP>Relais 2</TD>
<TD BGCOLOR="#EEEEEE" VALIGN=TOP>
<INPUT TYPE="radio" NAME="2" VALUE="1">On
<INPUT TYPE="radio" NAME="2" VALUE="0">Off </TD>
</TR><TR>
<TD BGCOLOR="#EEFF99" VALIGN=TOP>Relais 3</TD>
<TD BGCOLOR="#EEFF99" VALIGN=TOP>
<INPUT TYPE="radio" NAME="3" VALUE="1">On
<INPUT TYPE="radio" NAME="3" VALUE="0">Off </TD>
</TR><TR>
<TD BGCOLOR="#EEEEEE" VALIGN=TOP>Relais 4</TD>
<TD BGCOLOR="#EEEEEE" VALIGN=TOP>
<INPUT TYPE="radio" NAME="4" VALUE="1">On
<INPUT TYPE="radio" NAME="4" VALUE="0">Off </TD>
</TR><TR>
<TD BGCOLOR="#EEFF99" VALIGN=TOP>Relais 5</TD>
<TD BGCOLOR="#EEFF99" VALIGN=TOP>
<INPUT TYPE="radio" NAME="5" VALUE="1">On
<INPUT TYPE="radio" NAME="5" VALUE="0">Off </TD>
</TR><TR>
<TD BGCOLOR="#EEEEEE" VALIGN=TOP>Relais 6</TD>
<TD BGCOLOR="#EEEEEE" VALIGN=TOP>
<INPUT TYPE="radio" NAME="6" VALUE="1">On
<INPUT TYPE="radio" NAME="6" VALUE="0">Off </TD>
</TR><TR>
<TD BGCOLOR="#EEFF99" VALIGN=TOP>Relais 7</TD>
<TD BGCOLOR="#EEFF99" VALIGN=TOP>
<INPUT TYPE="radio" NAME="7" VALUE="1">On
<INPUT TYPE="radio" NAME="7" VALUE="0">Off </TD>
</TR><TR>
<TD BGCOLOR="#EEEEEE" VALIGN=TOP>Summer</TD>
<TD BGCOLOR="#EEEEEE" VALIGN=TOP>
<INPUT TYPE="radio" NAME="8" VALUE="1">On
<INPUT TYPE="radio" NAME="8" VALUE="0">Off </TD>
</TR><TR>
<TD BGCOLOR="#EEEE99" VALIGN=TOP>Passwort:
<INPUT TYPE="PASSWORD" NAME="PASS" LENGTH=8></TD>
<TD BGCOLOR="#EEEE99" VALIGN=TOP>
<INPUT TYPE="SUBMIT" VALUE=" Absenden "></TD>
</TR>
</TABLE>
</FORM>
</CENTER>
</TD>
<TD VALIGN=BOTTOM ALIGN=RIGHT><IMG SRC="relais2.jpg"></TD>
</TR></TABLE>
Der Server als Daemon
Bisher wurde zu Testzwecken der Server immer von der Konsole oder vom Terminalfenster
aus gestartet. Das Programm läßt sich mit den Erkenntnissen aus der Vorlesung
aber recht einfach zum Daemon umgestalten. Der Server wird als Kindprozeß in den
Hintergrund verlagert und die Standard-Ausgabe/Standard-Fehlerausgabe in eine Log-Datei
umgeleitet. Auf das Anlegen einer PID-Datei wird im folgenden Beispiel verzichtet.
#!/usr/bin/perl -w
# Server fuer Relaissteuerung als Daemon
use strict;
use POSIX 'setsid';
use IO::Socket;
use Digest::MD5 qw(md5 md5_hex md5_base64);
my $PORT = 666;
my $PASS= "geheim";
my ($child, $rc, $sock, $client, $tim, $rs, $hs, $p, $com, $res);
# Exit-Handler setzen
$SIG{TERM} = $SIG{INT} = sub { exit(0); };
# Daemon werden
$child = fork();
if ($child < 0) { die "Cannot fork!\n"; }
exit(0) if ($child > 0); # Eltenprozess beendet sich
&setsid(); # Abtrennen
open(STDIN, "</dev/null"); # Standarddateien umlenken
open(STDOUT, ">/var/log/relais.log");
open(STDERR, ">&STDOUT");
chdir('/tmp'); # Arbeitsverzeichnis /tmp
umask(0); # UMASK definieren
# Pfad definiert setzen:
$ENV{PATH} = '/bin; /sbin; /usr/bin; /usr/sbin; /usr/local/bin;';
# und den Server starten
$sock = new IO::Socket::INET(LocalPort => $PORT,
Reuse => 1,
Listen => 5)
|| die "Can't create local socket : $@\n";
# ins Logfile schreiben (Startmeldung):
print "Accepting connections on port ",$PORT, "...\n";
while ($client = $sock->accept())
{
# ins Logfile schreiben:
print "Accepted connection from ",
$client->peerhost(), ":", $client->peerport(), "\n";
$tim = gmtime();
$rs = substr(rand(),2,4);
print $client "AUTH\n$tim\n$rs\n";
next if (<$client> ne (md5_hex($tim, $rs, $PASS))."\n");
$rc = <$client>; chomp ($rc);
print $client md5_hex($tim, $rc, $PASS)."\n";
next if (! defined($com = <$client>));
chomp ($com);
print "Commandline: $com\n"; # ins Logfile
$res = `/usr/local/bin/relais $com`;
print $client "$res\n";
}
continue { $client->close(); }
Die Programme und Dateien können über folgenden Link
heruntergeladen werden:
relais.zip
- Steuerprogramm 'relais.c'
- Client-Programm
- Server-Programm
- Relais-Daemon
- CGI-Steuerprogramm
- HTML-Formular
Um die Sache schliesslich vollständig zu automatisieren, kann
man in /etc/init.d noch ein Start/Stopp-Skript anlegen.
Die folgende Version arbeitet unter der Debian-Distribution:
#!/bin/sh
# Start/stop the relais daemon.
test -f /root/bin/relais-daemon.pl || exit 0
case "$1" in
start) echo -n "Starting Relais-Daemon"
start-stop-daemon --start --quiet --exec /root/bin/relais-daemon.pl
echo "."
;;
stop) echo -n "Stopping Relais-Daemon"
kill -HUP `ps ax | grep relais-daemon.pl | grep -v grep | awk '{print $1}'`
echo "."
;;
restart) echo -n "Restarting Relais-Daemon"
$0 stop
sleep 2
$0 start
echo "."
;;
*) echo "Usage: /etc/init.d/relais start|stop|restart"
exit 1
;;
esac
exit 0
In den entsprechenden Runlevel-Verzeichnissen müssen dann noch die symbolischen
Links (S99relais bzw. K01relais) angelegt werden.
|
|
|