# vim: set cindent expandtab ts=4 sw=4:
#
# Copyright (c) 1998-2005 Chi-Keung Ho. All rights reserved.
#
# This programe 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
# of the License, or (at your option) any later version.
#
# Extmail - a high-performance webmail to maildir
# $Id$
use strict;
use Net::LDAP;
package Ext::Auth::LDAP;
use Exporter;
use Ext::Passwd;
use vars qw(@ISA @EXPORT);
@ISA = qw(Exporter);
@EXPORT = qw(auth);
sub new {
my $this = shift;
my $self = bless {@_}, ref $this || $this;
$self->init(@_);
$self;
}
sub init {
my $self = shift;
my %opt = @_;
$opt{host} = '127.0.0.1' if not defined $opt{host};
$opt{base} = 'dc=extmail.org' if not defined $opt{base};
$opt{rootdn} = 'cn=Manager,dc=extmail.org'
if not defined $opt{rootdn};
$opt{rootpw} = 'rootpw' if not defined $opt{rootpw};
$opt{bind} = 0 if not defined $opt{bind};
$opt{filter} = 'mail=*' if not defined $opt{filter};
$self->{opt}=\%opt;
my ($ldap, $msg);
$ldap = Net::LDAP->new($opt{host}) or die "LDAP operation fail, $!\n";
if($opt{bind}) {
$msg = $ldap->bind(
$opt{rootdn},
password=>$opt{rootpw},
version => 3
);
$self->{msg} = $msg;
}
$self->{pwhandle} = Ext::Passwd->new(
fallback_scheme => $opt{crypt_type} || 'crypt'
);
$self->{ldap} = $ldap;
}
sub search {
my $self = shift;
my $result = $self->{ldap}->search(
base => $_[2] || $self->{opt}->{base},
scope => "sub",
filter => "$_[0]" || $self->{opt}->{filter},
attrs => $_[1]
);
$result;
}
# return value redifination since 0.24-RC2
#
# $rv = 0 LOGIN_OK
# $rv = -1 LOGIN_FAIL
# $rv = 1 LOGIN_DISABLED
# $rv = 2 LOGIN_DEACTIVE
# $rv = 3 LOGIN_EXPIRED
sub auth {
my $self = shift;
my ($username, $password) = (@_);
# here we don't use $self, for it init LDAP without bind, if the
# auth operation can receive userPassword field without bind, then
# we can simplly use $self->search not create a new obj.
#
# Caution: filter should advoid special quoted chars. if you must
# do it, prepend \\\, eg: \\\@domain.tld
my $res = $self->search("mail=$username", undef, undef);
if($res->entry(0)) {
my $attr_pwd = $self->{opt}->{'ldif_attr_passwd'};
my $pwd = $res->entry(0)->get_value($attr_pwd);
my $rv = -1; # flag to indicate authentication ok/fail
my $handle = $self->{pwhandle}; # Ext::Passwd object
# this step is a must, or null userpassword record will cause hole
# that anonymous can step in the system
return -1 unless($password && $pwd);
if($handle->verify($password, $pwd)) {
if ($self->{opt}->{'ldif_attr_disablewebmail'} &&
$res->entry(0)->get_value($self->{opt}->{'ldif_attr_disablewebmail'})) {
return ($rv = 1);
}
if ($self->{opt}->{'ldif_attr_active'} &&
!$res->entry(0)->get_value($self->{opt}->{'ldif_attr_active'})) {
return ($rv = 2);
}
$self->{INFO} = $self->_fill_user_info($res->entry(0));
return 0;
}else {
return -1;
}
}
-1; # default ?:)
}
sub change_passwd {
my $self = shift;
my ($username, $old, $new) = @_;
if($self->auth($username, $old) == 0) {
my $handle = $self->{pwhandle};
my $type = $handle->{_scheme};
my $cnew = $handle->encrypt($type, $new);
# according to RFC2307/2256 must prepend password type
if ($type eq 'MD5' and substr($cnew, 0, 3) eq '$1$') {
$cnew = '{CRYPT}'.$cnew;
}
if ($type eq 'CRYPT') {
$cnew = '{CRYPT}'.$cnew;
}
my $res = $self->search("mail=$username", undef, undef);
my $pwa = [ $self->{opt}->{'ldif_attr_passwd'} => $cnew ];
if ($self->{opt}->{'ldif_attr_clearpw'}) {
# fillin clear password if the attribute defined
push @$pwa, ($self->{opt}->{'ldif_attr_clearpw'} => $new);
}
my $mesg = $self->{ldap}->modify(
$res->entry(0)->dn,
replace => $pwa,
);
return 0 if($mesg->code); # error while modifying
return 1;
}else {
return 0;
}
}
sub _fill_user_info {
my $self = shift;
my $opt = $self->{opt};
my $entry = $_[0];
my %info = ();
foreach my $attr ($entry->attributes) {
$info{$attr} = join(",", $entry->get_value($attr));
}
$info{QUOTA} = $info{$opt->{'ldif_attr_quota'}};
$info{NETDISKQUOTA} = $info{$opt->{'ldif_attr_netdiskquota'}};
$info{HOME} = $info{$opt->{'ldif_attr_home'}}; # must exist
$info{MAILDIR} = $info{$opt->{'ldif_attr_maildir'}} || "$info{HOME}/Maildir";
if ($info{$opt->{'ldif_attr_disablenetdisk'}}) {
$info{OPTIONS} = 'disablenetdisk';
}
if ($info{$opt->{'ldif_attr_disablepwdchange'}}) {
$info{OPTIONS} = ($info{OPTIONS} ? $info{OPTIONS}.',' : '') .'disablepwdchange';
}
\%info;
}
1;
syntax highlighted by Code2HTML, v. 0.9.1