- Dovecot quota - check mailbox free space through Dovecot api before accepting mail
- Dovecot auth - authenticate through Dovecot api
- RCPT Database - *EXAMPLE* plugin to check if recipient is in database
- Remove headers - remove headers from outgoing emails due privacy or redundancy
Dovecot auth
Download
#!perl -w
=head1 NAME
auth_dovecot
=head1 DESCRIPTION
This plugin authenticate against dovecot socket (/var/run/dovecot/auth-smtp)
=head1 CONFIGURATION
You need enable socket at /etc/dovecot/conf.d/10-master.conf with something like this:
service auth {
unix_listener auth-smtp {
mode = 0666
}
}
=head1 AUTHOR
Copyright (c) 2015 Stanislav Humplik (stanislav@analogic.cz)
http://poste.io
This plugin is free software; you can distribute it and/or modify it
under the terms of the General Public License v3.
=cut
use strict;
use warnings;
use Qpsmtpd::Auth;
use Qpsmtpd::Constants;
use IO::Socket::UNIX;
use IO::Select;
use MIME::Base64 qw(encode_base64);
sub register {
my ($self, $qp, %args) = @_;
$self->register_hook("auth-plain", "dovecot_auth_plain");
$self->register_hook("auth-login", "dovecot_auth_plain");
$self->{_dovecot_auth_socket} = $args{socket} || '/var/run/dovecot/auth-smtp';
$self->{_dovecot_auth_timeout} = $args{timeout} || 5;
}
sub dovecot_auth_plain {
my ($self, $transaction, $method, $user, $passClear, $passHash, $ticket) =
@_;
if ($user =~ /\x00/) {
$self->log(LOGERROR, "deny: invalid username");
return (DENY, "dauth, invalid username");
};
utf8::encode($user);
utf8::encode($passClear);
my $login = $user;
my $passwd = $passClear;
my $service = 'smtp';
my $timeout = $self->{_dovecot_auth_timeout};
my $socket = $self->{_dovecot_auth_socket};
my $sock = new IO::Socket::UNIX(Type => SOCK_STREAM, Peer => $socket);
if(!$sock) {
$self->log(LOGERROR, "Can't open socket. Check dovecot is running and $socket is readable.");
return (DENYSOFT, "dauth, internal problem");
}
my $handshake = read_until($sock, '^DONE$', $timeout);
if($handshake !~ /^VERSION\t1\t\d+$/m) {
$self->log(LOGERROR, "Unsupported protocol version");
return (DENYSOFT, "dauth, internal problem");
}
if($handshake !~ /^MECH\tPLAIN/m) {
$self->log(LOGERROR, "PLAIN mechanism is not supported by the authentication daemon");
return (DENYSOFT, "dauth, internal problem");
}
my $base64 = encode_base64("\0$login\0$passwd");
my $message = "VERSION\t1\t0\nCPID\t$$\nAUTH\t1\tPLAIN\tservice=$service\tresp=$base64";
if(!$sock->send($message)) {
$self->log(LOGERROR, "Can't write to socket. Check dovecot is running and $socket is writable.");
return (DENYSOFT, "dauth, internal problem");
}
my $result = read_until($sock, '\n', $timeout);
$sock->close;
if ($result =~ /^OK/) {
$self->log(LOGINFO, "pass: authentication for: $user");
$self->qp->connection->notes('logging-session-auth', $user);
return (OK, "auth success for $user");
} else {
$self->log(LOGINFO, "fail: authentication failure for: $user");
return (DENY, 'auth failure (100)');
}
}
sub read_until {
my ($sock, $re, $timeout) = @_;
my $sel = new IO::Select($sock);
my $result = '';
while ($result !~ /$re/m) {
$sel->can_read($timeout) or die "Timed out while waiting for response";
defined recv($sock, my $buf, 256, 0) or die 'Error while reading response';
$result .= $buf;
}
return $result;
}