—##@file
# Base package for Lemonldap::NG configuration system
##@class
# Implements Lemonldap::NG shared configuration system.
# In case of error or warning, the message is stored in the global variable
# $Lemonldap::NG::Common::Conf::msg
package
Lemonldap::NG::Common::Conf;
use
strict;
use
utf8;
no
strict
'refs'
;
# Import compacter
*compactConf
= \
&Lemonldap::NG::Common::Conf::Compact::compactConf
;
# TODO: don't import this big file, use a proxy
;
#link protected cipher Object "cypher" in configuration hash
use
Config::IniFiles;
#inherits Lemonldap::NG::Common::Conf::Backends::File
#inherits Lemonldap::NG::Common::Conf::Backends::DBI
#inherits Lemonldap::NG::Common::Conf::Backends::SOAP
#inherits Lemonldap::NG::Common::Conf::Backends::LDAP
our
$VERSION
=
'2.19.0'
;
our
$msg
=
''
;
our
$iniObj
;
our
$PlaceHolderRe
=
'%SERVERENV:(.*?)%'
;
BEGIN {
eval
{
threads::shared::share(
$iniObj
);
};
}
## @cmethod Lemonldap::NG::Common::Conf new(hashRef arg)
# Constructor.
# Succeed if it has found a way to access to Lemonldap::NG configuration with
# $arg (or default file). It can be :
# - Nothing: default configuration file is tested,
# - { confFile => "/path/to/storage.conf" },
# - { Type => "File", dirName => "/path/to/conf/dir/" },
# - { Type => "DBI", dbiChain => "DBI:MariaDB:database=lemonldap-ng;host=1.2.3.4",
# dbiUser => "user", dbiPassword => "password" },
# - { Type => "SOAP", proxy => "https://auth.example.com/config" },
# - { Type => "LDAP", ldapServer => "ldap://localhost", ldapConfBranch => "ou=conf,ou=applications,dc=example,dc=com",
# ldapBindDN => "cn=manager,dc=example,dc=com", ldapBindPassword => "secret"},
#
# $self->{type} contains the type of configuration access system and the
# corresponding package is loaded.
# @param $arg hash reference or hash table
# @return New Lemonldap::NG::Common::Conf object
sub
new {
my
$class
=
shift
;
my
$self
=
bless
{},
$class
;
if
(
ref
(
$_
[0] ) ) {
%$self
= %{
$_
[0] };
}
else
{
if
( (
@_
) &&
$#_
% 2 == 1 ) {
%$self
=
@_
;
}
}
unless
(
$self
->{mdone} ) {
unless
(
$self
->{type} ) {
# Use local conf to get configStorage and localStorage
my
$localConfAccessPrms
=
$self
->getLocalConf( CONFSECTION,
$self
->{confFile}, 0 );
if
(
defined
$localConfAccessPrms
) {
%$self
= (
%$self
,
%$localConfAccessPrms
);
}
}
unless
(
$self
->{type} ) {
$msg
.=
"Error: configStorage: type is not defined.\n"
;
return
0;
}
unless
(
$self
->{type} =~ /^[\w:]+$/ ) {
$msg
.=
"Error: configStorage: type is not well formed.\n"
;
}
$self
->{type} =
"Lemonldap::NG::Common::Conf::Backends::$self->{type}"
unless
$self
->{type} =~ /::/;
eval
"require $self->{type}"
;
if
($@) {
$msg
.=
"Error: failed to load $self->{type}: \n $@"
;
return
0;
}
return
0
unless
$self
->prereq;
$self
->{mdone}++;
$msg
=
"$self->{type} loaded.\n"
;
}
if
(
$self
->{localStorage} and not
defined
(
$self
->{refLocalStorage} ) ) {
eval
"use $self->{localStorage};"
;
if
($@) {
$msg
.=
"Error: Unable to load $self->{localStorage}: $@.\n"
;
}
# TODO: defer that until $> > 0 (to avoid creating local cache with
# root privileges
else
{
$self
->{refLocalStorage} =
$self
->{localStorage}->new(
$self
->{localStorageOptions} );
}
}
return
$self
;
}
## @method int saveConf(hashRef conf, hash args)
# Serialize $conf and call store().
# @param $conf Lemonldap::NG configuration hashRef
# @param %args Parameters
# @return Number of the saved configuration, <=0 in case of error.
sub
saveConf {
my
(
$self
,
$conf
,
%args
) =
@_
;
my
$last
=
$self
->lastCfg;
# If configuration was modified, return an error
if
( not
$args
{force} ) {
return
CONFIG_WAS_CHANGED
if
(
$conf
->{cfgNum} ne
$last
||
$args
{cfgDate} &&
$args
{cfgDate} ne
$args
{currentCfgDate} );
return
DATABASE_LOCKED
if
(
$self
->isLocked() or not
$self
->
lock
() );
}
$conf
->{cfgNum} =
$last
+ 1
unless
(
$args
{cfgNumFixed} );
delete
$conf
->{cipher};
# Try to store configuration
my
$tmp
=
$self
->store(
$conf
);
unless
(
$tmp
> 0 ) {
$msg
.=
"Error: Configuration $conf->{cfgNum} not stored.\n"
;
$self
->unlock();
return
(
$tmp
?
$tmp
: UNKNOWN_ERROR );
}
$msg
.=
"Configuration $conf->{cfgNum} stored.\n"
;
$self
->setDefault(
$conf
);
$self
->setValuesFromEnv(
$conf
);
$self
->compactConf(
$conf
);
if
( Lemonldap::NG::Handler::Main->can(
'tsv'
)
and Lemonldap::NG::Handler::Main->tsv->{msgBrokerWriter} )
{
Lemonldap::NG::Handler::Main->tsv->{msgBrokerWriter}
->publish(
$conf
->{eventQueueName}, {
action
=>
'newConf'
} );
}
else
{
eval
{ Lemonldap::NG::Handler::Main->reload() };
}
$self
->unlock();
return
$tmp
;
}
## @method hashRef getConf(hashRef args)
# Get configuration from remote configuration storage system or from local
# cache if configuration has not been changed. If $args->{local} is set and if
# a local configuration is available, remote configuration is not tested.
#
# Uses lastCfg to test and getDBConf() to get the remote configuration
# @param $args Optional, contains {local=>1} or nothing
# @return Lemonldap::NG configuration
sub
getConf {
my
(
$self
,
$args
) =
@_
;
my
$res
;
my
$local
=
$args
->{
local
};
my
$cfgNum
=
$args
->{cfgNum};
my
$localPrm
=
$args
->{localPrm};
my
$noCache
=
$args
->{noCache};
my
$raw
=
$args
->{raw};
my
$allow_cache_for_root
=
(
$self
->{localStorageOptions}
?
$self
->{localStorageOptions}->{allow_cache_for_root}
: 0 );
# If running as UID=0, disabled the cache unless explicitely allowed
if
( $> == 0 and !
$allow_cache_for_root
) {
$noCache
= 1;
}
# Use only cache to get conf if $local is set
if
( !
$noCache
and
$local
and
ref
(
$self
->{refLocalStorage} )
and
$res
=
$self
->{refLocalStorage}->get(
'conf'
) )
{
$msg
.=
"Get configuration from cache without verification.\n"
;
}
# Check cfgNum in conf backend
# Get conf in backend only if a newer configuration is available
else
{
$cfgNum
||=
$self
->lastCfg;
unless
(
$cfgNum
) {
$msg
.=
"Error: No configuration available in backend.\n"
;
}
my
$r
;
unless
(
ref
(
$self
->{refLocalStorage} ) ) {
$msg
.=
"Get remote configuration (localStorage unavailable).\n"
;
$r
=
$self
->getDBConf( {
cfgNum
=>
$cfgNum
} );
return
undef
unless
(
$r
->{cfgNum} );
$self
->setDefault(
$r
,
$localPrm
);
$self
->setValuesFromEnv(
$r
);
$self
->compactConf(
$r
);
}
else
{
eval
{
$r
=
$self
->{refLocalStorage}->get(
'conf'
) }
if
( !
$noCache
);
$msg
.=
"Warn: $@"
if
($@);
if
(
ref
(
$r
)
and
$r
->{cfgNum}
and
$cfgNum
and
$r
->{cfgNum} ==
$cfgNum
)
{
$msg
.=
"Configuration unchanged, get configuration from cache.\n"
;
}
else
{
my
$r2
=
$self
->getDBConf( {
cfgNum
=>
$cfgNum
} );
unless
(
$r2
->{cfgNum} ) {
$r
=
$self
->{refLocalStorage}->get(
'conf'
)
unless
(
$r
);
$msg
.=
$r
?
"Error: Using previous cached configuration\n"
:
"Error: No configuration found in local cache\n"
;
return
undef
unless
(
$r
);
}
else
{
$r
=
$r2
;
}
# Store configuration in cache
$self
->setLocalConf(
$r
)
if
(
$self
->{refLocalStorage}
and ( !
$noCache
or
$noCache
!= 1 )
and !
$raw
);
}
}
# Return configuration hash
$res
=
$r
;
}
$self
->setDefault(
$res
,
$localPrm
);
$self
->setValuesFromEnv(
$res
);
$self
->compactConf(
$res
);
# Create cipher object and replace variable placeholder
unless
(
$raw
) {
$self
->replacePlaceholders(
$res
)
if
$self
->{useServerEnv};
eval
{
$res
->{cipher} = Lemonldap::NG::Common::Crypto->new(
$res
->{key} );
};
if
($@) {
$msg
.=
"Bad key: $@. \n"
;
}
}
return
$res
;
}
## @method hashRef setDefault(hashRef conf, hashRef localPrm)
# Set default params
# @param $conf Lemonldap::NG configuration hashRef
# @param $localPrm Local parameters
# @return conf
sub
setDefault {
my
(
$self
,
$conf
,
$localPrm
) =
@_
;
if
(
defined
$localPrm
) {
$self
->{localPrm} =
$localPrm
;
}
else
{
$localPrm
=
$self
->{localPrm};
}
my
$defaultValues
=
Lemonldap::NG::Common::Conf::DefaultValues->defaultValues();
if
(
$localPrm
and
%$localPrm
) {
foreach
my
$k
(
keys
%$localPrm
) {
$conf
->{
$k
} =
$localPrm
->{
$k
};
}
}
foreach
my
$k
(
keys
%$defaultValues
) {
$conf
->{
$k
} //=
$defaultValues
->{
$k
};
}
# Some parameters expect key name (example), not variable ($example)
if
(
defined
$conf
->{whatToTrace} ) {
$conf
->{whatToTrace} =~ s/^\$//;
}
return
$conf
;
}
sub
setValuesFromEnv {
my
(
$self
,
$conf
) =
@_
;
$conf
->{hashedSessionStore} =
$ENV
{LLNG_HASHED_SESSION_STORE}
if
$ENV
{LLNG_HASHED_SESSION_STORE};
return
$conf
;
}
## @method hashRef getLocalConf(string section, string file, int loaddefault)
# Get configuration from local file
#
# @param $section Optional section name (default DEFAULTSECTION)
# @param $file Optional file name (default DEFAULTCONFFILE)
# @param $loaddefault Optional load default section parameters (default 1)
# @return Lemonldap::NG configuration
sub
getLocalConf {
my
(
$self
,
$section
,
$file
,
$loaddefault
) =
@_
;
my
$r
= {};
$section
||= DEFAULTSECTION;
$file
||=
$self
->{confFile}
||
$ENV
{LLNG_DEFAULTCONFFILE}
|| DEFAULTCONFFILE;
$loaddefault
= 1
unless
(
defined
$loaddefault
);
my
$cfg
;
# First, search if this file has been parsed
unless
(
$cfg
=
$iniObj
->{
$file
} ) {
# If default configuration cannot be read
# - Error if configuration section is requested
# - Silent exit for other section requests
if
(
open
(
my
$fh
,
'<'
,
$file
) ) {
# Parse ini file
$cfg
= Config::IniFiles->new(
-file
=>
$fh
,
-allowcontinue
=> 1 );
close
(
$fh
);
}
else
{
if
(
$section
eq CONFSECTION ) {
$msg
.=
"Cannot read $file to get configuration access parameters: $!\n"
;
return
$r
;
}
return
$r
;
}
unless
(
defined
$cfg
) {
$msg
.=
"Local config error: "
. (
join
"\n"
,
@Config::IniFiles::errors
) .
"\n"
;
return
$r
;
}
# Check if default section exists
unless
(
$cfg
->SectionExists(DEFAULTSECTION) ) {
$msg
.=
"Default section ("
. DEFAULTSECTION .
") is missing. \n"
;
return
$r
;
}
# Check if configuration section exists
if
(
$section
eq CONFSECTION and !
$cfg
->SectionExists(CONFSECTION) ) {
$msg
.=
"Configuration section ("
. CONFSECTION .
") is missing.\n"
;
return
$r
;
}
}
# First load all default section parameters
if
(
$loaddefault
) {
foreach
(
$cfg
->Parameters(DEFAULTSECTION) ) {
$r
->{
$_
} =
$cfg
->val( DEFAULTSECTION,
$_
);
if
(
$_
eq
"require"
) {
foreach
my
$require
(
split
/[,\s]+/,
$r
->{
$_
} ) {
eval
{
require
$require
};
$msg
.=
"Error: $@"
if
($@);
}
}
if
(
$r
->{
$_
} =~ /^[{\[].*[}\]]$/
||
$r
->{
$_
} =~ /^
sub
\s*{.*}$/ )
{
eval
"\$r->{$_} = $r->{$_}"
;
if
($@) {
$msg
.=
"Warn: error in file $file: $@.\n"
;
return
$r
;
}
}
}
}
# Stop if the requested section is the default section
return
$r
if
(
$section
eq DEFAULTSECTION );
# Check if requested section exists
return
$r
unless
$cfg
->SectionExists(
$section
);
# Load section parameters
foreach
(
$cfg
->Parameters(
$section
) ) {
$r
->{
$_
} =
$cfg
->val(
$section
,
$_
);
# Remove spaces before and after value (#1488)
$r
->{
$_
} =~ s/^\s*(.+?)\s*/$1/;
if
(
$r
->{
$_
} =~ /^[{\[].*[}\]]$/ ||
$r
->{
$_
} =~ /^
sub
\s*{.*}$/ ) {
eval
"\$r->{$_} = $r->{$_}"
;
if
($@) {
$msg
.=
"Warn: error in file $file: $@.\n"
;
return
$r
;
}
}
}
return
$r
;
}
## @method void setLocalConf(hashRef conf)
# Store $conf in the local cache.
# @param $conf Lemonldap::NG configuration hashRef
sub
setLocalConf {
my
(
$self
,
$conf
) =
@_
;
eval
{
$self
->{refLocalStorage}->set(
"conf"
,
$conf
) };
$msg
.=
"Warn: $@\n"
if
($@);
}
## @method hashRef getDBConf(hashRef args)
# Get configuration from remote storage system.
# @param $args hashRef that must contains a key "cfgNum" (number of the wanted
# configuration)
# @return Lemonldap::NG configuration hashRef
sub
getDBConf {
my
(
$self
,
$args
) =
@_
;
my
$cfgNum
=
$args
->{cfgNum};
return
undef
unless
$cfgNum
;
if
(
$cfgNum
< 0 ) {
my
@a
=
$self
->available();
$cfgNum
=
(
@a
+
$cfgNum
> 0 )
? (
$a
[
$#a
+
$cfgNum
] )
:
$a
[0];
}
my
$conf
=
$self
->load(
$cfgNum
);
return
undef
if
$conf
==
"-1"
;
$msg
.=
"Get configuration $conf->{cfgNum}.\n"
if
(
defined
$conf
->{cfgNum} );
return
$conf
;
}
sub
_launch {
my
$self
=
shift
;
my
$sub
=
shift
;
my
@res
;
eval
{
local
$SIG
{ALRM} =
sub
{
die
"TIMEOUT\n"
};
eval
{
alarm
(
$self
->{confTimeout} || 10 );
@res
= &{
$self
->{type} .
"::$sub"
}(
$self
,
@_
);
};
alarm
0;
die
$@
if
$@;
};
if
($@) {
$msg
.= $@;
STDERR
"MSG $msg\n"
;
return
undef
;
}
return
wantarray
? (
@res
) :
$res
[0];
}
## @method boolean prereq()
# Call prereq() from the $self->{type} package.
# @return True if succeed
sub
prereq {
return
shift
->_launch(
'prereq'
,
@_
);
}
## @method @ available()
# Call available() from the $self->{type} package.
# @return list of available configuration numbers
sub
available {
return
shift
->_launch(
'available'
,
@_
);
}
## @method int lastCfg()
# Call lastCfg() from the $self->{type} package.
# @return Number of the last configuration available
sub
lastCfg {
return
shift
->_launch(
'lastCfg'
,
@_
) || 0;
}
## @method boolean lock()
# Call lock() from the $self->{type} package.
# @return True if succeed
sub
lock
{
return
shift
->_launch(
'lock'
,
@_
);
}
## @method boolean isLocked()
# Call isLocked() from the $self->{type} package.
# @return True if database is locked
sub
isLocked {
return
shift
->_launch(
'isLocked'
,
@_
);
}
## @method boolean unlock()
# Call unlock() from the $self->{type} package.
# @return True if succeed
sub
unlock {
return
shift
->_launch(
'unlock'
,
@_
);
}
## @method int store(hashRef conf)
# Call store() from the $self->{type} package.
# @param $conf Lemondlap configuration serialized
# @return Number of new configuration stored if succeed, 0 else.
sub
store {
return
shift
->_launch(
'store'
,
@_
);
}
## @method load(int cfgNum, arrayRef fields)
# Call load() from the $self->{type} package.
# @return Lemonldap::NG Configuration hashRef if succeed, 0 else.
sub
load {
return
shift
->_launch(
'load'
,
@_
);
}
## @method boolean delete(int cfgNum)
# Call delete() from the $self->{type} package.
# @param $cfgNum Number of configuration to delete
# @return True if succeed
sub
delete
{
my
(
$self
,
$c
) =
@_
;
my
@a
=
$self
->available();
if
(
grep
( /^
$c
$/,
@a
) ) {
return
$self
->_launch(
'delete'
,
$c
);
}
else
{
return
0;
}
}
sub
logError {
return
shift
->_launch(
'logError'
,
@_
);
}
sub
_substPlaceHolders {
return
$_
[0]
unless
$_
[0];
$_
[0] =~ s/
$PlaceHolderRe
/
$ENV
{$1}/ge;
return
$_
[0];
}
## @method void replacePlaceholders(res: LLNG_Conf)
#
# Recursively replace %SERVERENV:VariableName% by $ENV{VariableName} value
sub
replacePlaceholders {
my
(
$self
,
$conf
) =
@_
;
if
(
ref
$conf
eq
'HASH'
) {
foreach
my
$key
(
keys
%$conf
) {
if
(
$key
=~ /
$PlaceHolderRe
/ ) {
my
$val
=
$conf
->{
$key
};
delete
$conf
->{
$key
};
my
$nk
= _substPlaceHolders(
$key
);
$conf
->{
$nk
} =
$val
;
}
next
unless
(
$conf
->{
$key
} );
if
(
ref
$conf
->{
$key
} ) {
$self
->replacePlaceholders(
$conf
->{
$key
} );
}
elsif
(
$conf
->{
$key
} =~ /
$PlaceHolderRe
/ ) {
$conf
->{
$key
} = _substPlaceHolders(
$conf
->{
$key
} );
}
}
}
elsif
(
ref
$conf
eq
'ARRAY'
) {
for
(
my
$i
= 0 ;
$i
<
@$conf
;
$i
++ ) {
if
(
ref
$conf
->[
$i
] ) {
$self
->replacePlaceholders(
$conf
->[
$i
] );
}
elsif
(
$conf
->[
$i
] =~ /
$PlaceHolderRe
/ ) {
$conf
->[
$i
] = _substPlaceHolders(
$conf
->[
$i
] );
}
}
}
}
1;
__END__
=head1 NAME
=encoding utf8
Lemonldap::NG::Common::Conf - Perl extension written to manage Lemonldap::NG
Web-SSO configuration.
=head1 SYNOPSIS
use Lemonldap::NG::Common::Conf;
# Lemonldap::NG::Common::Conf reads loacl configuration from lemonldap-ng.ini.
# Parameters can be overridden in a hash:
my $confAccess = new Lemonldap::NG::Common::Conf(
{
type=>'File',
dirName=>"/tmp/",
# To use local cache, set :
localStorage => "Cache::FileCache",
localStorageOptions = {
'namespace' => 'lemonldap-ng-config',
'default_expires_in' => 600,
'directory_umask' => '007',
'cache_root' => '/tmp',
'cache_depth' => 5,
},
},
) or die "Unable to build Lemonldap::NG::Common::Conf, see Apache logs";
# Next, get global configuration. Note that local parameters override global
# ones
my $config = $confAccess->getConf();
=head1 DESCRIPTION
Lemonldap::NG::Common::Conf is used by all Lemonldap::NG packages to access to
local/global configuration.
=head2 SUBROUTINES
=over
=item * B<new> (constructor)
It can takes any Lemonldap::NG parameter to override configuration. The
'confFile' parameter can be used to override lemonldap-ng.ini path.
Examples:
=over
=item * B<Set another lemonldap-ng.ini file>
$confAccess = new Lemonldap::NG::Common::Conf(
{ confFile => '/opt/lemonldap-ng.ini' } );
=item * B<Override global storage>:
$confAccess = new Lemonldap::NG::Common::Conf(
{
type => 'File',
dirName => '/var/lib/lemonldap-ng/conf',
});
=back
=item * B<getConf>: returns a hash reference to the configuration. it takes
a hash reference as first argument containing 2 optional parameters:
=over
=item * C<cfgNum => $number>: the number of the configuration wanted. If this
argument is omitted, the last configuration is returned.
=back
=item * B<saveConf>: stores the Lemonldap::NG configuration passed in argument
(hash reference). it returns the number of the new configuration.
=back
=head1 SEE ALSO
=head1 AUTHORS
=over
=item LemonLDAP::NG team L<http://lemonldap-ng.org/team>
=back
=head1 BUG REPORT
Use OW2 system to report bug or ask for features:
=head1 DOWNLOAD
Lemonldap::NG is available at
=head1 COPYRIGHT AND LICENSE
See COPYING file for details.
This library is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see L<http://www.gnu.org/licenses/>.
=cut