Elliot F wrote:
> Here is a plugin to allow authentication against an LDAP Directory. Let me
> know
> how it should be wrapped/formatted, or if there are any changes that should be
> made. Specifically, if some other names for the configuration files are
> preferred.
So, not that I'm replying to my own post, but (other than the input validation
issue) what do you guys think? What changes do I need to make before someone
commits this?
> Elliot
>
>
> ------------------------------------------------------------------------
>
> # bind to directory as user/pass given
> #
> # $user (form of '[EMAIL PROTECTED]' or just 'user'), $passclear, $passHash
> (md5?)
> #
>
> sub register {
> my ( $self, $qp ) = @_;
> $self->register_hook( "auth-plain", "authldap" );
> $self->register_hook( "auth-login", "authldap" );
> }
>
> sub authldap {
> ## TODO: add unencrypted password retrieval in the future for CRAM
> ## TODO: add ability to specify custom filter in config
> ## Example:
> (&(uid=$USER)(customattr=$DOMAIN)(!(accountstatus=disabled)))
>
> use Net::LDAP qw(:all);
> use Qpsmtpd::Constants;
> #use Digest::HMAC_MD5 qw(hmac_md5_hex);
>
> my ( $self, $transaction, $method, $user, $passClear, $passHash, $ticket ) =
> @_;
> my ($ldhost, $ldport, $ldwait, $ldbase, $ldmattr, $lduserdn, $ldh, $mesg);
>
> # do light validation of ldap_host and ldap_port to satisfy -T
> $ldhost = $self->qp->config('ldap_host');
> $ldport = $self->qp->config('ldap_port');
> if (($ldhost) && ($ldhost =~ m/^(([a-z0-9]+\.?)+)$/)) {
> $ldhost = $1;
> } else {
> $ldhost = "localhost";
> }
> if (($ldport) && ($ldport =~ m/^(\d+)$/)) {
> $ldport = $1;
> } else {
> $ldport = "389";
> }
> # log error here and DENY because a custom baseDN is required:
> $ldbase = $self->qp->config('ldap_base');
> unless ($ldbase) {
> $self->log(LOGERROR, "authldap/$method - please configure ldap_base" ) &&
> return ( DENY, "authldap/$method - temporary auth error" );
> }
> $ldwait = $self->qp->config('ldap_timeout') || "5";
> $ldmattr = $self->qp->config('ldap_auth_filter_attr') || "uid";
>
> my ( $pw_name, $pw_domain ) = split "@", lc($user);
>
> # find dn of user matching supplied username
> $ldh = Net::LDAP->new($ldhost, port=>$ldport, timeout=>$ldwait ) or
> $self->log(LOGALERT, "authldap/$method - error in initial conn" ) &&
> return ( DENY, "authldap/$method - temporary auth error" );
>
> # find the user's DN
> $mesg = $ldh->search(
> base=>$ldbase,
> scope=>'sub',
> filter=>"$ldmattr=$pw_name",
> attrs=>['uid'],
> timeout=>$ldwait,
> sizelimit=>'1') or
> $self->log(LOGALERT, "authldap/$method - err in search for user" ) &&
> return ( DENY, "authldap/$method - temporary auth error" );
>
> # deal with errors if they exist
> if ( $mesg->code ) {
> $self->log(LOGALERT, "authldap/$method - err " . $mesg->code . " in
> search for user" );
> return ( DENY, "authldap/$method - temporary auth error" );
> }
>
> # unbind, so as to allow a rebind below
> $ldh->unbind if ($ldh);
>
> # bind against directory as user with password supplied
> if ( $lduserdn = $mesg->entry->dn ) {
> $ldh = Net::LDAP->new($ldhost, port=>$ldport, timeout=>$ldwait ) or
> $self->log(LOGALERT, "authldap/$method - err in user conn" ) &&
> return ( DENY, "authldap/$method - temporary auth error" );
>
> # here's the whole reason for the script
> $mesg = $ldh->bind($lduserdn, password=>$passClear, timeout=>$ldwait);
> $ldh->unbind if ($ldh);
>
> # deal with errors if they exist, or allow success
> if ( $mesg->code ) {
> $self->log(LOGALERT, "authldap/$method - error in user bind" );
> return ( DENY, "authldap/$method - wrong username or password" );
> } else {
> $self->log( LOGINFO, "authldap/$method - $user auth success" );
> $self->log( LOGDEBUG, "authldap/$method - user: $user, pass:
> $passClear" );
> return ( OK, "authldap/$method" );
> }
>
> # this is if the plugin couldn't find user's entry
> } else {
> $self->log(LOGALERT, "authldap/$method - err in search results" ) &&
> return ( DENY, "authldap/$method - wrong username or password" );
> }
>
> $ldh->disconnect;
> }
>
> =head1 NAME
>
> auth_ldap_bind - Authenticate user via an LDAP bind
>
> =head1 DESCRIPTION
>
> This plugin authenticates users against an LDAP Directory. The plugin
> first performs a lookup for an entry matching the connecting user. This
> lookup uses the 'ldap_auth_filter_attr' attribute to match the connecting
> user to their LDAP DN. Once the plugin has found the user's DN, the plugin
> will attempt to bind to the Directory as that DN with the password that has
> been supplied.
>
> =head1 CONFIGURATION
>
> The only configuration file which is required is the 'ldap_base' file. This
> file tells the plugin what your base DN is. The plugin will deny access (the
> sane default) until it has been configured.
>
> The configuration files 'ldap_host' and 'ldap_port' specify the host and port
> at which your Directory server may be contacted. If these are not specified,
> the plugin will use port '389' on 'localhost'.
>
> The configuration file 'ldap_timeout' specifies how long the plugin should
> wait for a response from your Directory server. By default, the value is 5
> seconds.
>
> The configuration file 'ldap_auth_filter_attr' specifies how the plugin should
> find the user in your Directory. By default, the plugin will look up the user
> based on the 'uid' attribute.
>
> =head1 NOTES
>
> Each auth requires an initial lookup to find the user's DN. Ideally, the
> plugin would simply bind as the user without the need for this lookup(see
> FUTURE DIRECTION below).
>
> This plugin requires that the Directory allow anonymous bind (see FUTURE
> DIRECTION below).
>
> =head1 FUTURE DIRECTION
>
> A configurable LDAP filter should be made available, to account for users
> who are over quota, have had their accounts disabled, or whatever other
> arbitrary requirements.
>
> A configurable DN template (uid=$USER,ou=$DOMAIN,$BASE). This would prevent
> the need of the initial user lookup, as the DN is created from the template.
>
> A configurable bind DN, for Directories that do not allow anonymous bind.
>
> Another plugin ('ldap_auth_cleartext'?), to allow retrieval of plain-text
> passwords from the Directory, permitting CRAM-MD5 or other hash algorithm
> authentication.
>
> =head1 AUTHOR
>
> Elliot Foster <[EMAIL PROTECTED]>
>
> =head1 COPYRIGHT AND LICENSE
>
> Copyright (c) 2005 Elliot Foster
>
> This plugin is licensed under the same terms as the qpsmtpd package itself.
> Please see the LICENSE file included with qpsmtpd for details.
>
>
> =cut
>