9/2 Grundlagen
Transcrição
9/2 Grundlagen
Grundlagen Teil 9/2.1 Seite 1 Einführung 9/2 Grundlagen 9/2.1 Einführung Um genau zu verstehen, warum manche Sachen gerade so funktionieren und nicht anders, ist es hilfreich, zumindest grundlegende Kenntnisse über die internen Abläufe der Kommunikation via Internet zu haben. Abläufe In den folgenden Kapiteln wollen wir Ihnen zeigen, wie diese Kommunikation über UNIX-Sockets zu Stande kommt und welche Daten in welchen Formaten und welchen Protokollen wie und wann über das Internet verschickt werden. So erkennt man plötzlich, dass Passworte im Klartext über relativ einfach „anzapfbare“ Internet-Verbindungen übertragen werden, dass es bei Kenntnis der Kommunikationsstati möglich ist, bestehende Verbindungen „feindlich“ zu übernehmen, was passiert, wenn plötzlich einzelne Server ausfallen und vieles andere mehr. Neben den Grundlagen finden Sie natürlich auch viele praktische Tipps und Hinweise, wie Sie z.B. selbst Internet-Clients und -Server programmieren können oder wie Sie sich einfach über Ihre Tastatur direkt mit einem {HTTP, SMTP, POP3, ...}-Server irgendwo im Internet auf „Protokoll-Niveau“ unterhalten können. Praxistips Teil 9/2.1 Seite 2 Einführung Grundlagen Grundlagen Teil 9/2.2 Seite 1 Socket-Programmierung 9/2.2 Socket-Programmierung Die Kommunikation im Internet läuft über Ports, die von Dämonen bzw. Servern auf einer bestimmten Maschine bedient werden. Ports Der Ablauf der Kommunikation zwischen einem Client und einem Server geschieht immer nach dem gleichen Prinzip: • Zuerst wird das Protokoll festgelegt, über das kommuniziert werden soll (z. B. TCP). • Dann wird der Port bestimmt, über den kommuniziert werden soll (für HTTP z. B. 80). • Als Letztes wird festgelegt, mit welchem Rechner kommuniziert werden soll. Jetzt liegen die Informationen Protokoll-ID, Portnummer und IP-Adresse des Gegenübers sowie die eigene IP-Adresse vor. Socket • Aus diesen Informationen werden jetzt die Adressen der beiden Socket-Endpunkte bestimmt. • Dann wird ein Socket erzeugt, der wie eine Art Pipe (Rohr) angesehen werden kann, auf deren beide Enden allerdings nur die beiden Kommunikationspartner lesend und schreibend zugreifen können. • Mit bind wird das eine Ende des Sockets dem erzeugenden Prozess als Stream zur Verfügung gestellt. • Mit connect wird das andere Ende des Sockets mit dem Server, der den angegebenen Port auf dem anderen Rechner bedient, verbunden. Nun kann vom Client-Prozess aus in den Socket geschrieben und aus ihm gelesen werden, als ob es sich um eine reguläre Datei handeln würde. Die geschriebenen Daten werden jedoch über das definierte Protokoll an den für den angegebenen Port zuständigen Server auf dem anderen Rechner übermittelt. Ebenso werden Daten, die von diesem Server kommen, im Socket zum Lesen bereitgestellt. Kommunikation Teil 9/2.2 Seite 2 Grundlagen Socket-Programmierung #!/usr/bin/perl ($them,$port) = @ARGV; $port = 2345 unless $port; $them = ’localhost’ unless $them; $SIG{’INT’} = ’dokill’; sub dokill { kill 9,$child if $child; } use Socket; $sockaddr = ’S n a4 x8’; chop($hostname = ‘hostname‘); ($name, $aliases, $proto) = getprotobyname(’tcp’); ($name, $aliases, $port) = getservbyname($port, ’tcp’) unless $port =~ /^\d+$/; ($name, $aliases, $type, $len, $thisaddr) = gethostbyname($hostname); ($name, $aliases, $type, $len, $thataddr) = gethostbyname($them); $this = pack($sockaddr, AF_INET, 0, $thisaddr); $that = pack($sockaddr, AF_INET, $port, $thataddr); $me = join(".",unpack("C4",$thisaddr)); $you = join(".",unpack("C4",$thataddr)); print print print print "Create Socket proto=$proto, "; "port=$port,\n"; "this=($me/$hostname), "; "that=($you/$them)\n"; socket(S, PF_INET, SOCK_STREAM, $proto) || die "socket: $!"; bind(S, $this) || die "bind: $!"; connect(S, $that) || die "connect: $!"; select(S); $| = 1; select(stdout); $| = 1; if ($child = fork) { while (<STDIN>) { print S; } sleep 3; do dokill(); } else { while (<S>) { print; } } Grundlagen Teil 9/2.2 Seite 3 Socket-Programmierung Beispiel-Scripts zur Socket-Programmierung Wie das konkret funktioniert sei an den beiden einfachsten Perl-Scripts aus dem Perl-Handbuch demonstriert, die auf der gegenüberliegenden und auf der folgenden Seite abgedruckt sind. Das erste Script zeigt einen einfachen Client, das zweite einen einfachen Server, der auf einem bestimmten Port auf eingehende Verbindungen wartet. Einfacher TCP-Client Im ersten Block werden die optional übergebenen Parameter (das Script kann mit den Parametern Host und Portnummer aufgerufen werden) in den Variablen $them und $port abgelegt. Falls nichts eingegeben wurde, wird als Host localhost und als Port 2345 verwendet. Beschreibung des Scripts Im zweiten Abschnitt wird die Signalbehandlungsroutine im Fall des Abbrechens (Ctrl-C, entspricht SIG_INT) umdefiniert, indem der später gestartete Child-Prozess, falls er existiert, erst gekillt und dann erst das Programm beendet wird. • getprotobyname liefert die Nummer des im Klartext angegebenen Protokolls (tcp) zurück, die unter UNIX z. B. in /etc/protocols definiert sind (s. Kapitel 1/6.2 Protokolle). • getservbyname liefert die Port-Nummer des im Klartext angegebenen Services. Diese sind unter UNIX z. B. in der Datei /etc/services definiert (s. Kapitel 1/6.3 Services) • gethostbyname liefert schließlich die IP-Adresse des im Klartext angegebenen Hosts. $sockaddr = 'S n a4 x8' legt das Format für den SocketAddress-String fest, der dann mit pack gefüllt wird. Dieser String stellt eine generische Socket-Adresse dar, die aus folgenden Elementen besteht: • S: Unsigned Short (16 Bit) mit der Konstanten AF_INET, die die IP-Prokollfamilie identifiziert (=2) • N: Short in Network-Order (Big-Endian), der die Portnummer bzw. 0 (je nach Ende des Sockets) enthält. Teil 9/2.2 Seite 4 Grundlagen Socket-Programmierung • a4: ASCII-String mit 4 Zeichen (mit Nullen aufgefüllt). Dieser String enthält die 4 Byte der IP-Adresse des jeweiligen Kommunikationspartners. 192.168.1.1 wird z.B. als String mit den ASCII-Zeichen 192, 168, 1 und 1 codiert. • x8: 8 Nullbytes (reserviert u. a. für IP6 (AF_INET6=10), das dann 8-Byte-IP-Adressen unterstützen wird). #!/usr/bin/perl ($port) = @ARGV; $port = 2345 unless $port; use Socket; $sockaddr = ’S n a4 x8’; ($name, $aliases, $proto) = getprotobyname(’tcp’); ($name, $aliases, $port) = getservbyname($port, ’tcp’) unless $port =~ /^\d+$/; $this = pack($sockaddr, AF_INET, $port, "\0\0\0\0"); select(NS); $| = 1; select(stdout); socket(S, PF_INET, SOCK_STREAM, $proto) || die "socket: $!"; bind(S, $this) || die "bind: $!"; listen(S, 5) || die "connect: $!"; select(S); $| = 1; select(stdout); $| = 1; for (;;) { print "Listening again\n"; ($addr = accept(NS,S)) || die $!; print "accept ok\n"; ($af,$port,$inetaddr) = unpack($sockaddr,$addr); @inetaddr = unpack(’C4’,$inetaddr); print "$af $port @inetaddr\n"; while (<NS>) { print; print NS; } } Grundlagen Teil 9/2.2 Seite 5 Socket-Programmierung Nachdem wir verbunden sind und der Socket auf unbuffered ($|=1) umgestellt wurde, teilen wir unseren Prozess in zwei Prozesse, von denen der eine (der von fork die Prozess-ID des neuen zurückerhält) alles, was auf der Standardeingabe eingegeben wird an den Socket schickt. Der andere Prozess (der Child-Prozess, der von fork eine 0 zurückgeliefert bekommt) gibt einfach alles, was über den Socket ankommt, auf dem Terminal aus. Einfacher TCP-Server Das Script für die Implementation des Servers ist analog zu dem des Client aufgebaut. Zuerst wird das optionale Argument Port ausgewertet. Daraufhin wird wieder die ProtokollID und die Portnummer bestimmt. Beschreibung des Scripts Nun erscheint bereits der erste Unterschied: Wir binden zwar wieder das lokale Ende des Sockets, aber diesmal mit der IPAdresse 0.0.0.0 (IN_ADDR_ANY, d. h. wir akzeptieren Verbindungen von beliebigen IP-Adressen) und der richtigen Port-Nummer. Das andere Ende des Sockets wird jedoch nicht connectet, sondern wir setzen einen listen ab (die 5 bedeutet, dass maximal 5 Verbindungen warten dürfen. Werden es mehr, erhalten diese Clients einen „connection refused error“). Um nun die Verbindung zu etablieren, warten wir mit accept auf einen eingehenden connect und bearbeiten diesen dann, bis das Dateiende an diesem Eingang (NS) erreicht ist. Verwendung der Beispielscripts Auch ohne das SimpleClient-Script von oben kann mit dem Systemprogramm telnet bereits eine Verbindung zu einem beliebigen Port aufgebaut werden. Dazu muss telnet neben dem Host nur noch eine Portnummer mit übergeben werden (Eingaben in fett): Port-Verbindung mit Telnet Teil 9/2.2 Seite 6 Grundlagen Socket-Programmierung $ telnet localhost 80 Trying 127.0.0.1... Connected to localhost. Escape character is ’^]’. HEAD / HTTP/1.0 HTTP/1.1 200 OK Date: Thu, 18 Jun 1998 06:42:01 GMT Server: Apache/1.2.5 Last-Modified: Mon, 09 Mar 1998 15:26:45 GMT ETag: "4002-174f-35040a35" Content-Length: 5967 Accept-Ranges: bytes Connection: close Content-Type: text/html Connection closed by foreign host. Auf Port 80 des lokalen Rechners wurde eine Telnet-Session gestartet. Hier erwartete uns ein Apache HTTP-Server, der auf unsere Anfrage nach der Information zur Rootpage (Methode HEAD, Datei /, Protokoll HTTP/1.0, s. Kapitel 9/4.6 HTTP) mit dem entsprechenden Reply antwortete und danach die Verbindung geschlossen hat (die Meldung „Connection closed by foreign host“ stammt von telnet). SimpleClient Analog funktioniert das SimpleClient-Script, das nach einem SimpleClient localhost 80 folgende Ausgaben macht: Create Socket proto=6, port=80, this=(127.0.0.1/localhost), that=(127.0.0.1/localhost) HEAD / HTTP/1.0 HTTP/1.1 200 OK Date: Thu, 18 Jun 1998 06:42:01 GMT Server: Apache/1.2.5 Last-Modified: Mon, 09 Mar 1998 15:26:45 GMT ETag: "4002-174f-35040a35" Content-Length: 5967 Accept-Ranges: bytes Connection: close Content-Type: text/html Grundlagen Teil 9/2.2 Seite 7 Socket-Programmierung Setzen wir statt eines HEAD-Kommandos ein GET ab, erhalten wir die angegebene Web-Seite geliefert. Create Socket proto=6, port=80, this=(127.0.0.1/localhost), that=(127.0.0.1/localhost) GET / HTTP/1.0 HTTP/1.1 200 OK Date: Thu, 18 Jun 1998 06:42:01 GMT Server: Apache/1.2.5 Last-Modified: Mon, 09 Mar 1998 15:26:45 GMT ETag: "4002-174f-35040a35" Content-Length: 5967 Accept-Ranges: bytes Connection: close Content-Type: text/html <HTML> <HEAD><TITLE>Titelseite</TITLE></HEAD> <BODY> ... </BODY> </HTML> Hierbei erkennt man, dass der HTTP-Server bei dem zurückgelieferten Informationskopf jede Zeile mit einem CRLF (ASCII 13 + ASCII 10) abschließt, die Datei jedoch unverändert übermittelt. Um nun das SimpleServer-Script zu testen, starten wir den Server mit SimpleServer und connecten mit einem WebBrowser (hier einfach Lynx) unter Angabe des Ports (URL: http://localhost:2345/) auf diesen Server. SimpleServer Teil 9/2.2 Seite 8 Grundlagen Socket-Programmierung Listening again accept ok 2 1063 127 0 0 1 GET /?0,0 HTTP/1.0 Host: localhost:2345 Accept: text/html, text/plain, text/sgml, */*;q=0.01 Accept-Encoding: gzip, compress Accept-Language: en Negotiate: trans User-Agent: Lynx/2.8rel.2 libwww-FM/2.14 Referer: http://localhost/test.html Listening again accept ok 2 1064 127 0 0 1 GET /subdir/file.html HTTP/1.0 Host: localhost:2345 Accept: text/html, text/plain, text/sgml, */*;q=0.01 Accept-Encoding: gzip, compress Accept-Language: en Negotiate: trans User-Agent: Lynx/2.8rel.2 libwww-FM/2.14 Listening again Diese Log-Datei zeigt zwei Verbindungen: Als Erste die Anfrage auf eine serverseitige Imagemap der Art <A HREF="http://localhost:2345"><IMG SRC="myimage.gif" ISMAP></A> die als Argumente (nach dem ?) die Koordinaten 0,0 zurückgeliefert hat. Die zweite Verbindung war die einfache Abfrage einer Datei /subdir/file.html. Die Texte „Listening again“ und, „accept ok“ und die Angabe der Socketadresse (2 1064 127 0 0 1) stammen dabei vom SimpleServer-Script, alles andere sind Texte, die Lynx an den Server geschickt hat. Grundlagen Teil 9/2.3 Seite 1 Windows-Sockets-API 9/2.3 Windows-Sockets-API Autor: Andreas Ecker Dieser Artikel gibt Ihnen einen Überblick über die Konstanten, Datenstrukturen und Funktionen, die das WindowsSockets-API (kurz WinSock-API) zur Verfügung stellt. WinSock-API Die meisten auf dem Markt verfügbaren Internet-Applikationen stellen ausschließlich die WinSock-API der Version 1.1, oft in Form einer DLL in der Datei WINSOCK.DLL, zur Verfügung. Von Microsoft wird eine Implementierung des WinSock-API der Version 1.1 angeboten, die für alle Windows-Betriebssysteme ab Windows for Workgroups 3.11 kostenlos verfügbar ist. In Windows 95 und NT ist sie sogar fest ins Betriebssystem integriert, allerdings handelt es sich dabei um eine 16-Bit-DLL, die nur von 16-Bit-Applikationen eingebunden werden kann. Verfügbarkeit Unter Windows 3.1 muss man auf eine der vielen SharewareVarianten ausweichen. Die stabilste Shareware-Implementierung stammt nach unseren Erfahrungen von Peter Tattam und heißt Trumpet WinSocket. Diese Implementierung hat eine Startapplikation mit integriertem RAS (zum Aufbau einer Modem-Verbindung zum ISP) und unterstützt die Verbindungsprotokolle SLIP und PPP. Eine Implementierung der Version 2.0 des WinSock-API ist in Windows 95 und NT integriert und wird im Systemverzeichnis in Form der Datei WSOCK32.DLL bereitgestellt. Das WinSock-API ist ein offener Standard. Die schon recht umfangreiche Dokumentation von Version 1.1 der WinSockAPI ist in der Version 2.0 nochmals erheblich angewachsen. Das lässt sich schon an der Dateigröße der Dokumentation (einer ASCII-Datei) ablesen: während für die Version 1.1 noch 330 kB ausreichten, benötigt die Version 2.0 schon mehr als das Doppelte. Hinzu kommen noch einmal knapp 400 kB für das seit 2.0 verfügbare Dokument zum Service Provider Interface. Versionsumfang Teil 9/2.3 Seite 2 Grundlagen Windows-Sockets-API Lassen Sie sich nicht vom Umfang der API-Dokumente abschrecken. Ein Großteil der Texte beschreibt Funktionen, die ausschließlich für Sonderfälle vorgesehen sind, oder ist – vor allem bei gleichartigen Funktionen – extrem redundant. Applikationen Mit wenigen Kernfunktionen sind bereits professionelle, asynchrone Netz-Applikationen wie ein Web-Browser oder ein E-Mail-Client entwickelbar. Auch die Erweiterungen der Version 2.0 sind dazu nicht notwendig. Entsprechend dem Umfang des API geht dieser Artikel nur auf die wichtigsten Funktionen des WinSock-API 1.1 näher ein. Internet-BasisProtokolle Das WinSock-API stellt die Transport-Protokolle der IPS zur Verfügung, die vor allem für die Internet-Programmierung benötigt werden. Das WinSock-API kapselt die LowLevelbzw. Basisprotokolle der IPS bis einschließlich zur Transportschicht. Dazu gehören vor allem die Protokolle TCP und UDP sowie das Internet-Protokoll (IP). DNS Die im Protokoll-Stack höher liegenden Internet-Protokolle müssen selbst programmiert oder mit Hilfe einer Komponente abgedeckt werden. Eine Ausnahme bildet das Domain-Protokoll des DNS (Domain Name Service). WinSock-ProgrammiererInnen können diesen Dienst ohne Kenntnisse des Protokolls nutzen. Die dafür zuständigen Funktionen werden in den WinSock-Dokumentationen als Datenbankfunktionen bezeichnet. ICMP Ausgefallene WinSock-Implementierungen bieten auch den Zugriff auf tiefer liegende Protokolle wie ICMP (Internet Control Message Protocol) oder unterstützen (LowLevel-) Techniken zur direkten Erstellung und Manipulation der zu verschickenden Datenpakete. Raw-Sockets Der Zugriff auf die tiefer liegenden Schichten erfolgt mit Hilfe von Raw-Sockets, die jedoch nur in speziellen WinSockVersionen implementiert sind und nicht Thema dieses Artikels sind. Grundlagen Teil 9/2.3 Seite 3 Windows-Sockets-API WinSock-API 1.1 – Konstanten Für C-ProgrammiererInnen werden in der Headerdatei WINSOCK.H Deklarationen der Parameter- oder Rückgabewerte des WinSock-API der Version 1.1 bereitgestellt. Diese mit 30 kB nicht gerade kleine Header-Datei ist frei verfügbar und standardisiert. Für Visual Basic gibt es bisher keine standardisierte Deklarationsdatei. Winsock.h Das Socket-API stellt mit seiner Vielzahl von Konstanten und Datenstrukturen und den ca. 30 Funktionen den größten Teil dieser Deklarationsdatei. Leider haben nicht alle vom API verwendeten Werte (vor allem Nullwerte) eine ihnen zugeordnete Konstante. Zudem hält sich das Socket-API kaum an Namenskonventionen, was die Übersicht über dieses API zusätzlich erschwert. Beim Entwurf des WinSock-API mussten die Namen der Socket-API-Deklarationen unter Berücksichtigung des Berkeley Software License Agreement größtenteils unverändert in das WinSock-API übernommen werden. Die WinSock-Erweiterungen sind dagegen eindeutig an den drei führenden Buchstaben WSA zu erkennen. Sie ergänzen die Deklarationsdatei um zusätzliche Konstanten. Zusätzliche Konstanten Das Socket-API definiert mehr als fünfzig verschiedene Fehlernummern. Auch die Namen dieser Konstanten wurden aus den o. a. Gründen in das WinSock-API übernommen. Zusätzlich werden für die Freunde von Namenskonventionen die Fehlernummern ein zweites Mal mit dem Namensvorsatz WSAE deklariert. Ergänzt werden die WinSock-Fehlernummern um Fehlerkonstanten für Windows-spezifische Fehler. Fehlernummern Alle WinSock-Fehlernummern liegen im numerischen Bereich zwischen 10 000 und 20 000, um eine Überschneidung mit den Fehlernummern des Betriebssystems auszuschließen. Die Fehlerkonstante wird in WINSOCK.H mit Hilfe einer Basisnummer (WSAEBASEERR) deklariert. Basisnummer Teil 9/2.3 Seite 4 Grundlagen Windows-Sockets-API Dies würde in Visual Basic formuliert wie folgt aussehen: Const WSABASEERR = 10000 ... Const WSAEWOULDBLOCK = WSABASEERR + 35 Die Basisnummer in der Konstanten WSAEBASEERR wird in allen uns bekannten WinSock-Implementierungen mit der Zahl 10 000 vorbelegt. Die WinSock-Fehlerkonstanten (WSAE…) werden durch Addition der Basisnummer mit einem Offset-Wert gebildet. Bei Verschiebungen der Fehlernummern in zukünftigen WinSock-Versionen ermöglicht die Basisnummer eine schnelle Anpassung der FehlerkonstantenDeklarationen. Konstanten des WinSock-API Die gebräuchlichsten Konstanten des WinSock-API sind nachfolgend zusammengefasst: Name Bedeutung Einsatzgebiet FD_ACCEPT Verbindungsangebot Parameter von WSAAsyncSelect() und Rückmeldefunktion FD_CLOSE Verbindungsende Parameter von WSAAsyncSelect() und Rückmelde-Ereignis FD_CONNECT Verbindungsanfrage Parameter von WSAAsyncSelect() und Rückmelde-Ereignis FD_READ Empfangsanfrage Parameter von WSAAsyncSelect() und Rückmelde-Ereignis MAXGETHOSTSTRUCT Byteanzahl Maximale Größe der Socket-HostDatenstrukturen MSG_PEEK Pufferabfrage Parameter von recv(), Empfangspuffer kopieren, nicht auslesen SOCKET_ERROR Fehleranzeige Returnwert der meisten API-Funktionen WSAEWOULDBLOCK Warnung Netzressource nicht verfügbar, API-Fehler Tabelle 9/2.3-1: Häufig verwendete Konstanten des WinSock-API Grundlagen Teil 9/2.3 Seite 5 Windows-Sockets-API Benutzerdefinierte Datentypen Von den mehr als zehn benutzerdefinierten Datentypen (Strukturen) des WinSock-API reichen für die meisten Aufgabenstellungen die vier Strukturen HostEnt, ServEnt, Sock Addr und WSAData aus. Die HostEnt-Struktur wird bei der Nutzung der Datenbankfunktionen des WinSock-API benötigt. HostEnt enthält den Domain- und den Aliasnamen sowie die IP-Adressen eines Hosts. Nachfolgend finden Sie eine Nachbildung dieser Struktur in Visual-Basic-Syntax: HostEnt Type HostEnt h_name As Long ’ Zeiger auf offiziellen Namen des Host h_aliases As Long ’ Zeiger auf Zeigerarray, dessen Elemente ’ .. auf die Aliasnamen des Host zeigen h_addrtype As Integer ’ AddressType des Host (==AF_INET) h_length As Integer ’ AddressLen des Host (==AF_INET_LENGTH) h_addr_list As Long ’ Zeiger auf Zeigerarray, dessen Elemente ’ .. auf alle IP-Adressen des Host zeigen End Type Die Server-Entry-Struktur speichert die wichtigsten Verbindungsdaten eines Netzwerk-Diensts (Service) und wird ebenso zum Aufruf bestimmter Datenbankfunktionen benötigt. Die Nachbildung der ServEnt-Struktur mit Visual Basic sehen Sie nachfolgend. Die wichtigsten Felder dieser Struktur sind der Name s_name und der Port s_port eines Dienstes. Type ServEnt s_name As Long ’ Zeiger auf offiziellen Namen des Service s_aliases As Long ServEnt Teil 9/2.3 Seite 6 Grundlagen Windows-Sockets-API ’ Zeiger auf Zeigerarray, dessen Elemente ’ .. auf die Aliasnamen des Service zeigen s_port As Integer ’ Portnummer des Services s_proto As Long ’ Zeiger auf Protokollstring End Type SockAddr Die Struktur SockAddr wird von den Funktionen des WinSock-API benutzt, um eine Socket-Adresse anzugeben oder zu erfragen. Beispielsweise sind die elementaren WinSockFunktionen recvfrom(), sendto(), connect() und bind() mit einem SockAddr-Parameter ausgestattet. Type sockaddr_in sin_family As Integer ’ AddressFamily (AF_*-Konstanten, z. B. AF_INET) sin_port As Integer ’ Portnummer im Netzformat (big endian byte order) sin_addr As Long ’ IP-Address (IPv4: 4 mal 8 Bit) sin_zero(0 To 7) As Byte ’ ungenutzt bzw. reserviert End Type WSAData Die Struktur WSAData wird von einer API-Funktion (WSAStartup()) zur Rückgabe von Implementierungsdetails des verwendeten WinSock-API eingesetzt. Byte-Anordnung Die Felder der vorgestellten Strukturen enthalten Daten, die in einem besonderen Zahlenformat abgelegt sind. Bei der Übernahme und Übergabe von Zahlenwerten aus dem bzw. ins Internet ist zu beachten, dass das im Internet verwendete Zahlenformat (Big Endian) sich vom PC-Format (Little Endian) unterscheidet. Im Folgenden werden die Funktionen des WinSock-API vorgestellt, die die Umwandlung für Sie erledigen. Grundlagen Teil 9/2.3 Seite 7 Windows-Sockets-API API-Funktionen Die Funktionen des WinSock-API sind in Tabelle 9/2.3-2 ihrem Namen nach sortiert aufgelistet. Die mit einem „*“ markierten API-Funktionen decken alle Bedürfnisse eines soliden Internet-Client vollkommen ab. Zur Entwicklung eines Servers kommen noch die Funktionen accept(), bind() und listen() hinzu. Die restlichen API-Funktionen werden nur für spezielle Anforderungen benötigt. Grundfunktionen Fast alle DLL-Funktionen des WinSock-API sind in allen Versionen von Visual Basic direkt deklarierbar und aufrufbar. Nur manche der eher selten benötigten API-Funktionen (z. B. die …BlockingHook…()-Funktionen) können ausschließlich mit C-ähnlichen Sprachen (wie Visual Basic ab der Version 5.0, Delphi, CA-VO) genutzt werden. Visual Basic Funktionen des WinSock-API Funktion G. Aufgabe accept() K eintreffende Verbindungsanfrage beantworten, Verbindung aufbauen bind() K Socket initialisieren, Socket-Adresse festlegen closesocket()* K Socket schließen connect()* K Verbindungsanfrage abschicken, Verbindung aufbauen gethostbyaddr()# D Host- bzw. Servernamen und -adressen erfragen, Eingabe: IP-Adresse gethostbyname()# D Host- bzw. Servernamen und -adressen erfragen, Eingabe: Hostname gethostname() D lokale Host- bzw. Servernamen erfragen getpeername() K Peer-Name des Socket erfragen getprotobyname()# D Protokollname und -nummer erfragen, Eingabe: Protokollname Teil 9/2.3 Seite 8 Grundlagen Windows-Sockets-API Funktion G. Aufgabe getprotobynumber()# D Protokollname und -nummer erfragen, Eingabe: Protokollnummer getservbyname()# D Dienstname und zugehörige Portnummer erfragen, Eingabe: Dienstname getservbyport()# D Dienstname und zugehörige Portnummer erfragen, Eingabe: Portnummer getsockname() K Socket-Adresse erfragen getsockopt() S Socket-Option erfragen htonl()* W konvertiert 32-Bit-Wert vom lokalen Format ins Netzformat htons()* W konvertiert 16-Bit-Wert vom lokalen Format ins Netzformat inet_addr()* W wandelt IP-Adresse vom Zeichenfolgen- ins numerische (32-Bit-)Format inet_ntoa()* W konvertiert IP-Adresse vom numerischen ins Zeichenfolgenformat ioctlsocket()* S Socket-Steuerung bzw. -Konfiguration listen() K auf eingehende Verbindungsanfrage warten ntohl()* W konvertiert 32-Bit-Wert vom Netz- ins lokale Format ntohs()* W konvertiert 16-Bit-Wert vom Netz- ins lokale Format recv()* K Daten (in verbindungsorientierter Kommunikation) empfangen recvfrom()* K Datenpaket empfangen select() K Statusabfrage eines oder mehrerer Sockets send()* K Daten (in verbindungsorientierter Kommunikation) verschicken sendto()* K Datenpaket verschicken setsockopt() S Socket-Option einstellen Grundlagen Teil 9/2.3 Seite 9 Windows-Sockets-API Funktion G. Aufgabe shutdown()* K verbindungsorientierte Kommunikation abbrechen bzw. beenden socket()* K Socket erstellen, Empfangs- und Sendepuffer einrichten WSAAsyncSelect()* K Ereignis bei Änderung des VerbindungsStatus beantragen WSACancelAsyncRequest()* S Antrag einer asynchronen Datenbankfunktion zurückziehen WSACancelBlockingCall() S blockierende Socket-Funktion abbrechen WSACleanup()* S Windows-Sockets-DLL abmelden WSAGetLastError()* S Fehlernummer des letzten Socket-Fehlers erfragen WSAGet<X>By<Y>()* D asynchrone Datenbank-Funktionen (wie Funktionen mit #) WSAIsBlocking() K Socket-Blockierung bzw. -Bereitschaft erfragen WSASetBlockingHook() S applikationseigene Überwachungs-Funktion (Hook) installieren WSASetLastError() S Socket-Fehlernummer einstellen WSAStartup()* S Windows-Sockets-DLL anmelden, mit Applikation bekannt machen WSAUnhookBlockingHook() S applikationseigene Überwachungs-Funktion (Hook) deinstallieren Tabelle 9/2.3-2: Funktionen des WinSock-API der Version 1.1 Die durchgängig aus Kleinbuchstabenen zusammengesetzten Funktionensnamen sind von den Funktionen der BerkeleySockets (Socket-API) abgeleitet. Berkeley-SocketFunktionen Zusätzlich wurden 16 asynchrone Erweiterungsfunktionen des Socket-API speziell für die Windows-Umgebung entwickelt, deren Funktionsnamen mit der Vorsilbe WSA beginnen. WindowsErweiterungen Teil 9/2.3 Seite 10 Grundlagen Windows-Sockets-API Funktionsgruppen Die Funktionen des WinSock-API lassen sich in 4 Gruppen einteilen. In Tabelle 9/2.3-2 ist in Spalte G. die Zugehörigkeit zu einer Funktionsgruppe durch Buchstaben angegeben. • • • • D K S W = = = = Datenbank Kommunikation Steuerung und Konfiguration Wandlung, Konvertierung Von den Konvertierungsfunktionen (W-Gruppe) werden die Funktionen der Form ntoh…() und hton…() am häufigsten eingesetzt. Spätestens beim Debugging oder beim Ausdruck der Werte einer Socket-Adresse (IP-Adresse oder Portnummer) werden Sie merken, dass ohne Zuhilfenahme der Konvertierungsfunktionen scheinbar unsinnige Zahlenwerte angezeigt werden. Beachten Sie, dass manuell ermittelte Portnummern und IPAdressen vor der Übergabe an das API (als Parameter oder Strukturfeld) mit hton…() konvertiert werden müssen, um die numerischen Werte vom lokalen Format (Little Endian) ins Internet-Format (Big Endian) zu transferieren. Umgekehrt ist eine Wandlung mit einer ntoh…()-Funktion nötig, wenn eine 16- oder 32-Bit-Zahl aus dem API bzw. dem Internet übernommen und angezeigt bzw. manuell korrigiert werden soll. Grundlagen Teil 9/2.4 Seite 1 Proxies 9/2.4 Proxies Ein Proxy ist ein Computer, der die Verbindungen, die über ihn laufen, so weiterleitet, dass es scheint, als ob die Anfrage direkt von ihm käme (unter der Website von JunkBuster.com (http://internet.junkbuster.com/) findet sich dazu eine recht interessante Software . Außerdem speichert ein Proxy abgefragte Web-Seiten zwischen und liefert sie bei einer Anfrage aus, um unnötigen Netz-Traffic zu vermeiden. WWW-Proxy Generell wirkt jeder Firewall zusätzlich als Proxy-Server. Das heißt, jede Anfrage vom sicheren internen Netz wird an den Firewall (Proxy) gerichtet, der dann seinerseits unter Angabe seiner Adresse erst die Verbindung zum endgültigen Zielrechner aufnimmt. Auf diese Weise erhält der Zielrechner als Reply-Adresse die des Proxies, und nur der Proxy kennt den ursprünglichen Absender der Anfrage und schickt ihm die vom Zielrechner übermittelte Information zurück. Firewalls Normalerweise funktioniert eine HTTP-Verbindung wie folgt (Beispiel zum Laden der Seite http://www.yahoo .com/): HTTP via Proxy • Es wird eine Socketverbindung zu dem in der URL angegebenen Host geöffnet (z. B. www.yahoo.com). • An diesen wird die Abfrage nach einer bestimmten Seite geschickt (z. B. „GET / HTTP/1.0“ plus eventuell weitere Header- und Body-Zeilen). • Der Host antwortet darauf mit einem Status-Code, dem Header und der angeforderten Datei. Um mit HTTP nun eine Verbindung über einen Proxy aufzubauen, ist eine etwas andere Vorgehensweise nötig: • Eine Socket-Verbindung zum entsprechenden Port des Proxy wird aufgebaut (z. B. firewall.mynet.de auf Port 8000). Teil 9/2.4 Seite 2 Grundlagen Proxies • An diesen wird jetzt eine modifizierte Abfrage geschickt: „GET http://www.yahoo.com/ HTTP/1.0“ plus eventuell weitere Header- und Body-Zeilen – einziger Unterschied ist also, dass in der Anfrage nicht nur der Pfad, sondern auch Protokoll und Server angegeben werden müssen. • Der Proxy zerlegt diese Angabe, baut seinerseits die Verbindung zum hier angegebenen Host auf und schickt diesem die Anfrage ohne Servernamen weiter („GET / HTTP/1.0“) • Der Host antwortet darauf mit einem Status-Code, dem Header und der angeforderten Datei. • Diese Datei wird dann vom Server über den noch offenen Socket – nach eventuellen Viren- und Content-Checks – an den anfragenden Client weitergereicht. Grundlagen Teil 9/2.5 Seite 1 Authentifizierung 9/2.5 Authentifizierung Viele HTTP-Server erlauben die Sicherung bestimmter Verzeichnisse durch eine User/Passwort-Kombination. Unter dem Apache-Web-Server kann dies z. B. mit einer .htaccessDatei im zu sichernden Verzeichnis geschehen. HTTP-Server AuthUserFile /home/httpd/x-users AuthGroupFile /dev/null AuthName Sicherheitsbereich AuthType Basic <Limit GET POST> require valid-user </Limit> Das Listing zeigt eine .htaccess-Datei, die das Verzeichnis, in dem sie liegt, durch eine Passwortabfrage vor unberechtigten Zugriffen schützt. Die Usernamen und Passworte der Benutzer sind in der Form „user:verschlüsseltes_passwort“ in der oben angegebenen Datei „/home/httpd/x-users“ abgelegt (hier eine Beispielzeile als Ausschnitt). : luser:Qxno61okNQxpM : Der Benutzername lautet „luser“, das Passwort „secret“. Dieses Passwort wurde mit der UNIX-Crypt-Funktion unter Angabe eines „Salt“, also eines Startwertes, auf dem die Verschlüsselung basiert, verschlüsselt. crypt("secret","Qx") Salt ist hier im Beispiel „Qx“. Diese beiden Buchstaben erscheinen dann als erste Buchstaben des verschlüsselten Passworts. So kann man relativ einfach ein vom Benutzer einge- Verschlüsselung von Passwörtern Teil 9/2.5 Seite 2 Grundlagen Authentifizierung gebenes Passwort wieder mit diesem „Salt“ verschlüsseln und mit dem verschlüsselt abgespeicherten Passwort vergleichen (Beispiel in Perl, Passwortdatei geöffnet als Stream PWD). $_ = <PWD>; chop; # Zeile aus Passwortdatei lesen # Zeilenende abschneiden # Felder an : aufteilen und zuweisen ($user,$password) = split(/:/); # benoetigtes salt bestimmen $salt = substr($password,0,2); # Vom Benutzer eingegebenes Passwort steht # in $pwd, dieses verschluesseln wir $encrypted = crypt($pwd,$salt) # vergleichen der verschlüsselten Passwörter if ($encrypted eq $password) { print "Passwort richtig.\n"; ... } else { print "Tut mir Leid ...\n"; } HTTP-Request Doch zurück zur Abfrage einer Datei vom HTTP-Server. Schickt man an den Server mit GET /users_only/index.html HTTP/1.0 Unauthorized eine Anfrage nach einer Datei in einem derartig geschützten Verzeichnis „users_only“, so antwortet der Server mit HTTP/1.0 401 Unauthorized Date: Fri, 31 Jul 1998 11:12:00 GMT Server: Apache/1.1.3 WWW-Authenticate: Basic realm="Sicherheitsbereich" Content-type: text/html <HEAD><TITLE>Authorization Required</TITLE></HEAD> <BODY><H1>Authorization Required</H1> This server could not verify that you are authorized to access the document you requested. Either you supplied the wrong credentials (e.g., bad password), or your browser doesn’t understand how to supply the credentials required.<P> </BODY> Grundlagen Teil 9/2.5 Seite 3 Authentifizierung Um doch Zugriff auf diese Datei zu erhalten erkennt der Browser den Status-Code 401 und frägt den User unter Angabe des im Error-Header angegebenen „realm“ nach Username und Passwort. Dies wird dann vom Server Base64Encoded und in einem neuerlichen HTTP-Request mitübermittelt. Passwortabfrage GET /users_only/index.html HTTP/1.0 Authorization: Basic dXNlcjpzZWNyZXQ= Nun sollte der Server korrekt mit einem Status 200 OK antworten und das Dokument liefern. HTTP/1.0 200 OK Date: Fri, 31 Jul 1998 11:35:55 GMT Server: Apache/1.1.3 Content-type: text/html <HTML> <HEAD><TITLE>Users-Only-Bereich</TITLE> </HEAD> <BODY> ... </BODY> </HTML> Es ist allerdings zu beachten, dass Benutzername und Passwort im Klartext (nur Base64-Codiert) übertragen werden. Das heißt, dass jeder von jedem Rechner aus, über den das TCP-Paket mit diesem Inhalt geroutet wird, mit einem einfachen Package-Sniffer diesen Text auslesen und Base64Decodieren kann, um dann selbst auf diesen vermeintlich „geschützten“ Bereich zuzugreifen. Achtung Teil 9/2.5 Seite 4 Authentifizierung Grundlagen