📄 proxyserver.pm
字号:
# $Header: /home/timbo/dbi/lib/DBI/RCS/ProxyServer.pm,v 11.9 2003/05/14 11:08:17 timbo Exp $# -*- perl -*-## DBI::ProxyServer - a proxy server for DBI drivers## Copyright (c) 1997 Jochen Wiedmann## The DBD::Proxy module is free software; you can redistribute it and/or# modify it under the same terms as Perl itself. In particular permission# is granted to Tim Bunce for distributing this as a part of the DBI.### Author: Jochen Wiedmann# Am Eisteich 9# 72555 Metzingen# Germany## Email: joe@ispsoft.de# Phone: +49 7123 14881################################################################################require 5.004;use strict;use RPC::PlServer 0.2001;# require DBI; # deferred till AcceptVersion() to aid threadingrequire Config;package DBI::ProxyServer;############################################################################## Constants#############################################################################use vars qw($VERSION @ISA);$VERSION = "0.3005";@ISA = qw(RPC::PlServer DBI);# Most of the options below are set to default values, we note them here# just for the sake of documentation.my %DEFAULT_SERVER_OPTIONS;{ my $o = \%DEFAULT_SERVER_OPTIONS; $o->{'chroot'} = undef, # To be used in the initfile, # after loading the required # DBI drivers. $o->{'clients'} = [ { 'mask' => '.*', 'accept' => 1, 'cipher' => undef } ]; $o->{'configfile'} = '/etc/dbiproxy.conf' if -f '/etc/dbiproxy.conf'; $o->{'debug'} = 0; $o->{'facility'} = 'daemon'; $o->{'group'} = undef; $o->{'localaddr'} = undef; # Bind to any local IP number $o->{'localport'} = undef; # Must set port number on the # command line. $o->{'logfile'} = undef; # Use syslog or EventLog. # XXX don't restrict methods that can be called (trust users once connected) $o->{'XXX_methods'} = { 'DBI::ProxyServer' => { 'Version' => 1, 'NewHandle' => 1, 'CallMethod' => 1, 'DestroyHandle' => 1 }, 'DBI::ProxyServer::db' => { 'prepare' => 1, 'commit' => 1, 'rollback' => 1, 'STORE' => 1, 'FETCH' => 1, 'func' => 1, 'quote' => 1, 'type_info_all' => 1, 'table_info' => 1, 'disconnect' => 1, }, 'DBI::ProxyServer::st' => { 'execute' => 1, 'STORE' => 1, 'FETCH' => 1, 'func' => 1, 'fetch' => 1, 'finish' => 1 } }; if ($Config::Config{'usethreads'} eq 'define') { $o->{'mode'} = 'threads'; } elsif ($Config::Config{'d_fork'} eq 'define') { $o->{'mode'} = 'fork'; } else { $o->{'mode'} = 'single'; } # No pidfile by default, configuration must provide one if needed $o->{'pidfile'} = 'none'; $o->{'user'} = undef;};############################################################################## Name: Version## Purpose: Return version string## Inputs: $class - This class## Result: Version string; suitable for printing by "--version"#############################################################################sub Version { my $version = $DBI::ProxyServer::VERSION; "DBI::ProxyServer $version, Copyright (C) 1998, Jochen Wiedmann";}############################################################################## Name: AcceptApplication## Purpose: Verify DBI DSN## Inputs: $self - This instance# $dsn - DBI dsn## Returns: TRUE for a valid DSN, FALSE otherwise#############################################################################sub AcceptApplication { my $self = shift; my $dsn = shift; $dsn =~ /^dbi:\w+:/i;}############################################################################## Name: AcceptVersion## Purpose: Verify requested DBI version## Inputs: $self - Instance# $version - DBI version being requested## Returns: TRUE for ok, FALSE otherwise#############################################################################sub AcceptVersion { my $self = shift; my $version = shift; require DBI; DBI::ProxyServer->init_rootclass(); $DBI::VERSION >= $version;}############################################################################## Name: AcceptUser## Purpose: Verify user and password by connecting to the client and# creating a database connection## Inputs: $self - Instance# $user - User name# $password - Password#############################################################################sub AcceptUser { my $self = shift; my $user = shift; my $password = shift; return 0 if (!$self->SUPER::AcceptUser($user, $password)); my $dsn = $self->{'application'}; $self->Debug("Connecting to $dsn as $user"); local $ENV{DBI_AUTOPROXY} = ''; # :-) $self->{'dbh'} = eval { DBI::ProxyServer->connect($dsn, $user, $password, { 'PrintError' => 0, 'Warn' => 0, 'RaiseError' => 1, 'HandleError' => sub { my $err = $_[1]->err; my $state = $_[1]->state || ''; $_[0] .= " [err=$err,state=$state]"; return 0; } }) }; if ($@) { $self->Error("Error while connecting to $dsn as $user: $@"); return 0; } [1, $self->StoreHandle($self->{'dbh'}) ];}sub CallMethod { my $server = shift; my $dbh = $server->{'dbh'}; # We could store the private_server attribute permanently in # $dbh. However, we'd have a reference loop in that case and # I would be concerned about garbage collection. :-( $dbh->{'private_server'} = $server; $server->Debug("CallMethod: => " . join(",", @_)); my @result = eval { $server->SUPER::CallMethod(@_) }; my $msg = $@; undef $dbh->{'private_server'}; if ($msg) { $server->Debug("CallMethod died with: $@"); die $msg; } else { $server->Debug("CallMethod: <= " . join(",", @result)); } @result;}sub main { my $server = DBI::ProxyServer->new(\%DEFAULT_SERVER_OPTIONS, \@_); $server->Bind();}############################################################################## The DBI part of the proxyserver is implemented as a DBI subclass.# Thus we can reuse some of the DBI methods and overwrite only# those that need additional handling.#############################################################################package DBI::ProxyServer::dr;@DBI::ProxyServer::dr::ISA = qw(DBI::dr);package DBI::ProxyServer::db;@DBI::ProxyServer::db::ISA = qw(DBI::db);sub prepare { my($dbh, $statement, $attr, $params, $proto_ver) = @_; my $server = $dbh->{'private_server'}; if (my $client = $server->{'client'}) { if ($client->{'sql'}) { if ($statement =~ /^\s*(\S+)/) { my $st = $1; if (!($statement = $client->{'sql'}->{$st})) { die "Unknown SQL query: $st"; } } else { die "Cannot parse restricted SQL statement: $statement"; } } } my $sth = $dbh->SUPER::prepare($statement, $attr); my $handle = $server->StoreHandle($sth); if ( $proto_ver and $proto_ver > 1 ) { $sth->{private_proxyserver_described} = 0; return $handle; } else { # The difference between the usual prepare and ours is that we implement # a combined prepare/execute. The DBD::Proxy driver doesn't call us for # prepare. Only if an execute happens, then we are called with method # "prepare". Further execute's are called as "execute". my @result = $sth->execute($params); my ($NAME, $TYPE); my $NUM_OF_FIELDS = $sth->{NUM_OF_FIELDS}; if ($NUM_OF_FIELDS) { # is a SELECT $NAME = $sth->{NAME}; $TYPE = $sth->{TYPE}; } ($handle, $NUM_OF_FIELDS, $sth->{'NUM_OF_PARAMS'}, $NAME, $TYPE, @result); }}sub table_info { my $dbh = shift; my $sth = $dbh->SUPER::table_info(); my $numFields = $sth->{'NUM_OF_FIELDS'}; my $names = $sth->{'NAME'}; my $types = $sth->{'TYPE'}; # We wouldn't need to send all the rows at this point, instead we could # make use of $rsth->fetch() on the client as usual. # The problem is that some drivers (namely DBD::ExampleP, DBD::mysql and # DBD::mSQL) are returning foreign sth's here, thus an instance of # DBI::st and not DBI::ProxyServer::st. We could fix this by permitting # the client to execute method DBI::st, but I don't like this. my @rows; while (my ($row) = $sth->fetch()) { last unless defined $row; push(@rows, [@$row]); } ($numFields, $names, $types, @rows);}package DBI::ProxyServer::st;@DBI::ProxyServer::st::ISA = qw(DBI::st);sub execute { my $sth = shift; my $params = shift; my $proto_ver = shift; my @outParams; if ($params) { for (my $i = 0; $i < @$params;) { my $param = $params->[$i++]; if (!ref($param)) { $sth->bind_param($i, $param); } else { if (!ref(@$param[0])) {#It's not a reference $sth->bind_param($i, @$param); } else { $sth->bind_param_inout($i, @$param); my $ref = shift @$param; push(@outParams, $ref); } } } } my $rows = $sth->SUPER::execute(); if ( $proto_ver and $proto_ver > 1 and not $sth->{private_proxyserver_described} ) { my ($NAME, $TYPE); my $NUM_OF_FIELDS = $sth->{NUM_OF_FIELDS}; if ($NUM_OF_FIELDS) { # is a SELECT $NAME = $sth->{NAME}; $TYPE = $sth->{TYPE}; } $sth->{private_proxyserver_described} = 1; # First execution, we ship back description. return ($rows, $NUM_OF_FIELDS, $sth->{'NUM_OF_PARAMS'}, $NAME, $TYPE, @outParams); } ($rows, @outParams);}sub fetch { my $sth = shift; my $numRows = shift || 1; my($ref, @rows); while ($numRows-- && ($ref = $sth->SUPER::fetch())) { push(@rows, [@$ref]); } @rows;}1;__END__=head1 NAMEDBI::ProxyServer - a server for the DBD::Proxy driver=head1 SYNOPSIS use DBI::ProxyServer; DBI::ProxyServer::main(@ARGV);=head1 DESCRIPTIONDBI::Proxy Server is a module for implementing a proxy for the DBI proxydriver, DBD::Proxy. It allows access to databases over the network if theDBMS does not offer networked operations. But the proxy server might beusefull for you, even if you have a DBMS with integrated networkfunctionality: It can be used as a DBI proxy in a firewalled environment.DBI::ProxyServer runs as a daemon on the machine with the DBMS or on thefirewall. The client connects to the agent using the DBI driver DBD::Proxy,thus in the exactly same way than using DBD::mysql, DBD::mSQL or any otherDBI driver.The agent is implemented as a RPC::PlServer application. Thus you haveaccess to all the possibilities of this module, in particular encryptionand a similar configuration file. DBI::ProxyServer adds the possibility ofquery restrictions: You can define a set of queries that a client mayexecute and restrict access to those. (Requires a DBI driver that supportsparameter binding.) See L</CONFIGURATION FILE>.The provided driver script, L<dbiproxy>, may either be used as it is orused as the basis for a local version modified to meet your needs.=head1 OPTIONSWhen calling the DBI::ProxyServer::main() function, you supply anarray of options. These options are parsed by the Getopt::Long module.The ProxyServer inherits all of RPC::PlServer's and hence Net::Daemon'soptions and option handling, in particular the ability to readoptions from either the command line or a config file. SeeL<RPC::PlServer>. See L<Net::Daemon>. Available options include=over 4=item I<chroot> (B<--chroot=dir>)(UNIX only) After doing a bind(), change root directory to the givendirectory by doing a chroot(). This is usefull for security, but itrestricts the environment a lot. For example, you need to load DBIdrivers in the config file or you have to create hard links to Unixsockets, if your drivers are using them. For example, with MySQL, aconfig file might contain the following lines: my $rootdir = '/var/dbiproxy'; my $unixsockdir = '/tmp'; my $unixsockfile = 'mysql.sock'; foreach $dir ($rootdir, "$rootdir$unixsockdir") { mkdir 0755, $dir; } link("$unixsockdir/$unixsockfile", "$rootdir$unixsockdir/$unixsockfile"); require DBD::mysql; { 'chroot' => $rootdir, ... }If you don't know chroot(), think of an FTP server where you can see acertain directory tree only after logging in. See also the --group and--user options.=item I<clients>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -