Nahe treten

Auf die Oracle-Datenbank ohne spezielle Treiber zuzugreifen, das womöglich noch vom Internet aus und trotzdem sicher, oder auf die Access-Datenbank vom Linux-Webserver: von Perl aus dank DBI-Proxy leicht zu schaffen.

In Pocket speichern vorlesen Druckansicht 12 Kommentare lesen
Lesezeit: 15 Min.
Von
  • Jochen Wiedmann
Inhaltsverzeichnis

DBI, das Database independent Interface for Perl, ist die Standardschnittstelle zum Zugriff auf Datenbanken für Perl-Programmierer. Ähnliches kennt man auch von ODBC oder JDBC, nur ist DBI erheblich einfacher - eingefleischte Perl-Programmierer dürften das nicht weiter verwunderlich finden.

Besonders bekannt sind Proxies im Web: Sie haben die Aufgabe, Browsern HTML-Seiten zur Verfügung zu stellen. Die Gründe für ihre Benutzung sind vielfältig: Den einen hindert ein Firewall am direkten Zugang zum WWW, der andere mag den großen Cache von HTML-Seiten, den ein solcher Proxy meist hat, und den dritten beschränkt der Proxy auf eine Liste verfügbarer Webserver.

Letzten Endes ist ein Proxy nichts anderes als ein Stellvertreter, der zwischen dem eigentlichen Server und einem Client steht, und dieselben Dienste anbietet. Im Idealfall ist dieser Vorgang für den Client transparent.

Analog ist ein Datenbank-Proxy eine Softwareschicht, die sich zwischen den Client (das Anwendungsprogramm) und den Server (die eigentliche Datenbank) schiebt. Was auf den ersten Blick als überflüssiger Aufwand erscheint, ist tatsächlich ein nützliches Prinzip, dessen Vorteile in diesem Fall sind:

  • Mit Hilfe des Proxy lassen sich fast beliebige Restriktionen hinsichtlich des Datenbankzugriffs erzwingen;
  • er erlaubt den Zugriff auf Datenbanken, die nicht von sich aus netzwerkfähig sind;
  • kommerzielle Datenbanken wie Oracle sind häufig nur über proprietäre Schnittstellen ansprechbar, die auf jedem Client vorhanden sein müssen.

Ursprünglich entwickelt wurde der DBI-Proxy aus Sicherheitsgründen. Ein typisches Szenario ist, dass die Datenbank auf einem Intranetserver läuft, der Proxy auf dem Firewall und der Client irgendwo im Internet. Dadurch ist der sichere Zugriff auf interne Datenbanken von außen möglich. Ebenso hilft der Proxy weiter, wenn der Linux-Webserver Daten aus Access unter Windows benutzen soll. Beispiele für solche Anforderungen sind auch dBase- oder Textdateien mit Trennzeichen, die man in Perl als Datenbanken anwenden kann: Der Treiber DBD::CSV macht es möglich. Viele Windows-Benutzer glauben zwar, dass Access ‘netzwerkfähig’ sei, und verweisen darauf, dass sie von ihrem Arbeitsplatz .mdb-Dateien auf einem Server benutzen. Allerdings läuft in diesem Fall Datenbanksoftware auf der Maschine des Benutzers und eben nicht auf dem Server. Das kann das Arbeiten bremsen, wie das Beispiel der SQL-Abfrage SELECT email FROM Adressen WHERE name = ‘Larry Wall’ zeigt.

Eine wirklich netzwerkfähige Datenbank bekommt in diesem Fall den Text der Abfrage übermittelt. Einschließlich eines gewissen Overheads dürften etwa 500 bis 1000 Bytes über das Netz zum Server fließen. Er wählt den richtigen Datensatz aus und schickt diesen zurück. All dies belastet das Netz kaum. Nicht so im Fall einer Datenbank wie Access: Da die auf dem Client laufende Software die richtige Zeile aus der Tabelle auswählt, muss im Extremfall die ganze Tabelle oder zumindest der ganze Index auf den Client transferiert werden. Bei großen Tabellen kann das aufwändig sein und lange dauern.

