poste.io

complete mail server built in docker
Open source plugins for QPSMTPD:

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;
}