Hi friends,

I was trying to use rcpt_ldap plugin which I got from contrib/vetinari directory in the qpsmtpd repository. I was getting the following error "FATAL PLUGIN ERROR: Can't call method "bind" on an undefined value at ./plugins/rcpt_ldap line 88." When I looked into the code, strangely bind function was being called before connecting to the directory server. After doing the necessary changes, rcpt_ldap was able to verify users. I am attaching the changed rcpt_ldap file with email. Please confirm what I have found again and if right, replace the file with what I have attached with this.

Below is the link to the present buggy rcpt_ldap plugin.
http://svn.perl.org/qpsmtpd/contrib/vetinari/rcpt_ldap

--

Regards, Vignesh
http://www.deeproot.in
Ph: +91 (80) 4089 0000

#!/usr/bin/perl -w

# POD is at the end

sub register {
    my ( $self, $qp, @args ) = @_;
    $self->register_hook( "rcpt", "ldap_rcpt" );

    # pull config defaults in from file
    %{ $self->{"ldconf"} } = map { 
                                    (split /\s+/, $_, 2)[0,1] 
                                 } $self->qp->config('ldap');

    # override ldap config defaults with plugin args
    for my $ldap_arg (@args) {
        %{ $self->{"ldconf"} } = map { (split /\s+/, $_, 2)[0,1] } $ldap_arg;
    }

    # do light validation of ldap_host and ldap_port to satisfy -T
    my $ldhost = $self->{"ldconf"}->{'ldap_host'};
    my $ldport = $self->{"ldconf"}->{'ldap_port'};
    if (($ldhost) && ($ldhost =~ m/^(([a-z0-9]+\.?)+)$/)) {
        $self->{"ldconf"}->{'ldap_host'} = $1
    } else {
        undef $self->{"ldconf"}->{'ldap_host'};
    }
    if (($ldport) && ($ldport =~ m/^(\d+)$/)) {
        $self->{"ldconf"}->{'ldap_port'} = $1
    } else {
        undef $self->{"ldconf"}->{'ldap_port'};
    }

    # set any values that are not already
    $self->{"ldconf"}->{"ldap_host"} ||= "127.0.0.1";
    $self->{"ldconf"}->{"ldap_port"} ||= 389;
    $self->{"ldconf"}->{"ldap_user"} ||= "";
    $self->{"ldconf"}->{"ldap_password"} ||= "";
    $self->{"ldconf"}->{"ldap_timeout"} ||= 5;
    $self->{"ldconf"}->{"ldap_rcpt_filter_attr"} ||= "dn";
    $self->{"ldconf"}->{"ldap_rcpt_filter"} ||= 
        '(& (objectClass=inetOrgPerson)(| (mail=%r)(mailAlternateAddress=%r) ) 
)';
                                                     # %r rcpt address
                                                           # %h host part of %r
                                                           # %u user part of %r
}