Selbst zur Anbindung kommerzieller und netzwerkfähiger Datenbanken benutzen viele den DBI-Proxy. Die eigentlichen Schnittstellen sind meist teuer oder schlecht bis gar nicht portabel, weil sie in C geschrieben sind und kaum ein Hersteller Quelltexte anbietet. In diesem Fall genügt es, dass ein Rechner (der Proxy) die Schnittstelle des Herstellers einsetzt, alle anderen brauchen nur zwei oder drei Perl-Module.

Einer der größten Vorzüge von Perl ist die Menge vorhandener Komponenten und Modulen. Der DBI-Proxy macht davon reichlichen Gebrauch und besteht aus einer Reihe von Komponenten.

Das Beispiel geht von einem unter Windows laufenden Perl-Skript aus, das mit Access arbeiten soll. Ferner sei ein ODBC-DSN (Data Source Name) ‘adressen’ eingerichtet, der den Zugriff auf die Access-Datenbank ermöglicht.

Um mit der Datenbank zu arbeiten, benötigt das Skript zunächst ein so genantes Datenbank-Handle ($dbh). Dieser dient als abstrakte Repräsentation der Datenbank und wird durch einen Aufruf der DBI-Methode connect() gewonnen:

my $dsn = "DBI:ODBC:adressen";
my $dbh = DBI->connect($dsn, "myusername", "mypassword");

Die übergebenen Parameter sind der ‘Datasource Name’ oder DBI-DSN, im Falle des ODBC-Treibers DBD::ODBC nichts anderes als ein Verweis auf die ODBC-DSN, sowie der Benutzername und das Passwort. Das erhaltene Datenbank-Handle erlaubt nun die Ausführung von SQL-Statements:

$dbh->do("INSERT INTO adressen ('Larry Wall','larry@wall.org')");

Ergebnistabellen erlauben das Lesen von Daten. Innerhalb von DBI werden sie durch ‘Statement Handle’ ($sth) repräsentiert, aus denen man zeilenweise lesen kann. Das sieht etwa so aus:

my $sth = $dbh->prepare("SELECT name, email FROM adressen");
$sth->execute();
while (my $ref = $sth->fetchrow_arrayref()) {
print "Name = $ref->{'name'}, Email = $ref->{'email'}\n";
}

DBI erlaubt es, beliebig viele gleichzeitige Datenbank- oder Statement-Handles anzulegen, sodass man einen Baum von Objekten aufbauen kann.

Die grobe Idee des Proxy-Servers ist die folgende: Auf dem Server wird genau derselbe Baum generiert. Die Objekte auf dem Server sind ‘echt’, der Client verwendet so genannte Proxy- oder Schattenobjekte.

Um auf dem Client ein Datenbank-Handle zu generieren, ruft man das Proxy-Objekt DBI auf:

my $dsn = "DBI:Proxy:hostname=ntserver;port =2000; \
dsn=DBI:ODBC:adressen";
my $dbh = DBI->connect($dsn, "myusername", "mypassword");

Dadurch geschieht Folgendes:

  1. Der Hostname beziehungsweise die IP-Nummer des Servers (hostname=ntserver) und die serverseitige DSN (dsn=DBI:ODBC:adressen) werden gelesen.
  2. Das Proxy-Objekt DBI transferiert die Parameter zum DBI-Objekt auf dem Server ‘ntserver’.
  3. Der Server ruft sein DBI-Objekt mit den empfangenen Parametern auf. Es liefert als Ergebnis ein Datenbank-Handle.
  4. Der Server speichert diesen, erzeugt ein Proxy-Objekt und schickt es zum Client, der es als Ergebnis des Methodenaufrufs übergeben bekommt.

