our
@EXPORT
=
qw(ldap)
;
our
$VERSION
=
'1.3.0'
;
our
$ppLoaded
= 0;
BEGIN {
eval
{
threads::shared::share(
$ppLoaded
);
};
}
sub
new {
my
$class
=
shift
;
my
$portal
=
shift
;
my
$self
;
unless
(
$portal
) {
$class
->abort(
"$class : portal argument required !"
);
}
my
$useTls
= 0;
my
$tlsParam
;
my
@servers
= ();
foreach
my
$server
(
split
/[\s,]+/,
$portal
->{ldapServer} ) {
if
(
$server
=~ m{^ldap\+tls://([^/]+)/?\??(.*)$} ) {
$useTls
= 1;
$server
= $1;
$tlsParam
= $2 ||
""
;
}
else
{
$useTls
= 0;
}
push
@servers
,
$server
;
}
$self
= Net::LDAP->new(
\
@servers
,
onerror
=>
undef
,
(
$portal
->{ldapPort} ? (
port
=>
$portal
->{ldapPort} ) : () ),
(
$portal
->{ldapTimeout} ? (
timeout
=>
$portal
->{ldapTimeout} ) : () ),
(
$portal
->{ldapVersion} ? (
version
=>
$portal
->{ldapVersion} ) : () ),
(
$portal
->{ldapRaw} ? (
raw
=>
$portal
->{ldapRaw} ) : () ),
);
unless
(
$self
) {
$portal
->lmLog( $@,
'error'
);
return
0;
}
bless
$self
,
$class
;
if
(
$useTls
) {
my
%h
=
split
( /[&=]/,
$tlsParam
);
$h
{cafile} =
$portal
->{caFile}
if
(
$portal
->{caFile} );
$h
{capath} =
$portal
->{caPath}
if
(
$portal
->{caPath} );
my
$mesg
=
$self
->start_tls(
%h
);
if
(
$mesg
->code ) {
$portal
->lmLog(
'StartTLS failed'
,
'error'
);
return
0;
}
}
$self
->{portal} =
$portal
;
$self
->{portal}->{ldapPwdEnc} ||=
'utf-8'
;
return
$self
;
}
sub
bind
{
my
$self
=
shift
;
my
$mesg
;
my
(
$dn
,
%args
) =
splice
@_
;
unless
(
$dn
) {
$dn
=
$self
->{portal}->{managerDn};
$args
{password} =
$self
->{portal}->{managerPassword};
}
if
(
$dn
&&
$args
{password} ) {
if
(
$self
->{portal}->{ldapPwdEnc} ne
'utf-8'
) {
eval
{
my
$tmp
= encode(
$self
->{portal}->{ldapPwdEnc},
decode(
'utf-8'
,
$args
{password} )
);
$args
{password} =
$tmp
;
};
print
STDERR
"$@\n"
if
($@);
}
$mesg
=
$self
->SUPER::
bind
(
$dn
,
%args
);
}
else
{
$mesg
=
$self
->SUPER::
bind
();
}
return
$mesg
;
}
sub
loadPP {
my
$self
=
shift
;
return
1
if
(
$ppLoaded
);
if
(
$Net::LDAP::VERSION
< 0.38 ) {
$self
->{portal}->abort(
"Module Net::LDAP is too old for password policy, please install version 0.38 or higher"
);
}
if
($@) {
$self
->{portal}->lmLog(
"Module Net::LDAP::Control::PasswordPolicy not found in @INC"
,
'error'
);
return
0;
}
$ppLoaded
= 1;
}
sub
userBind {
my
$self
=
shift
;
if
(
$self
->{portal}->{ldapPpolicyControl} ) {
my
$pp
= Net::LDAP::Control::PasswordPolicy->new();
my
$mesg
=
$self
->
bind
(
@_
,
control
=> [
$pp
] );
my
(
$resp
) =
$mesg
->control(
"1.3.6.1.4.1.42.2.27.8.5.1"
);
unless
(
defined
$resp
) {
if
(
$mesg
->code == 49 ) {
$self
->{portal}->_sub(
'userError'
,
"Bad password for $self->{portal}->{user}"
);
return
PE_BADCREDENTIALS;
}
return
(
$mesg
->code == 0 ? PE_OK : PE_LDAPERROR );
}
if
(
$resp
->grace_authentications_remaining ) {
$self
->{portal}->info(
"<h3>"
.
$resp
->grace_authentications_remaining .
" "
.
$self
->{portal}->msg(PM_PP_GRACE)
.
"</h3>"
);
}
if
(
$resp
->time_before_expiration ) {
$self
->{portal}->info(
"<h3>"
.
sprintf
(
$self
->{portal}->msg(PM_PP_EXP_WARNING),
$self
->{portal}->convertSec(
$resp
->time_before_expiration )
)
.
"</h3>"
);
}
my
$pp_error
=
$resp
->pp_error;
if
(
defined
$pp_error
) {
$self
->{portal}->_sub(
'userError'
,
"Password policy error $pp_error for $self->{portal}->{user}"
);
return
[
PE_PP_PASSWORD_EXPIRED,
PE_PP_ACCOUNT_LOCKED,
PE_PP_CHANGE_AFTER_RESET,
PE_PP_PASSWORD_MOD_NOT_ALLOWED,
PE_PP_MUST_SUPPLY_OLD_PASSWORD,
PE_PP_INSUFFICIENT_PASSWORD_QUALITY,
PE_PP_PASSWORD_TOO_SHORT,
PE_PP_PASSWORD_TOO_YOUNG,
PE_PP_PASSWORD_IN_HISTORY,
]->[
$pp_error
];
}
elsif
(
$mesg
->code == 0 ) {
return
PE_OK;
}
}
else
{
my
$mesg
=
$self
->
bind
(
@_
);
if
(
$mesg
->code == 0 ) {
return
PE_OK;
}
}
$self
->{portal}
->_sub(
'userError'
,
"Bad password for $self->{portal}->{user}"
);
return
PE_BADCREDENTIALS;
}
sub
userModifyPassword {
my
(
$self
,
$dn
,
$newpassword
,
$confirmpassword
,
$oldpassword
,
$ad
) =
splice
@_
;
my
$ppolicyControl
=
$self
->{portal}->{ldapPpolicyControl};
my
$setPassword
=
$self
->{portal}->{ldapSetPassword};
my
$asUser
=
$self
->{portal}->{ldapChangePasswordAsUser};
my
$requireOldPassword
=
$self
->{portal}->{portalRequireOldPassword};
my
$passwordAttribute
=
"userPassword"
;
my
$err
;
my
$mesg
;
unless
(
$newpassword
eq
$confirmpassword
) {
$self
->{portal}->lmLog(
"Password $newpassword and password $confirmpassword are not the same"
,
'debug'
);
return
PE_PASSWORD_MISMATCH;
}
if
(
$ad
) {
$ppolicyControl
= 0;
$setPassword
= 0;
$passwordAttribute
=
"unicodePwd"
;
$newpassword
= utf8(
chr
(34) .
$newpassword
.
chr
(34) )->utf16le();
if
(
$oldpassword
) {
$oldpassword
= utf8(
chr
(34) .
$oldpassword
.
chr
(34) )->utf16le();
}
$self
->{portal}->lmLog(
"Active Directory mode enabled"
,
'debug'
);
}
if
( !
$ppolicyControl
) {
if
(
$setPassword
) {
if
(
$oldpassword
and
$asUser
) {
$mesg
=
$self
->
bind
(
$dn
,
password
=>
$oldpassword
);
if
(
$mesg
->code != 0 ) {
$self
->{portal}->lmLog(
"Bad old password"
,
'debug'
);
return
PE_BADOLDPASSWORD;
}
}
$mesg
=
(
$oldpassword
)
?
$self
->set_password(
user
=>
$dn
,
oldpasswd
=>
$oldpassword
,
newpasswd
=>
$newpassword
)
:
$self
->set_password(
user
=>
$dn
,
newpasswd
=>
$newpassword
);
if
(
$mesg
->code == 53 ) {
$self
->{portal}->lmLog(
"Bad old password"
,
'debug'
);
return
PE_BADOLDPASSWORD;
}
}
else
{
if
(
$ad
and
$oldpassword
) {
$mesg
=
$self
->modify(
$dn
,
delete
=> {
$passwordAttribute
=>
$oldpassword
},
add
=> {
$passwordAttribute
=>
$newpassword
}
);
}
else
{
if
(
$requireOldPassword
) {
return
PE_MUST_SUPPLY_OLD_PASSWORD
if
( !
$oldpassword
);
$mesg
=
$self
->
bind
(
$dn
,
password
=>
$oldpassword
);
if
(
$mesg
->code != 0 ) {
$self
->{portal}->lmLog(
"Bad old password"
,
'debug'
);
return
PE_BADOLDPASSWORD;
}
$self
->
bind
()
unless
$asUser
;
}
$mesg
=
$self
->modify(
$dn
,
replace
=> {
$passwordAttribute
=>
$newpassword
} );
}
}
$self
->{portal}
->lmLog(
"Modification return code: "
.
$mesg
->code,
'debug'
);
return
PE_WRONGMANAGERACCOUNT
if
(
$mesg
->code == 50 ||
$mesg
->code == 8 );
return
PE_PP_INSUFFICIENT_PASSWORD_QUALITY
if
(
$mesg
->code == 53 &&
$ad
);
return
PE_LDAPERROR
unless
(
$mesg
->code == 0 );
$self
->{portal}
->_sub(
'userNotice'
,
"Password changed $self->{portal}->{user}"
);
return
PE_PASSWORD_OK;
}
else
{
my
$pp
= Net::LDAP::Control::PasswordPolicy->new;
if
(
$setPassword
) {
if
(
$oldpassword
and
$asUser
) {
$mesg
=
$self
->
bind
(
$dn
,
password
=>
$oldpassword
);
if
(
$mesg
->code != 0 ) {
$self
->{portal}->lmLog(
"Bad old password"
,
'debug'
);
return
PE_BADOLDPASSWORD;
}
}
$mesg
=
(
$oldpassword
)
?
$self
->set_password(
user
=>
$dn
,
oldpasswd
=>
$oldpassword
,
newpasswd
=>
$newpassword
,
control
=> [
$pp
]
)
:
$self
->set_password(
user
=>
$dn
,
newpasswd
=>
$newpassword
,
control
=> [
$pp
]
);
if
(
$mesg
->code == 53 ) {
$self
->{portal}->lmLog(
"Bad old password"
,
'debug'
);
return
PE_BADOLDPASSWORD;
}
}
else
{
if
(
$oldpassword
) {
$mesg
=
$self
->
bind
(
$dn
,
password
=>
$oldpassword
);
if
(
$mesg
->code != 0 ) {
$self
->{portal}->lmLog(
"Bad old password"
,
'debug'
);
return
PE_BADOLDPASSWORD;
}
$self
->
bind
()
unless
$asUser
;
}
$mesg
=
$self
->modify(
$dn
,
replace
=> {
$passwordAttribute
=>
$newpassword
},
control
=> [
$pp
]
);
}
my
(
$resp
) =
$mesg
->control(
"1.3.6.1.4.1.42.2.27.8.5.1"
);
$self
->{portal}
->lmLog(
"Modification return code: "
.
$mesg
->code,
'debug'
);
return
PE_WRONGMANAGERACCOUNT
if
(
$mesg
->code == 50 ||
$mesg
->code == 8 );
if
(
$mesg
->code == 0 ) {
$self
->{portal}->_sub(
'userNotice'
,
"Password changed $self->{portal}->{user}"
);
return
PE_PASSWORD_OK;
}
if
(
defined
$resp
) {
my
$pp_error
=
$resp
->pp_error;
if
(
defined
$pp_error
) {
$self
->{portal}->_sub(
'userError'
,
"Password policy error $pp_error for $self->{portal}->{user}"
);
return
[
PE_PP_PASSWORD_EXPIRED,
PE_PP_ACCOUNT_LOCKED,
PE_PP_CHANGE_AFTER_RESET,
PE_PP_PASSWORD_MOD_NOT_ALLOWED,
PE_PP_MUST_SUPPLY_OLD_PASSWORD,
PE_PP_INSUFFICIENT_PASSWORD_QUALITY,
PE_PP_PASSWORD_TOO_SHORT,
PE_PP_PASSWORD_TOO_YOUNG,
PE_PP_PASSWORD_IN_HISTORY,
]->[
$pp_error
];
}
}
else
{
return
PE_LDAPERROR;
}
}
}
sub
ldap {
my
$self
=
shift
;
unless
(
$self
->{_multi} ) {
return
$self
->{ldap}
if
(
ref
(
$self
->{ldap} ) );
}
else
{
$self
->lmLog(
"LDAP Cache disabled in multi mode"
,
'debug'
);
}
if
(
$self
->{ldap} = Lemonldap::NG::Portal::_LDAP->new(
$self
)
and
my
$mesg
=
$self
->{ldap}->
bind
)
{
if
(
$mesg
->code != 0 ) {
$self
->lmLog(
"LDAP error: "
.
$mesg
->error,
'error'
);
}
else
{
if
(
$self
->{ldapPpolicyControl}
and not
$self
->{ldap}->loadPP() )
{
$self
->lmLog(
"LDAP password policy error"
,
'error'
);
}
else
{
return
$self
->{ldap};
}
}
}
else
{
$self
->lmLog(
"LDAP error: $@"
,
'error'
);
}
return
0;
}
sub
searchGroups {
my
(
$self
,
$base
,
$key
,
$value
,
$attributes
) =
splice
@_
;
my
$portal
=
$self
->{portal};
my
$groups
;
my
$searchFilter
=
"(&(objectClass="
.
$portal
->{ldapGroupObjectClass} .
")(|"
;
foreach
(
split
(
$portal
->{multiValuesSeparator},
$value
) ) {
$searchFilter
.=
"("
.
$key
.
"="
. escape_filter_value(
$_
) .
")"
;
}
$searchFilter
.=
"))"
;
$portal
->lmLog(
"Group search filter: $searchFilter"
,
'debug'
);
my
$mesg
=
$self
->search(
base
=>
$base
,
filter
=>
$searchFilter
,
attrs
=>
$attributes
,
);
if
(
$mesg
->code() == 0 ) {
foreach
my
$entry
(
$mesg
->all_entries ) {
$portal
->lmLog(
"Matching group "
.
$entry
->dn() .
" found"
,
'debug'
);
if
(
$portal
->{ldapGroupRecursive} ) {
my
$group_value
=
$self
->getLdapValue(
$entry
,
$portal
->{ldapGroupAttributeNameGroup} );
if
(
$group_value
) {
$portal
->lmLog(
"Recursive search for $group_value"
,
'debug'
);
my
$recursive_groups
=
$self
->searchGroups(
$base
,
$key
,
$group_value
,
$attributes
);
$groups
.=
$recursive_groups
.
$portal
->{multiValuesSeparator}
if
(
$recursive_groups
);
}
}
foreach
(
@$attributes
) {
next
if
(
$_
eq
$portal
->{ldapGroupAttributeValueGroup} );
my
$data
=
$entry
->get_value(
$_
);
if
(
$data
) {
$portal
->lmLog(
"Store $data in groups"
,
'debug'
);
$groups
.=
$data
.
"|"
;
}
}
$groups
=~ s/\|$//g;
$groups
.=
$portal
->{multiValuesSeparator};
}
$groups
=~ s/\Q
$portal
->{multiValuesSeparator}\E$//;
}
return
$groups
;
}
sub
getLdapValue {
my
(
$self
,
$entry
,
$attribute
) =
splice
@_
;
return
$entry
->dn()
if
(
$attribute
eq
"dn"
);
my
$value
;
foreach
(
$entry
->get_value(
$attribute
) ) {
$value
.=
$_
;
$value
.=
$self
->{portal}->{multiValuesSeparator};
}
$value
=~ s/\Q
$self
->{portal}->{multiValuesSeparator}\E$//;
return
$value
;
}
1;