sub ldap_rcpt {
    use Net::LDAP qw(:all);
    use Qpsmtpd::Constants;

    my ($self, $transaction, $recipient) = @_;
    my ($ldhost, $ldport, $ldwait, $ldbase, $ldfattr, $ldfilter, $lduserdn, 
        $ldh, $mesg, $rcpt, $user, $host, $ldbinddn, $ldbindpw);

    unless (&is_rcpthost($self,$recipient->user,$recipient->host)) {
         return (OK) if $self->qp->connection->relay_client;
         return (DECLINED);
    }

    my @ldap_domains = $self->qp->config("ldap_domains");
    if (@ldap_domains) {
        my $rcpt_host    = lc $recipient->host;
        my $rcpt_is_ldap = 0;
        foreach my $dom (@ldap_domains) {
            if (lc $dom eq $rcpt_host) {
                ++$rcpt_is_ldap;
                last;
            }
        }
        return (DECLINED)
          unless $rcpt_is_ldap;
    }

    # pull values in from config
    $ldhost = $self->{"ldconf"}->{"ldap_host"};
    $ldport = $self->{"ldconf"}->{"ldap_port"};
    $ldbase = $self->{"ldconf"}->{"ldap_base"};
    $ldbinddn = $self->{"ldconf"}->{"ldap_user"};
    $ldbindpw = $self->{"ldconf"}->{"ldap_password"};

    # log error here and DECLINE if no baseDN, because a custom 
    # baseDN is required:
    unless ($ldbase) {
        $self->log(LOGERROR, "ldap_rcpt - please configure ldap_base") &&
          return (DECLINED, "temporary user lookup error");
    }
    $ldwait     = $self->{"ldconf"}->{'ldap_timeout'};
    $ldfattr    = $self->{"ldconf"}->{'ldap_rcpt_filter_attr'};

    # find dn of user matching supplied username
    $ldh = Net::LDAP->new($ldhost, port=>$ldport, timeout=>$ldwait) or
      $self->log(LOGALERT, "ldap_rcpt - error in initial conn") &&
      return (DENYSOFT, "temporary user lookup error");
    
    if (($ldbinddn ne "") && ($ldbindpw ne "")) {
       $ldh->bind($ldbinddn, password => $ldbindpw)
       or
       $self->log(LOGALERT, "ldap_rcpt - error trying to bind with user and 
password") 
       &&  
       return (DENYSOFT, "temporary user lookup error");
    }

    ($rcpt = $recipient->address) =~ s/[()]/\\$1/g;
    ($user = $recipient->user) =~ s/[()]/\\$1/g;     
    $host  = $recipient->host;     

  mbox_retry:
    $ldfilter = $self->{"ldconf"}->{'ldap_rcpt_filter'};
    $ldfilter =~ s/\%r/$rcpt/g;
    $ldfilter =~ s/\%h/$host/g;
    $ldfilter =~ s/\%u/$user/g;


    # find the user's DN
    $mesg = $ldh->search(
        base=>$ldbase,
        scope=>'sub',
        filter=>$ldfilter,
        attrs=>[$ldfattr],
        timeout=>$ldwait
        ) 
      or $self->log(LOGALERT, "ldap_rcpt - err in search for user")
        && return (DENYSOFT, "temporary user lookup error");

    # deal with errors if they exist
    if ($mesg->code) {
        $self->log(LOGALERT, "ldap_rcpt - err ".$mesg->code
                            ." in search for user");
        return (DENYSOFT, "temporary user lookup error");
    }

    # bind against directory as user with password supplied
    if ($mesg->count) {
        $self->log(LOGWARN, "ldap_rcpt - '".$recipient->user
                           ."' lookup success");
        return (OK, "ldap_rcpt");
    } else {
        # if the plugin couldn't find user's entry
        while ($user =~ s/^(.*)\+.*$/$1/) {
            $rcpt = $user.'@'.$host;
            $self->log(LOGINFO, "ldap_rcpt - retrying with '$user'");
            goto mbox_retry;
        }
        $self->log(LOGALERT, "ldap_rcpt - user ".$recipient->address
                            ." not found") 
                && return (DENY, "User not found");
    }
    ### err ... this should be called earlier?!?! (pre return());
    $ldh->disconnect;
}

### this is an ugly duplication of code... it should be in Qpsmtpd::* 
### somewhere (from plugins/rcpt_ok)
sub is_rcpthost {
    my ($self, $user, $host) = @_;

    my @rcpt_hosts = ($self->qp->config("me"), $self->qp->config("rcpthosts"));
    
    # Allow 'no @' addresses for 'postmaster' and 'abuse'
    # qmail-smtpd will do this for all users without a domain, but we'll
    # be a bit more picky.    Maybe that's a bad idea.
    $host = $self->qp->config("me")
        if ($host eq "" && (lc $user eq "postmaster" || lc $user eq "abuse"));
    
    # Check if this recipient host is allowed
    for my $allowed (@rcpt_hosts) {
        $allowed =~ s/^\s*(\S+)/$1/;
        return 1 if $host eq lc $allowed;
        return 1 if substr($allowed,0,1) eq "." and $host =~ m/\Q$allowed\E$/i;
    }

    my $more_rcpt_hosts = $self->qp->config('morercpthosts', 'map');
    return(exists $more_rcpt_hosts->{$host});
}

1;

=head1 NAME

rcpt_ldap - verify local users by looking in LDAP

=head1 DESCRIPTION

This plugin looks up users in an LDAP Directory. This plugin uses the 
'ldap_rcpt_filter' to match the recipient address.

=head1 CONFIGURATION

If you have a mix of ldap / non-ldap domains, add all ldap domains to the
C<ldap_domains> config file, one per line, all lower cased. Only these will
be checked against the ldap directory. 

Configuration items can be held in either the 'ldap' configuration file, or as
arguments to the plugin.

Configuration items in the 'ldap' configuration file
are set one per line, starting the line with the configuration item key,
followed by a space, then the values associated with the configuration item.

Configuration items given as arguments to the plugin are keys and values
separated by spaces.  Be sure to quote any values that have spaces in them.

The only configuration item which is required is 'ldap_base'.  This tells the
plugin what your base DN is.  The plugin will not work until it has been
configured.

The configuration items '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 item '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 item 'ldap_rcpt_filter' specifies how the plugin should
find the user in your Directory. By default, the plugin will look up the
recipient based on the 'mail' or 'mailAlternateAddress' attributes.

=head1 NOTES

The default 'ldap_rcpt_filter' assumes the qmail.schema from 
http://www.qmail-ldap.org/ (inside the diff against qmail). Any filter
will work, as long as at least one result is returned. 
Any '%r' in the filter will be replaced by the recpient address, any '%h' 
with the host part of '%r' and any '%u' with the user part of '%r'.

=head1 FUTURE DIRECTION / THOUGHTS

Any LDAP plugin should use the same config file ('ldap') for qpsmtpd.

=head1 CHANGES

- added $user+$mbox support

- ldap authentication (by Guilherme Buonfiglio)

- "ldap_domains" config

=head1 AUTHOR

Hanno Hecker <h...@uu-x.de>
based on the auth_ldap_bind plugin by Elliot Foster <elli...@gratuitous.net>

=head1 COPYRIGHT AND LICENSE

Copyright (c) 2005 Hanno Hecker

This plugin is licensed under the same terms as the qpsmtpd package itself.
Please see the LICENSE file included with qpsmtpd for details.

=cut

# vim: ts=4 sw=4 expandtab syn=perl

Reply via email to