Dovecot : restriction de l’authentification utilisateur

Boris HUISGEN June 2, 2013

administration messagerie dovecot postfix postfixadmin mysql thunderbird

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>

See also

Postfix : gérer les extensions d’adresses avec Dovecot en LDA
Read more
MySQL : installation multi-instances
Read more
Postfix : réécriture des entêtes mail
Read more