Bis auf den geänderten DSN ist dieser Vorgang völlig transparent. Mit den Proxy-Objekten kann man arbeiten, als wären sie die Originale. Man kann die Methode do aufrufen, um SQL-Anweisungen auf dem Server auszuführen oder mit prepare eine Ergebnistabelle generieren lassen. In allen Fällen werden automatisch die Parameter zum Objekt auf dem Server transferiert, in einen entsprechenden Methodenaufruf gewandelt, und die Ergebnisse fließen zurück zum Client.

Hinter diesem einfachen Schema versteckt sich eine ganze Menge. Die eigentlichen Perl-Module DBD::Proxy (Client) und DBI::ProxyServer (Server) sind aber überraschenderweise nicht einmal besonders komplex. Das liegt in erster Linie daran, dass beide ausgiebigen Gebrauch von anderen Perl-Modulen oder Komponenten machen.

  • DBI, die eigentliche Datenbankschnittstelle, ist bereits bekannt. Der Proxy-Server, das heißt das Perl-Modul DBI::ProxyServer, ist als Subklasse von DBI implementiert.
  • Das Modul RPC::PlServer ist der Serverteil von PlRPC (Perl-RPC), ein System zur Implementierung von Client/Server-Anwendungen in Perl, ähnlich Javas RMI (Remote Method Invocation) oder Suns RPC (Remote Procedure Call), aber erheblich einfacher anwendbar. Die Verwaltung der Proxy-Objekte und die Verbindung zwischen ihnen und den echten Objekten ist seine Aufgabe.
  • Net::Daemon ist eine abstrakte Klasse zur Erstellung beliebiger Serveranwendungen: Im Unterschied zu RPC::PlServer ist dieses Modul nicht an ein bestimmtes Protokoll gebunden. Es leistet aber bereits eine ganze Menge, zum Beispiel verschiedene, an das jeweilige Betriebssystem angepasste Serverarchitekturen (Thread, fork, Single-Client), Zugriffskontrollen auf der Ebene von IP-Nummern, Verarbeitung von Konfigurationsdateien et cetera.
  • Storable erledigt die Serialisierung von Daten. Man steckt ein beinahe beliebig komplexes Perl-Objekt in einen Aufruf von Storable::freeze() hinein, beispielsweise Hash-, Array- sowie Hash-Referenzen von Hash-Referenzen und erhält einen String. Diesen kann man in einer Datei speichern oder über das Netz schicken und später durch einen Aufruf von Storable::thaw in dasselbe, wiederhergestellte Perl-Objekt wandeln.

Alle diese Komponenten müssen zunächst installiert werden. Nötig sind die Module Storable, Net::Daemon, PlRPC und DBI sowie ein passender DBI-Treiber. Eine Unix-Maschine soll als Proxy-Client fungieren, ein Windows-Rechner als Server. Mit ActiveStates Perl unter Windows erledigt der folgende Aufruf dort alles Nötige:

ppm
install Storable
install Net-Daemon
Install PlRPC
install DBI
install DBD-ODBC

Zum Testen der korrekten Funktion bietet sich die DBI-Shell an:

C:\> dbish DBI:ODBC:adressen myusername mypassword ...
Connecting to 'DBI:ODBC:adressen' as 'myusername' ...
myusername@DBI:ODBC:adressen>

Meldet sich am Ende der Systemprompt (‘>’), ist alles korrekt eingerichtet, und man kann den Server starten. Das geht am einfachsten mit dem Skript dbiproxy, das ebenfalls Teil von DBI ist:

dbiproxy -logfile C:\temp\dbiproxy.log -localport 2000

Nun läuft der Server und wartet auf Port 2000 auf ankommende Verbindungen. dbiproxy kennt übrigens eine ganze Reihe von Optionen, die meisten stammen aus der untersten Schicht, nämlich von Net::Daemon. Eine Liste aller Optionen liefert

dbiproxy -help

Läuft der Server, ist der Client unter Unix zu bauen. Auf einem gut eingerichteten System verläuft die Installation dank des Perl-Moduls CPAN besonders einfach: Es genügt ein

perl -MCPAN -e "install Bundle::DBI"

Fehlt das CPAN-Modul, muss man sich die nötigen Module Storable, Net::Daemon, RPC::PlClient und DBI selbst per FTP vom CPAN (beispielsweise ftp.cpan.org) besorgen. Die Installation verläuft für jedes Modul identisch:

gzip -d <Modul>.tar.gz
tar xf Modul.tar
cd Modul
perl Makefile.PL
make
make test make install

Zur Prüfung, ob alles geklappt hat, kann man wieder die dbish benutzen:

dbish DBI:Proxy:host=ntserver;port=2000;\
dsn=DBI:ODBC:adressen myusername mypassword
...
Connecting to 'DBI:Proxy:host=ntserver;port=2000;\
dsn=DBI:ODBC:adressen' as 'myusername' ...
myusername@DBI:Proxy:host=ntserver;\
port=2000;dsn=DBI:ODBC:adressen>

Nach der Einrichtung des Proxy-Servers ist dieser praktisch eine im Netz laufende Datenbank. Sofern die zu Grunde liegende Datenbank nicht eigene Zugriffsbeschränkungen implementiert, eine für alle und alles Offene! Das ist möglicherweise in einem Intranet erwünscht, aber auf keinen Fall im Internet. Der Proxy-Server benötigt ein eigenes System der Zugriffskontrolle.

DBI::Proxy bietet dazu umfangreiche Möglichkeiten, die allerdings nicht mehr über die Kommandozeile zugänglich sind. Stattdessen benötigt man eine Konfigurationsdatei in einem recht einfachen Format: Sie enthält Perl-Quelltext, genauer gesagt eine Hash-Referenz. Die Schlüssel dieses Hash entsprechen den Optionen, die auch auf der Kommandozeile verwendbar sind. Beispiel:

{ 'logfile' => 'C:\temp\dbiproxy.log',
'localport' => '2000'
}

Das sind genau die Optionen, die der Proxy-Server weiter oben beim ersten Start mitbekommen hat. Um diese Konfigurationsdatei zu verwenden, ist er zu beenden und erneut zu starten:

dbiproxy -configfile c:\etc\dbiproxy.config

Ein weiterer Schlüssel in dieser Konfigurationsdatei ist clients. Der zugehörige Wert ist eine Array-Referenz mit einer Liste zulässiger Clients. Um zum Beispiel den Zugriff nur von Rechnern mit den IP-Nummern 192.168.1.* und zusätzlich dem Rechner larry.wall.org zu gestatten, könnte man die Konfigurationsdatei so ändern:

{ 'logfile' => 'C:\temp\dbiproxy.log',
'localport' => '2000'
# Zugriffskontrolle
'clients' => [
# Akzeptiere die IP-Nummern 192.168.1.*
{ 'mask' => '^192\.168\.1\.\d+$',
'accept' => 1
},
# Akzeptiere larry.wall.org
{ 'mask' => '^larry\.wall\.org$',
'accept' => 1
}
# Alles andere ablehnen
{ 'mask' => '.*',
'accept' => 0
}
]
}

Auch eine Einschränkung auf bestimmte Benutzernamen ist möglich. Ein ganz besonderer Leckerbissen sind aber die Zugriffsbeschränkungen auf bestimmte SQL-Queries. Der eingangs angesprochene WWW-Server soll vermutlich nur eines können, nämlich neue Adressen erfassen. Mit anderen Worten, er soll nur eine bestimmte Query ausführen können. Dazu dient ein neuer Eintrag in der Client-Liste der Konfigurationsdatei:

# www.meinefirma.de mit SQL-Restriktionen
{ 'mask' => '^larry\.wall\.org$',
'accept' => 1,
'sql' => {
'query1' => 'INSERT INTO adressen VALUES (?, ?)'
}
}

Leider geht es in diesem Fall nicht ohne Änderungen am Client-Programm. Ohne die SQL-Restriktionen stünde im CGI-Skript Folgendes:

