2008-12-31 16:10:02 +01:00
|
|
|
## @file
|
|
|
|
# Add get_key_from_all_sessions() function to Apache::Session modules.
|
|
|
|
# This file is used by Lemonldap::NG::Manager::Status and by the
|
|
|
|
# purgeCentralCache script.
|
|
|
|
#
|
|
|
|
# Warning, this works only with SQL databases, simple or Berkeley files (not
|
|
|
|
# for Apache::Session::Memcached for example)
|
2008-12-01 18:21:34 +01:00
|
|
|
package Lemonldap::NG::Common::Apache::Session;
|
|
|
|
|
2009-04-18 22:08:36 +02:00
|
|
|
use strict;
|
2010-10-08 16:21:49 +02:00
|
|
|
use AutoLoader 'AUTOLOAD';
|
|
|
|
use Apache::Session;
|
|
|
|
use base qw(Apache::Session);
|
2015-05-14 08:44:38 +02:00
|
|
|
use Lemonldap::NG::Common::Apache::Session::Serialize::JSON;
|
2014-02-21 17:38:57 +01:00
|
|
|
use Lemonldap::NG::Common::Apache::Session::Store;
|
2014-07-23 19:50:13 +02:00
|
|
|
use Lemonldap::NG::Common::Apache::Session::Lock;
|
2010-10-08 16:21:49 +02:00
|
|
|
|
2016-03-17 23:19:44 +01:00
|
|
|
our $VERSION = '2.0.0';
|
2010-10-08 16:21:49 +02:00
|
|
|
|
|
|
|
sub _load {
|
2016-02-18 15:56:06 +01:00
|
|
|
my ( $backend, $func ) = @_;
|
2010-10-08 16:21:49 +02:00
|
|
|
unless ( $backend->can('populate') ) {
|
2010-10-30 11:03:52 +02:00
|
|
|
eval "require $backend";
|
2010-11-04 07:19:08 +01:00
|
|
|
die $@ if ($@);
|
2009-10-21 14:43:13 +02:00
|
|
|
}
|
2016-02-10 15:49:04 +01:00
|
|
|
return $func ? $backend->can($func) : 0;
|
2010-10-08 16:21:49 +02:00
|
|
|
}
|
2009-10-21 14:43:13 +02:00
|
|
|
|
2010-10-08 16:21:49 +02:00
|
|
|
sub populate {
|
|
|
|
my $self = shift;
|
|
|
|
my $backend = $self->{args}->{backend};
|
|
|
|
_load($backend);
|
|
|
|
$backend .= "::populate";
|
|
|
|
{
|
|
|
|
no strict 'refs';
|
|
|
|
$self = $self->$backend(@_);
|
2008-12-01 18:21:34 +01:00
|
|
|
}
|
2016-02-10 15:49:04 +01:00
|
|
|
if ( $backend =~
|
|
|
|
/^Apache::Session::(?:(?:Postgre|Redi)s|(?:Oracl|Sybas)e|(?:My|No)SQL|F(?:ile|lex)|Cassandra|LDAP)/
|
|
|
|
and !$self->{args}->{useStorable} )
|
|
|
|
{
|
2015-03-03 20:52:36 +01:00
|
|
|
$self->{serialize} =
|
|
|
|
\&Lemonldap::NG::Common::Apache::Session::Serialize::JSON::serialize;
|
|
|
|
$self->{unserialize} =
|
|
|
|
\&Lemonldap::NG::Common::Apache::Session::Serialize::JSON::unserialize;
|
|
|
|
}
|
2014-06-08 11:20:58 +02:00
|
|
|
if ( $self->{args}->{generateModule} ) {
|
2014-06-08 12:04:50 +02:00
|
|
|
my $generate = $self->{args}->{generateModule};
|
|
|
|
eval "require $generate";
|
|
|
|
die $@ if ($@);
|
|
|
|
$self->{generate} = \&{ $generate . "::generate" };
|
|
|
|
$self->{validate} = \&{ $generate . "::validate" };
|
2014-06-08 11:20:58 +02:00
|
|
|
}
|
2010-10-08 16:21:49 +02:00
|
|
|
if ( $self->{args}->{setId} ) {
|
|
|
|
$self->{generate} = \&setId;
|
|
|
|
$self->{validate} = sub { 1 };
|
|
|
|
}
|
2014-02-21 17:38:57 +01:00
|
|
|
|
|
|
|
# If cache is configured, use our specific object store module
|
2015-12-25 13:50:32 +01:00
|
|
|
if ( $> and $self->{args}->{localStorage} ) {
|
2014-02-21 17:38:57 +01:00
|
|
|
$self->{args}->{object_store} = $self->{object_store};
|
|
|
|
$self->{object_store} =
|
|
|
|
Lemonldap::NG::Common::Apache::Session::Store->new($self);
|
2014-07-23 19:50:13 +02:00
|
|
|
$self->{args}->{lock_manager} = $self->{lock_manager};
|
|
|
|
$self->{lock_manager} =
|
|
|
|
Lemonldap::NG::Common::Apache::Session::Lock->new($self);
|
|
|
|
}
|
2010-10-08 16:21:49 +02:00
|
|
|
return $self;
|
|
|
|
}
|
2008-12-01 18:21:34 +01:00
|
|
|
|
2010-10-08 16:21:49 +02:00
|
|
|
__END__
|
2008-12-01 18:21:34 +01:00
|
|
|
|
2010-10-08 16:21:49 +02:00
|
|
|
sub setId {
|
|
|
|
my $session = shift;
|
|
|
|
$session->{data}->{_session_id} = $session->{args}->{setId};
|
|
|
|
}
|
|
|
|
|
|
|
|
sub searchOn {
|
|
|
|
my ( $class, $args, $selectField, $value, @fields ) = splice @_;
|
2016-02-10 15:49:04 +01:00
|
|
|
return $args->{backend}->searchOn( $args, $selectField, $value, @fields )
|
|
|
|
if ( _load( $args->{backend}, 'searchOn' ) );
|
2010-10-08 16:21:49 +02:00
|
|
|
my %res = ();
|
|
|
|
$class->get_key_from_all_sessions(
|
|
|
|
$args,
|
|
|
|
sub {
|
|
|
|
my $entry = shift;
|
|
|
|
my $id = shift;
|
|
|
|
return undef unless ( $entry->{$selectField} eq $value );
|
|
|
|
if (@fields) {
|
|
|
|
$res{$id}->{$_} = $entry->{$_} foreach (@fields);
|
2008-12-01 18:21:34 +01:00
|
|
|
}
|
|
|
|
else {
|
2010-10-08 16:21:49 +02:00
|
|
|
$res{$id} = $entry;
|
2008-12-01 18:21:34 +01:00
|
|
|
}
|
2010-10-08 16:21:49 +02:00
|
|
|
undef;
|
2008-12-01 18:21:34 +01:00
|
|
|
}
|
2010-10-08 16:21:49 +02:00
|
|
|
);
|
|
|
|
return \%res;
|
|
|
|
}
|
|
|
|
|
2013-07-03 21:31:04 +02:00
|
|
|
sub searchOnExpr {
|
|
|
|
my ( $class, $args, $selectField, $value, @fields ) = splice @_;
|
2016-02-10 15:49:04 +01:00
|
|
|
return $args->{backend}
|
|
|
|
->searchOnExpr( $args, $selectField, $value, @fields )
|
|
|
|
if ( _load( $args->{backend}, 'searchOnExpr' ) );
|
2013-07-03 21:31:04 +02:00
|
|
|
$value = quotemeta($value);
|
|
|
|
$value =~ s/\\\*/\.\*/g;
|
|
|
|
$value = qr/^$value$/;
|
|
|
|
my %res = ();
|
|
|
|
$class->get_key_from_all_sessions(
|
|
|
|
$args,
|
|
|
|
sub {
|
|
|
|
my $entry = shift;
|
|
|
|
my $id = shift;
|
|
|
|
return undef unless ( $entry->{$selectField} =~ $value );
|
|
|
|
if (@fields) {
|
|
|
|
$res{$id}->{$_} = $entry->{$_} foreach (@fields);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$res{$id} = $entry;
|
|
|
|
}
|
|
|
|
undef;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
return \%res;
|
|
|
|
}
|
|
|
|
|
2010-10-08 16:21:49 +02:00
|
|
|
sub searchLt {
|
|
|
|
my ( $class, $args, $selectField, $value, @fields ) = splice @_;
|
2016-02-10 15:49:04 +01:00
|
|
|
return $args->{backend}->searchLt( $args, $selectField, $value, @fields )
|
|
|
|
if ( _load( $args->{backend}, 'searchLt' ) );
|
2010-10-08 16:21:49 +02:00
|
|
|
my %res = ();
|
|
|
|
$class->get_key_from_all_sessions(
|
|
|
|
$args,
|
|
|
|
sub {
|
|
|
|
my $entry = shift;
|
|
|
|
my $id = shift;
|
|
|
|
return undef unless ( $entry->{$selectField} < $value );
|
|
|
|
if (@fields) {
|
|
|
|
$res{$id}->{$_} = $entry->{$_} foreach (@fields);
|
2008-12-01 18:21:34 +01:00
|
|
|
}
|
|
|
|
else {
|
2010-10-08 16:21:49 +02:00
|
|
|
$res{$id} = $entry;
|
2008-12-01 18:21:34 +01:00
|
|
|
}
|
2010-10-08 16:21:49 +02:00
|
|
|
undef;
|
2008-12-01 18:21:34 +01:00
|
|
|
}
|
2010-10-08 16:21:49 +02:00
|
|
|
);
|
|
|
|
return \%res;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub get_key_from_all_sessions {
|
|
|
|
my $class = shift;
|
|
|
|
|
2016-01-21 19:06:55 +01:00
|
|
|
my ( $args, $data ) = @_;
|
|
|
|
my $backend = $args->{backend};
|
2016-02-10 15:49:04 +01:00
|
|
|
if ( _load( $backend, 'get_key_from_all_sessions' ) ) {
|
|
|
|
return $backend->get_key_from_all_sessions( $args, $data );
|
|
|
|
}
|
2016-01-21 19:06:55 +01:00
|
|
|
if ( $args->{useStorable} ) {
|
|
|
|
require Storable;
|
|
|
|
$args->{unserialize} = \&Storable::thaw;
|
|
|
|
}
|
|
|
|
else {
|
2016-01-22 12:26:43 +01:00
|
|
|
$args->{unserialize} =
|
|
|
|
\&Lemonldap::NG::Common::Apache::Session::Serialize::JSON::_unserialize;
|
2016-01-21 19:06:55 +01:00
|
|
|
}
|
2010-10-08 16:21:49 +02:00
|
|
|
if ( $backend =~
|
2015-03-03 20:52:36 +01:00
|
|
|
/^Apache::Session::(MySQL|MySQL::NoLock|Postgres|Oracle|Sybase|Informix)$/
|
|
|
|
)
|
2010-10-08 16:21:49 +02:00
|
|
|
{
|
2010-12-09 18:16:11 +01:00
|
|
|
return $class->_dbiGKFAS( $1, @_ );
|
2010-10-08 16:21:49 +02:00
|
|
|
}
|
|
|
|
elsif ( $backend =~ /^Apache::Session::(?:NoSQL|Redis|Cassandra)$/ ) {
|
|
|
|
return $class->_NoSQLGKFAS(@_);
|
|
|
|
}
|
|
|
|
elsif ( $backend =~ /^Apache::Session::(File|PHP|DBFile|LDAP)$/ ) {
|
|
|
|
no strict 'refs';
|
|
|
|
my $tmp = "_${1}GKFAS";
|
|
|
|
return $class->$tmp(@_);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
die "$backend can not provide session exploration";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-12-09 18:16:11 +01:00
|
|
|
sub decodeThaw64 {
|
|
|
|
require MIME::Base64;
|
|
|
|
my $s = shift;
|
2011-01-30 09:49:59 +01:00
|
|
|
return Storable::thaw( MIME::Base64::decode_base64($s) );
|
2010-12-09 18:16:11 +01:00
|
|
|
}
|
|
|
|
|
2010-10-08 16:21:49 +02:00
|
|
|
sub _dbiGKFAS {
|
2010-12-09 18:16:11 +01:00
|
|
|
my ( $class, $type, $args, $data ) = @_;
|
2016-01-22 12:26:43 +01:00
|
|
|
my $next;
|
|
|
|
if ( $type !~ /(?:MySQL)/ ) {
|
|
|
|
$next = sub {
|
|
|
|
require MIME::Base64;
|
|
|
|
return thaw( MIME::Base64::decode_base64( $_[0] ) );
|
|
|
|
};
|
|
|
|
if ( $args->{useStorable} ) {
|
|
|
|
$args->{unserialize} = $next;
|
|
|
|
}
|
2010-12-09 18:16:11 +01:00
|
|
|
}
|
2010-10-08 16:21:49 +02:00
|
|
|
|
|
|
|
my $dbh =
|
|
|
|
DBI->connect( $args->{DataSource}, $args->{UserName}, $args->{Password} )
|
|
|
|
or die("$!$@");
|
|
|
|
my $sth = $dbh->prepare('SELECT id,a_session from sessions');
|
|
|
|
$sth->execute;
|
|
|
|
my %res;
|
|
|
|
while ( my @row = $sth->fetchrow_array ) {
|
|
|
|
if ( ref($data) eq 'CODE' ) {
|
2016-01-22 12:26:43 +01:00
|
|
|
my $tmp =
|
|
|
|
&$data( $args->{unserialize}->( $row[1], $next ), $row[0] );
|
2010-10-08 16:21:49 +02:00
|
|
|
$res{ $row[0] } = $tmp if ( defined($tmp) );
|
|
|
|
}
|
|
|
|
elsif ($data) {
|
|
|
|
$data = [$data] unless ( ref($data) );
|
2016-01-22 12:26:43 +01:00
|
|
|
my $tmp = $args->{unserialize}->( $row[1], $next );
|
2010-10-08 16:21:49 +02:00
|
|
|
$res{ $row[0] }->{$_} = $tmp->{$_} foreach (@$data);
|
|
|
|
}
|
|
|
|
else {
|
2016-01-22 12:26:43 +01:00
|
|
|
$res{ $row[0] } = $args->{unserialize}->( $row[1], $next );
|
2008-12-05 17:30:27 +01:00
|
|
|
}
|
|
|
|
}
|
2010-10-08 16:21:49 +02:00
|
|
|
return \%res;
|
|
|
|
}
|
2008-12-05 17:30:27 +01:00
|
|
|
|
2010-10-08 16:21:49 +02:00
|
|
|
sub _FileGKFAS {
|
|
|
|
my ( $class, $args, $data ) = @_;
|
|
|
|
$args->{Directory} ||= '/tmp';
|
2008-12-01 18:21:34 +01:00
|
|
|
|
2010-10-08 16:21:49 +02:00
|
|
|
unless ( opendir DIR, $args->{Directory} ) {
|
|
|
|
die "Cannot open directory $args->{Directory}\n";
|
|
|
|
}
|
|
|
|
my @t =
|
|
|
|
grep { -f "$args->{Directory}/$_" and /^[A-Za-z0-9@\-]+$/ } readdir(DIR);
|
|
|
|
closedir DIR;
|
|
|
|
my %res;
|
|
|
|
for my $f (@t) {
|
|
|
|
open F, "$args->{Directory}/$f";
|
|
|
|
my $row = join '', <F>;
|
|
|
|
if ( ref($data) eq 'CODE' ) {
|
2016-01-21 19:06:55 +01:00
|
|
|
eval { $res{$f} = &$data( $args->{unserialize}->($row), $f ); };
|
2012-10-30 16:22:36 +01:00
|
|
|
if ($@) {
|
|
|
|
$res{$f} = &$data( undef, $f );
|
|
|
|
}
|
2010-10-08 16:21:49 +02:00
|
|
|
}
|
|
|
|
elsif ($data) {
|
|
|
|
$data = [$data] unless ( ref($data) );
|
2012-10-30 16:22:36 +01:00
|
|
|
my $tmp;
|
2016-01-21 19:06:55 +01:00
|
|
|
eval { $tmp = $args->{unserialize}->($row); };
|
2012-10-30 16:22:36 +01:00
|
|
|
if ($@) {
|
|
|
|
$res{$f}->{$_} = undef foreach (@$data);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$res{$f}->{$_} = $tmp->{$_} foreach (@$data);
|
|
|
|
}
|
2010-10-08 16:21:49 +02:00
|
|
|
}
|
|
|
|
else {
|
2016-01-21 19:06:55 +01:00
|
|
|
eval { $res{$f} = $args->{unserialize}->($row); };
|
2010-10-08 16:21:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return \%res;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub _PHPGKFAS {
|
|
|
|
require Apache::Session::Serialize::PHP;
|
|
|
|
my ( $class, $args, $data ) = @_;
|
|
|
|
|
|
|
|
my $directory = $args->{SavePath} || '/tmp';
|
|
|
|
unless ( opendir DIR, $args->{SavePath} ) {
|
|
|
|
die "Cannot open directory $args->{SavePath}\n";
|
|
|
|
}
|
|
|
|
my @t =
|
|
|
|
grep { -f "$args->{SavePath}/$_" and /^sess_[A-Za-z0-9@\-]+$/ }
|
|
|
|
readdir(DIR);
|
|
|
|
closedir DIR;
|
|
|
|
my %res;
|
|
|
|
for my $f (@t) {
|
|
|
|
open F, "$args->{SavePath}/$f";
|
|
|
|
my $row = join '', <F>;
|
|
|
|
if ( ref($data) eq 'CODE' ) {
|
|
|
|
$res{$f} =
|
|
|
|
&$data( Apache::Session::Serialize::PHP::unserialize($row), $f );
|
|
|
|
}
|
|
|
|
elsif ($data) {
|
|
|
|
$data = [$data] unless ( ref($data) );
|
|
|
|
my $tmp = Apache::Session::Serialize::PHP::unserialize($row);
|
|
|
|
$res{$f}->{$_} = $tmp->{$_} foreach (@$data);
|
2008-12-01 18:21:34 +01:00
|
|
|
}
|
2010-10-08 16:21:49 +02:00
|
|
|
else {
|
|
|
|
$res{$f} = Apache::Session::Serialize::PHP::unserialize($row);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return \%res;
|
|
|
|
}
|
2008-12-05 17:30:27 +01:00
|
|
|
|
2010-12-09 18:16:11 +01:00
|
|
|
sub _DBFileGKFAS {
|
2010-10-08 16:21:49 +02:00
|
|
|
my ( $class, $args, $data ) = @_;
|
|
|
|
|
|
|
|
if ( !tied %{ $class->{dbm} } ) {
|
|
|
|
my $rv = tie %{ $class->{dbm} }, 'DB_File', $args->{FileName};
|
|
|
|
if ( !$rv ) {
|
|
|
|
die "Could not open dbm file " . $args->{FileName} . ": $!";
|
2008-12-05 17:30:27 +01:00
|
|
|
}
|
2010-10-08 16:21:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
my %res;
|
|
|
|
foreach my $k ( keys %{ $class->{dbm} } ) {
|
|
|
|
if ( ref($data) eq 'CODE' ) {
|
2016-01-21 19:06:55 +01:00
|
|
|
$res{$k} =
|
|
|
|
&$data( $args->{unserialize}->( $class->{dbm}->{$k} ), $k );
|
2009-04-18 22:08:36 +02:00
|
|
|
}
|
2010-10-08 16:21:49 +02:00
|
|
|
elsif ($data) {
|
|
|
|
$data = [$data] unless ( ref($data) );
|
2016-01-21 19:06:55 +01:00
|
|
|
my $tmp = $args->{unserialize}->( $class->{dbm}->{$k} );
|
2010-10-08 16:21:49 +02:00
|
|
|
$res{$k}->{$_} = $tmp->{$_} foreach (@$data);
|
|
|
|
}
|
|
|
|
else {
|
2016-01-21 19:06:55 +01:00
|
|
|
$res{$k} = $args->{unserialize}->( $class->{dbm}->{$k} );
|
2010-10-08 16:21:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return \%res;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub _LDAPGKFAS {
|
|
|
|
my ( $class, $args, $data ) = @_;
|
2015-06-12 15:14:32 +02:00
|
|
|
$args->{ldapObjectClass} ||= 'applicationProcess';
|
|
|
|
$args->{ldapAttributeId} ||= 'cn';
|
|
|
|
$args->{ldapAttributeContent} ||= 'description';
|
2010-10-08 16:21:49 +02:00
|
|
|
|
|
|
|
my $ldap = Apache::Session::Store::LDAP::ldap( { args => $args } );
|
|
|
|
my $msg = $ldap->search(
|
|
|
|
base => $args->{ldapConfBase},
|
2015-06-12 15:14:32 +02:00
|
|
|
filter => '(objectClass=' . $args->{ldapObjectClass} . ')',
|
|
|
|
attrs => [ $args->{ldapAttributeId}, $args->{ldapAttributeContent} ],
|
2010-10-08 16:21:49 +02:00
|
|
|
);
|
2015-06-12 15:29:16 +02:00
|
|
|
|
|
|
|
$ldap->unbind();
|
|
|
|
|
2010-10-08 16:21:49 +02:00
|
|
|
Apache::Session::Store::LDAP->logError($msg) if ( $msg->code );
|
2015-06-12 15:29:16 +02:00
|
|
|
|
2010-10-08 16:21:49 +02:00
|
|
|
my %res;
|
2015-06-12 15:14:32 +02:00
|
|
|
|
2010-10-08 16:21:49 +02:00
|
|
|
foreach my $entry ( $msg->entries ) {
|
2015-06-12 15:14:32 +02:00
|
|
|
my ( $k, $v ) = (
|
|
|
|
$entry->get_value('cn'),
|
|
|
|
$entry->get_value( $args->{ldapAttributeContent} )
|
|
|
|
);
|
2010-12-09 22:18:30 +01:00
|
|
|
eval { $v = decodeThaw64($v); };
|
2011-01-25 17:41:31 +01:00
|
|
|
next if ($@);
|
2010-10-08 16:21:49 +02:00
|
|
|
if ( ref($data) eq 'CODE' ) {
|
2010-12-09 22:18:30 +01:00
|
|
|
$res{$k} = &$data( $v, $k );
|
2010-10-08 16:21:49 +02:00
|
|
|
}
|
|
|
|
elsif ($data) {
|
|
|
|
$data = [$data] unless ( ref($data) );
|
2010-12-09 22:18:30 +01:00
|
|
|
my $tmp = $v;
|
2010-10-08 16:21:49 +02:00
|
|
|
$res{$k}->{$_} = $tmp->{$_} foreach (@$data);
|
|
|
|
}
|
|
|
|
else {
|
2010-12-09 22:18:30 +01:00
|
|
|
$res{$k} = $v;
|
2010-10-08 16:21:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return \%res;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub _NoSQLGKFAS {
|
|
|
|
my ( $class, $args, $data ) = @_;
|
2010-12-09 18:16:11 +01:00
|
|
|
require Redis;
|
2010-10-08 16:21:49 +02:00
|
|
|
die "Only Redis is supported" unless ( $args->{Driver} eq 'Redis' );
|
|
|
|
my $redis = Redis->new(%$args);
|
|
|
|
my @keys = $redis->keys('*');
|
|
|
|
my %res;
|
|
|
|
|
|
|
|
foreach my $k (@keys) {
|
2010-12-09 18:16:11 +01:00
|
|
|
my $v = eval { decodeThaw64( $redis->get($k) ); };
|
2010-10-08 16:21:49 +02:00
|
|
|
next if ($@);
|
|
|
|
if ( ref($data) eq 'CODE' ) {
|
|
|
|
$res{$k} = &$data( $v, $k );
|
|
|
|
}
|
|
|
|
elsif ($data) {
|
|
|
|
$data = [$data] unless ( ref($data) );
|
|
|
|
$res{$k}->{$_} = $v->{$_} foreach (@$data);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$res{$k} = $v;
|
2010-09-24 11:29:43 +02:00
|
|
|
}
|
|
|
|
}
|
2010-10-08 16:21:49 +02:00
|
|
|
return \%res;
|
2008-12-01 18:21:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
1;
|