* lib/Qpsmtpd/Auth.pm
Fix some totally egregious spelling errors * plugins/auth/auth_ldap_bind New plugin to authenticate against an LDAP database Thanks to Elliot Foster <elliotf@gratuitous.net> git-svn-id: https://svn.perl.org/qpsmtpd/trunk@404 958fd67b-6ff1-0310-b445-bb7760255be9
This commit is contained in:
parent
1be0263025
commit
58ded6369d
@ -8,8 +8,8 @@ Qpsmtpd::Auth - Authentication framework for qpsmtpd
|
|||||||
|
|
||||||
Provides support for SMTP AUTH within qpsmtpd transactions, see
|
Provides support for SMTP AUTH within qpsmtpd transactions, see
|
||||||
|
|
||||||
L<http://www.faqs.org/rfcs/rfc2222.html>
|
L<http://www.faqs.org/rfcs/rfc2222.html>
|
||||||
L<http://www.faqs.org/rfcs/rfc2554.html>
|
L<http://www.faqs.org/rfcs/rfc2554.html>
|
||||||
|
|
||||||
for more details.
|
for more details.
|
||||||
|
|
||||||
@ -106,23 +106,25 @@ of the following values (taken from Qpsmtpd::Constants):
|
|||||||
If the authentication has succeeded, the plugin can return this value and
|
If the authentication has succeeded, the plugin can return this value and
|
||||||
all subsequently registered hooks will be skipped.
|
all subsequently registered hooks will be skipped.
|
||||||
|
|
||||||
=item DECLINE
|
=item DECLINED
|
||||||
|
|
||||||
If the authentication has failed, but any additional plugins should be run,
|
If the authentication has failed, but any additional plugins should be run,
|
||||||
this value will be returned. If none of the registered plugins succeed, the
|
this value will be returned. If none of the registered plugins succeed, the
|
||||||
overall authentication will fail.
|
overall authentication will fail. Normally an auth plugin should return
|
||||||
|
this value for all cases which do not succeed (so that another auth plugin
|
||||||
|
can have a chance to authenticate the user).
|
||||||
|
|
||||||
=item DENY
|
=item DENY
|
||||||
|
|
||||||
If the authentication has failed, and the plugin wishes this to short circuit
|
If the authentication has failed, and the plugin wishes this to short circuit
|
||||||
any further testing, it should return this value. For example, a plugin could
|
any further testing, it should return this value. For example, a plugin could
|
||||||
register the L<auth-plain> hook and immediately fail any connection which is
|
register the L<auth-plain> hook and immediately fail any connection which is
|
||||||
not trusted (i.e. not in the same network).
|
not trusted (e.g. not in the same network).
|
||||||
|
|
||||||
Another reason to return DENY over DECLINE would be if the user name matched
|
Another reason to return DENY over DECLINED would be if the user name matched
|
||||||
an existing account but the password failed to match. This would make a
|
an existing account but the password failed to match. This would make a
|
||||||
dictionary-based attack much harder to accomplish. See the example authsql
|
dictionary-based attack much harder to accomplish. See the included
|
||||||
plugin for how this might be accomplished
|
auth_vpopmail_sql plugin for how this might be accomplished.
|
||||||
|
|
||||||
By returning DENY, no further authentication attempts will be made using the
|
By returning DENY, no further authentication attempts will be made using the
|
||||||
current method and data. A remote SMTP client is free to attempt a second
|
current method and data. A remote SMTP client is free to attempt a second
|
||||||
@ -138,9 +140,7 @@ and this will be appended to whatever response is sent to the remote SMTP
|
|||||||
client. There is no guarantee that the end user will see this information,
|
client. There is no guarantee that the end user will see this information,
|
||||||
though, since some prominent MTA's (produced by M$oft) I<helpfully>
|
though, since some prominent MTA's (produced by M$oft) I<helpfully>
|
||||||
hide this information under the default configuration. This message will
|
hide this information under the default configuration. This message will
|
||||||
be logged locally, if appropriate based on the configured log level. If
|
be logged locally, if appropriate, based on the configured log level.
|
||||||
you are running multiple auth plugins, it is helpful to include at least
|
|
||||||
the plugin name in the returned message (for debugging purposes).
|
|
||||||
|
|
||||||
=head1 Auth Hooks
|
=head1 Auth Hooks
|
||||||
|
|
||||||
@ -153,7 +153,7 @@ The currently defined authentication methods are:
|
|||||||
Any plugin which registers an auth-plain hook will engage in a plaintext
|
Any plugin which registers an auth-plain hook will engage in a plaintext
|
||||||
prompted negotiation. This is the least secure authentication method since
|
prompted negotiation. This is the least secure authentication method since
|
||||||
both the user name and password are visible in plaintext. Most SMTP clients
|
both the user name and password are visible in plaintext. Most SMTP clients
|
||||||
will preferentially chose a more secure method if it is advertised by the
|
will preferentially choose a more secure method if it is advertised by the
|
||||||
server.
|
server.
|
||||||
|
|
||||||
=item * auth-login
|
=item * auth-login
|
||||||
|
192
plugins/auth/auth_ldap_bind
Normal file
192
plugins/auth/auth_ldap_bind
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
#!/usr/bin/perl -Tw
|
||||||
|
|
||||||
|
sub register {
|
||||||
|
my ( $self, $qp, @args ) = @_;
|
||||||
|
$self->register_hook( "auth-plain", "authldap" );
|
||||||
|
$self->register_hook( "auth-login", "authldap" );
|
||||||
|
|
||||||
|
# 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_timeout"} ||= 5;
|
||||||
|
$self->{"ldconf"}->{"ldap_auth_filter_attr"} ||= "uid";
|
||||||
|
}
|
||||||
|
|
||||||
|
sub authldap {
|
||||||
|
use Net::LDAP qw(:all);
|
||||||
|
use Qpsmtpd::Constants;
|
||||||
|
|
||||||
|
my ( $self, $transaction, $method, $user, $passClear, $passHash, $ticket ) =
|
||||||
|
@_;
|
||||||
|
my ($ldhost, $ldport, $ldwait, $ldbase, $ldmattr, $lduserdn, $ldh, $mesg);
|
||||||
|
|
||||||
|
# pull values in from config
|
||||||
|
$ldhost = $self->{"ldconf"}->{"ldap_host"};
|
||||||
|
$ldport = $self->{"ldconf"}->{"ldap_port"};
|
||||||
|
$ldbase = $self->{"ldconf"}->{"ldap_base"};
|
||||||
|
|
||||||
|
# log error here and DECLINE if no baseDN, because a custom baseDN is required:
|
||||||
|
unless ($ldbase) {
|
||||||
|
$self->log(LOGERROR, "authldap/$method - please configure ldap_base" ) &&
|
||||||
|
return ( DECLINED, "authldap/$method - temporary auth error" );
|
||||||
|
}
|
||||||
|
$ldwait = $self->{"ldconf"}->{'ldap_timeout'};
|
||||||
|
$ldmattr = $self->{"ldconf"}->{'ldap_auth_filter_attr'};
|
||||||
|
|
||||||
|
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 ( DECLINE, "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 ( DECLINE, "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 ( DECLINE, "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 (($mesg->count) && ($lduserdn = $mesg->entry->dn)) {
|
||||||
|
$ldh = Net::LDAP->new($ldhost, port=>$ldport, timeout=>$ldwait ) or
|
||||||
|
$self->log(LOGALERT, "authldap/$method - err in user conn" ) &&
|
||||||
|
return ( DECLINE, "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" );
|
||||||
|
}
|
||||||
|
|
||||||
|
# if the plugin couldn't find user's entry
|
||||||
|
} else {
|
||||||
|
$self->log(LOGALERT, "authldap/$method - user not found" ) &&
|
||||||
|
return ( DECLINE, "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
|
||||||
|
|
||||||
|
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_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 <elliotf@gratuitous.net>
|
||||||
|
|
||||||
|
=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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user