# 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;