Dovecot : restriction de l'authentification utilisateur

Je vous présente ici la solution que j’ai retenu pour restreindre les possibilités d’authentification de Dovecot selon le protocole utilisé (POP/IMAP/SMTP/Sieve). La gestion des comptes de messagerie étant effectué en SQL avec PostfixAdmin, le schéma a été repris tel quel ; seule une table a été ajoutée. L’authentification SMTP est déportée de Postfix vers Dovecot (configuration LMTP).
Configuration SQL
La table dédiée à l’authentification est la suivante :
CREATE TABLE IF NOT EXISTS `auth` (
`username` varchar(255) NOT NULL,
`allow_smtp` tinyint(1) NOT NULL DEFAULT '1',
`smtp_nets` varchar(255) NOT NULL DEFAULT '::1,127.0.0.1,0::0/0,0.0.0.0/0',
`allow_imap` tinyint(1) NOT NULL DEFAULT '1',
`imap_nets` varchar(255) NOT NULL DEFAULT '::1,127.0.0.1',
`allow_pop3` tinyint(1) NOT NULL DEFAULT '1',
`pop3_nets` varchar(255) NOT NULL DEFAULT '::1,127.0.0.1,0::0/0,0.0.0.0/0',
`allow_sieve` tinyint(1) NOT NULL DEFAULT '1',
`sieve_nets` varchar(255) NOT NULL DEFAULT '::1,127.0.0.1',
PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Elle contiendra donc l’ensemble des comptes utilisateurs (username) avec les possibilités de chacun. Les valeurs par défaut autorisent ici :
- la connexion SMTP quelque soit l’IP source (IPv4 et IPv6)
- la connexion IMAP uniquement par IP locale (par webmail)
- la connexion POP3 quelque soit l’IP source
- la gestion des filtres Sieve uniquement en local (par webmail)
Configuration de Dovecot
Les requêtes SQL sont à modifier de cette façon :
user_query = \
SELECT \
CONCAT('/var/spool/vmail/', maildir) AS home, \
CONCAT('maildir:/var/spool/vmail/', maildir) AS mail, \
999 AS uid, \
999 AS gid, \
'dict:User quota::proxy::quota' AS quota, \
CONCAT('*:storage=',FLOOR(quota/1000)) AS quota_rule, \
'Trash:storage=+100M' AS quota_rule2 \
FROM \
mailbox \
WHERE \
username='%u' AND domain='%d' AND active='1'
password_query = \
SELECT \
mailbox.username AS user, \
mailbox.domain AS domain, \
password, \
CONCAT('/var/spool/vmail/', maildir) AS userdb_home, \
CONCAT('maildir:/var/spool/vmail/', maildir) AS userdb_mail, \
999 AS userdb_uid, \
999 AS userdb_gid, \
'dict:User quota::proxy::quota' AS userdb_quota, \
CONCAT('*:storage=',FLOOR(quota/1000)) AS userdb_quota_rule, \
'Trash:storage=+100M' AS userdb_quota_rule2, \
CASE '%s' \
WHEN 'smtp' THEN smtp_nets \
WHEN 'imap' THEN imap_nets \
WHEN 'pop3' THEN pop3_nets \
WHEN 'sieve' THEN sieve_nets \
END AS allow_nets \
FROM \
mailbox, auth \
WHERE \
mailbox.username=auth.username AND \
mailbox.username='%u' AND domain='%d' AND active='1' AND \
( \
('%s'='smtp' AND allow_smtp='1') \
OR \
('%s'='imap' AND allow_imap='1') \
OR \
('%s'='pop3' AND allow_pop3='1') \
OR \
('%s'='sieve' AND allow_sieve='1') \
OR \
'%s'='doveadm' \
)
iterate_query = \
SELECT \
username AS user \
FROM \
mailbox \
WHERE \
active='1'
Scripts complémentaires pour une gestion avec Postfixadmin
Du côté de PostfixAdmin, l’ajout et la suppresion de comptes déclenchent les scripts de modification des enregistrements dans la table auth :
root@mail:~# more mailbox-postcreation.d/10-auth
#!/usr/bin/perl
my $sql_host = 'localhost'; # SQL server hostname
my $sql_port = '3306'; # SQL server port
my $sql_database = 'postfix'; # SQL database name
my $sql_user = 'postfixadmin'; # SQL user
my $sql_password = 'blablablaaa'; # SQL password
use strict;
use warnings;
use DBI;
use DBD::mysql;
my ($dsn, $dbh, $qh, $mailbox);
$mailbox = $ARGV[0];
if (!defined($mailbox) || ($mailbox eq '')) {
exit 1
}
$dsn = "dbi:mysql:$sql_database:$sql_host:$sql_port";
$dbh = DBI->connect($dsn, $sql_user, $sql_password)
or die "failed to connect to database: $DBI::errstr\n";
$qh = $dbh->prepare("INSERT INTO auth (username) VALUES ('$mailbox')");
$qh->execute();
root@mail:~# more /root/scripts/postfixadmin/mailbox-postdeletion.d/10-auth
#!/usr/bin/perl
my $sql_host = 'localhost'; # SQL server hostname
my $sql_port = '3306'; # SQL server port
my $sql_database = 'postfix'; # SQL database name
my $sql_user = 'postfixadmin'; # SQL user
my $sql_password = 'blablablaaa'; # SQL password
use strict;
use warnings;
use DBI;
use DBD::mysql;
my ($dsn, $dbh, $qh, $mailbox);
$mailbox = $ARGV[0];
if (!defined($mailbox) || ($mailbox eq '')) {
exit 1
}
$dsn = "dbi:mysql:$sql_database:$sql_host:$sql_port";
$dbh = DBI->connect($dsn, $sql_user, $sql_password)
or die "failed to connect to database: $DBI::errstr\n";
$qh = $dbh->prepare("DELETE FROM auth WHERE username='$mailbox'");
$qh->execute();
Script pour autoconfiguration avec Thunderbird
Je vous renvoie sur mon article présentant l’autoconfiguration d’un compte sous Thunderbird. L’intérêt du script PHP est de récupérer les paramètres adaptés/autorisés pour l’utilisateur concerné.
<?php
/*
* autoconf.php
*
* Boris HUISGEN <bhuisgen@hbis.fr>
*/
define("SYSLOG_IDENT", "autoconfig");
define("SYSLOG_OPTIONS", LOG_PID|LOG_NDELAY);
define("SYSLOG_LOCAL", LOG_USER);
define("DB_DSN", "mysql:host=localhost;dbname=postfix");
define("DB_USER", "autoconfig");
define("DB_PASSWORD", "blablablaaa");
define("REGEX_ALLOWNETS", "/^(.*,)?(0::0\/0|0\.0\.0\.0\/0)(,.*)$/");
// script
openlog(SYSLOG_IDENT, SYSLOG_OPTIONS, SYSLOG_LOCAL);
if (getenv("HTTP_CLIENT_IP"))
$ip = getenv("HTTP_CLIENT_IP");
else if(getenv("HTTP_X_FORWARDED_FOR"))
$ip = getenv("HTTP_X_FORWARDED_FOR");
else if(getenv("REMOTE_ADDR"))
$ip = getenv("REMOTE_ADDR");
else $ip = "unknown IP";
if (!isset($_GET["emailaddress" ]))
{
syslog(LOG_ERR, "[client=$ip] failed to get email address, aborting execution");
exit(1);
}
$username = $_GET["emailaddress" ];
{
$c = new PDO(DB_DSN, DB_USER, DB_PASSWORD);
$c->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$s = $c->prepare("SELECT allow_smtp, smtp_nets, allow_pop3, pop3_nets, " .
"allow_imap, imap_nets FROM auth WHERE username = ?");
$s->bindColumn(1, $allow_smtp, PDO::PARAM_INT);
$s->bindColumn(2, $smtp_nets, PDO::PARAM_STR, 255);
$s->bindColumn(3, $allow_pop3, PDO::PARAM_INT);
$s->bindColumn(4, $pop3_nets, PDO::PARAM_STR, 255);
$s->bindColumn(5, $allow_imap, PDO::PARAM_INT);
$s->bindColumn(6, $imap_nets, PDO::PARAM_STR, 255);
$s->bindParam(1, $username);
$s->execute();
if ($s->rowCount() != 1)
{
syslog(LOG_ERR, "[client=$ip] $username not found");
exit(3);
}
$s->fetch(PDO::FETCH_BOUND);
}
catch (PDOException $e)
{
syslog(LOG_ERR, "PDO exception, aborting execution");
exit(2);
}
syslog(LOG_NOTICE, "[client=$ip] $username configured");
header("Content-type: text/xml; charset=utf-8");
?>
<?xml version="1.0" encoding="UTF-8"?>
<clientConfig version="1.1">
<emailProvider id="domaine.fr">
<domain>domaine.fr</domain>
<displayName>Test</displayName>
<displayShortName>Test</displayShortName>
<?php
if (($allow_imap == 1) &&
(preg_match(REGEX_ALLOWNETS, $imap_nets) == 1))
{
?>
<incomingServer type="imap">
<hostname>mail.domaine.fr</hostname>
<port>143</port>
<socketType>STARTTLS</socketType>
<username>%EMAILADDRESS%</username>
<authentication>password-cleartext</authentication>
</incomingServer>
<?php
}
if (($allow_pop3 == 1) &&
(preg_match(REGEX_ALLOWNETS, $pop3_nets) == 1))
{
?>
<incomingServer type="pop3">
<hostname>mail.domaine.fr</hostname>
<port>995</port>
<socketType>SSL</socketType>
<authentication>password-cleartext</authentication>
<username>%EMAILADDRESS%</username>
</incomingServer>
<?php
}
if (($allow_smtp == 1) &&
(preg_match(REGEX_ALLOWNETS, $smtp_nets) == 1))
{
?>
<outgoingServer type="smtp">
<hostname>noreply.domaine.fr</hostname>
<port>587</port>
<socketType>STARTTLS</socketType>
<username>%EMAILADDRESS%</username>
</outgoingServer>
<?php
}
?>
</emailProvider>
</clientConfig>