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." ); }