Hanno Hecker wrote:
> Hi,
> 
> while setting up a new system with qpsmtpd and LDAP I took rcpt_ok and
> auth/auth_ldap_bind, mixed it a bit and this is the result ;-) It uses
> the same config file as auth_ldap_bind... Currently it's not in
> production use, some tests have been done to see if it works like
> intended.

I attached my version of what you did, but it is not configurable like yours is.
 I did the same check for domain, but I did it against "locals", rather than
against "rcpthosts", as I do some secondary MX for people, so I wouldn't want to
reject users for their domains.  :)

> While writing I stumbled across the fact that I had to reuse most of the
> code from rcpt_ok (sub is_rcpthost()). Can this be moved to some
> Qpsmtpd::* module? This was not the first time I did something like
> this.

I asked a while ago (when I was writing this module) if we wanted some sort of
flag in the connection to say if it was local or not.  If you think that would
be cleaner, we might want to go ahead with that, writing one plugin that checks
to see if a domain is local/rcpthost and mark a flag (or a connection note) much
the same way that relay_client functions.  Would that do the trick?  in my
plugins, I run rcpt_ok after rcpt_verify_ldap.

> Elliot: mabye you can use the %r / %h / %u substitutions for the
> "configurable DN template" mentioned in the POD of your plugin? 

I will look into it, or you can modify my rcpt_verify plugin, if you wish.  I
need to clean up a few things about the auth_ldap plugin, and would like to make
the ldap queries asynchronous as well, to fit in better with the event model of
the new qpsmtpd code.

I may get around to this, but I'm busy with a new job at the moment, so...  Let
me know what you think or if you need help.  I may just sit down and hammer it 
out.

#!/usr/bin/perl -Tw

# make mattr an array?
# break out the local check into a different script, makes a notes("local")?
#   a lot of plugins check the rcpthosts/locals
# grab the user portion of address
# split the user portion by '-' or whatever the ext marker is
# look up mattr=portion
# how long should the sleeps be?

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

  # 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 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_timeout"} ||= 5;
  $self->{'ldconf'}->{'ldap_max_queries'} ||= 3;
  $self->{'ldconf'}->{'ldap_uid_attr'} ||= ['mail', 'mailalternateaddress'];
}

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

  my ($self, $transaction, $recipient) = @_;
  my ($ldhost, $ldport, $ldwait, $ldbase, @ldmattr, $ldmax, $local, $ldh, 
$sender);

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

  # don't bother if this is a relaying connection
  return ( DECLINED ) if ( $self->qp->connection->relay_client );

  my @hosts = ($self->qp->config("me"), $self->qp->config("locals"));
  
  $sender = $transaction->sender;
  if (($sender->user) && ($sender->host)) {
    $sender = $sender->user . '@' . $sender->host;
  } else {
    $sender = '<>';
  }

  # Allow 'no @' addresses for 'postmaster' and 'abuse'
  my $user = $recipient->user;
  my $host = $recipient->host;
  $self->log(LOGNOTICE, "rcpt user is postmaster or abuse, allowing" ) &&
    return ( DECLINED ) if ($host eq "" && (lc $user eq "postmaster" || lc 
$user eq "abuse"));
  
  # Check if this recipient host is allowed
  $local = '';
  for my $allowed (@hosts) {
    $allowed =~ s/^\s*(\S+)/$1/;
    $local = 'yes' if $host eq lc $allowed;
    $local = 'yes' if substr($allowed,0,1) eq "." and $host =~ 
m/\Q$allowed\E$/i;
  }
  # don't deny, because it might be a rcpthost even if it's not a local host
  return ( DECLINED ) unless ($local);

  # at this point we're sure it's a local domain, so we're authoritative from 
now on

  # pull values in from config
  $ldhost = $self->{'ldconf'}->{'ldap_host'};
  $ldport = $self->{'ldconf'}->{'ldap_port'};
  $ldwait = $self->{'ldconf'}->{'ldap_timeout'};
  $ldmax = $self->{'ldconf'}->{'ldap_max_queries'};
  @ldmattr = @{$self->{'ldconf'}->{'ldap_uid_attr'}};

  # should make the extdiv configurable
  my $extdiv = '-';
  my @local = split /$extdiv/, $user;
  my $counter = 1;

  # bind to directory server.  Support a sock file, please
  $ldh = Net::LDAP->new($ldhost, port=>$ldport, timeout=>$ldwait ) or
    $self->log(LOGERROR, "err in connecting to LDAP for \'$user\'" ) &&
      return ( DECLINED );

  # let's get the ball rolling, build the filter from the parts of local, then 
do the search
  $local= shift(@local);
  while ($counter <= $ldmax) {

    my $filter = "(|";
    foreach (@ldmattr) {
      $filter .= "(" . $_ . "=" . $local . ")";
    }
    $filter .= ")";

    # find the user's DN
    my $mesg = $ldh->search(
      base=>$ldbase,
      scope=>'sub',
      filter=>$filter,
      attrs=>['uid'],
      timeout=>$ldwait) or
        $self->log(LOGERROR, "err in search for \'$local\' while looking for 
\'$user\'" ) &&
          return ( DECLINED );

    # deal with errors if they exist, declining in case
    if ( $mesg->code ) {
      $ldh->unbind if ($ldh);
      $self->log(LOGERROR, "err in search for \'$local\' while looking for 
\'$user\'" ) &&
        return ( DECLINED );
    }

    # we found a match
    if ( $mesg->count eq '1') {
      my $entry = $mesg->entry;
      my $uid = $entry->get_value('uid');
      sleep 3;
      $self->qp->connection->notes('rcptuser', $uid ); # so that we can do 
per-user config
      $self->log(LOGNOTICE, "found \'$user\' as \'$uid\'" ) &&
        return ( DECLINED );
    }

    last unless (@local);
    $counter++;
    $local = join "$extdiv", $local, shift(@local);
  }

  $ldh->disconnect if ($ldh);

  # if the plugin couldn't find user's entry, then it is ok to DENY
  sleep 3;
  $self->log(LOGNOTICE, "could not find user \'[EMAIL PROTECTED]' for 
\'$sender\' at " . $self->qp->connection->remote_ip ) &&
    return ( DENY, "sorry, mailbox \'[EMAIL PROTECTED]' could not be found." );

}

Reply via email to