Elliot Foster wrote:
Bob Dodds wrote:
"domain is local/rcpthost" recipient domain
You would have to make the note a ref to a qpsmtpd
object which manages a hash of recipient addresses
as keys and 0 or 1 as values. $transaction->recipients
has to return an array, but it could internally use a
hash and then another method $transaction->local
to be used to set the values to 0 or 1 for each
recipient. What would the default be? Tristate?
Or let people set any number they like to handle
more than two routing possibilities.
You're right, I was thinking of just setting a flag in the $connection, but it
would be necessary to have one for each recipient.
Right now there ain't no such hash. We just see the
array of recipients and I don't know of any survivable
global memory method other than notes. We could
kludge one doing like SRS and aliasing the address
like: $rcpt->address( $address . $cruft )
or $cruft . $address and then strip off the $cruft in
queue. The trick is to think up a presumptuous
enough name and acronym and pompously document
it in thirty pages and set up a blog and schedule a
convention in Las Vega, then $cruft will be better than
$cruftless and Microsoft will buy it out.
Or make note a string-stored hash and keep adding
to it for each recipient, address as key, followed by
value, followed by next key, then next value. Again,
pretty crufty, but that would work, too.
I just do another ldap lookup for each recipient in
my queue plugin to see if recipients are local.
I'd prefer to reject in the SMTP conversation, so I don't have to worry about
double bounces, and so I don't flood any people getting joe job'ed. I dislike
the idea of silently dropping it, too.
I can verify or reject the bad recipients in rcpt via ldap,
good/bad relay senders in auth via ldap, but at queue I
have to do some of the same checking to figure out whether
it's a local or relay or for listserver or dspam retrain.
Rather than use notes, I just do some of the checking
over again in queue and then do some other checks
to route. There can be locals and relays in the same
multiple recipients $transaction->recipients array,
so one note won't do it.
Where would such a value be used? Queue? There
are more than two routing possibilities in some
queues.
I was thinking of it as a way of telling the server that it's "authoritative for
this domain" or not, so that it can reject mail from/to that domain. That would
be an example of a domain in "locals" whereas I'm most likely doing routing or
secondary for domains in "rcpthosts", so I don't want to look for local users
for that domain.
You definitely can act as authoritative, and give
the appropriate message in your OK or DENY.
Also you can keep them hanging on the line for
an assurance delivery occured. I have qpsmtpd
doing lmtp through dspam then lmtp to cyrus,
and qpsmtpd is on the line until cyrus and
dspam both sign off on the delivery as completed.
Not so tight with relays, though, as qpsmtpd
hands off to qmail, qmail spools it to a file, but
it was just a mere user on the line there so
who cares what we tell them, if it bounces,
they'll get it back.
-Bob
Elliot Foster wrote:
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." );
}