Dovecot : restriction de l'authentification utilisateur

Boris HUISGEN
Boris HUISGEN
|

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>
Boris HUISGEN
Boris HUISGEN
Blog owner
  • #dovecot