$dbh->do("INSERT INTO adressen VALUES (?, ?)", 
undef, $name, $email);

Das muss nun heißen:

$dbh->do("query1", undef, $name, $email);

Natürlich kann man den Schlüssel query1 durch die ganze Query ersetzen und das Client-Programm unverändert lassen.

Speziell im Internet ist eine weitere Funktion sinnvoll: Die Kompression der übertragenen Daten. Zu diesem Zweck übergibt man an den Proxy-Server beim Start die Option compression mit dem Wert gzip, entweder in der Konfigurationsdatei oder auf der Kommandozeile mit --compression=gzip. Im Client ergänzt man die DSN wie folgt:

my $dsn = 
"DBI:Proxy:hostname=ntserver;port=2000"
. ";compression=gzip;dsn=DBI:ODBC:adressen";

Ein weiteres Perl-Modul erledigt die Kompression, nämlich Compress::ZLib. Es muss wie oben beschrieben installiert sein, um Daten komprimiert übertragen zu können.

Ebenfalls besonders für das Internet geeignet ist die verschlüsselte Kommunikation zwischen Client und Server. Infrage kommen dabei bislang nur die zwei symmetrischen Verschlüsselungsverfahren DES und IDEA, die die Perl-Module Crypt::DES und Crypt::IDEA implementieren. Diese sind also gegebenenfalls auf Client und Server zunächst zu installieren.

Auf dem Server geschieht die Konfiguration der Verschlüsselung am besten in der Konfigurationsdatei durch Eintragen beispielsweise dieser Zeilen:

use Crypt::IDEA();
my $key = Crypt::IDEA-> \
new('97cd2375efa329aceef2098babdc9721');

Um nun für den WWW-Server Verschlüsselung einzuschalten, ändert man den Client-Eintrag wie folgt:

# www.meinefirma.de mit SQL-Restriktionen
# und Verschlüsselung
{ 'mask' => '^larry\.wall\.org$',
'accept' => 1,
'sql' => {
'query1' => 'INSERT INTO adressen VALUES (?, ?)'
},
'cipher' => $key
}

Auch auf dem Client (das heißt dem WWW-Server) muss man die Verschlüsselung einschalten. Das geht beim Aufruf von DBI->connect(), indem man die DSN ändert:

use Crypt::IDEA();
my $dsn =
"DBI:Proxy:hostname=ntserver;port=2000" . ";cipher=IDEA;\
key=97cd2375efa329aceef2098babdc9721"
. ";dsn=DBI:ODBC:adressen";

Mit DBI und den weiteren Modulen lässt sich so eine sichere und hinreichend schnelle Client/Server-Lösung aufbauen, mit der man auch Datenbanken ins Netz bekommt, die von Haus aus nur auf Einzelrechnern laufen.

Jochen Wiedmann
ist seit 1997 einer der aktivsten Autoren frei kopierbarer Perl-Module, speziell im Bereich Datenbanken und Netzwerk. Er arbeitet als Experte für XML-Datenbanken und E-Commerce bei der Software AG.

[1] Alligator Descartes, Tim Bunce; Programmierung mit Perl DBI; Deutsche Übersetzung von Jochen Wiedmann; O’Reilly Verlag, Köln 2000; ISBN 3-89721-143-2

Mehr Infos

iX-TRACT

  • DBI::Proxy ermöglicht die Perl-Anbindung von Datenbanken, für die die nötige lokale Software fehlt oder die von Haus aus nicht netzwerkfähig sind.
  • Der Proxy-Server muss auf demselben Rechner laufen, auf dem das Datenbanksystem installiert ist. Mit ihm kommuniziert der Proxy-Client über das Netz.
  • Zugriffe auf den Proxy-Server sind auf einzelne Rechner, Domains oder SQL-Abfragen einschränkbar, die Kommunikation kann mit DES oder IDEA verschlüsselt werden.

(ck)