Hébergement virtuel multi domaine basé sur Mysql

1. Introduction

Après avoir déja rédigé une documentation sur la réalisation d’un serveur pour gérer au niveau dns, mail et ftp plusieurs domaines virtuels stockés dans une base LDAP, voici la même chose mais en se basant cette fois sur une base de données MySQL.

Prérequis :
Une Debian proprement installée avec juste ce qu’il faut (c’est à dire vim et ssh 🙂
Un serveur Mysql, personnellement je me base sur la version 5 qui sera éventuellement installée sur le même serveur. Je n’entrerai absolument pas dans le paramétrage du serveur Mysql ni dans la sécurisation des accès à celui-ci. Pour des raisons de performances dans la montée en charge, les tables seront toutes au format InnoDB, il faudra donc veiller à ce que le support InnoDB soit présent.

2. Serveur DNS Powerdns

2.1 Installation

Pour installer les paquets, rien de particulier sur une Debian :

# apt-get install pdns-server pdns-recursor pdns-backend-mysql

Structure de la base de données DNS gérant les noms de domaine :

CREATE TABLE IF NOT EXISTS `domains` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(255) NOT NULL,
  `master` varchar(20) default NULL,
  `last_check` int(11) default NULL,
  `type` varchar(6) NOT NULL,
  `notified_serial` int(11) default NULL,
  `account` varchar(40) default NULL,
  PRIMARY KEY  (`id`),
  UNIQUE KEY `name_index` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CREATE TABLE IF NOT EXISTS `records` (
  `id` int(11) NOT NULL auto_increment,
  `domain_id` int(11) default NULL,
  `name` varchar(255) default NULL,
  `type` varchar(6) default NULL,
  `content` varchar(255) default NULL,
  `ttl` int(11) default NULL,
  `prio` int(11) default NULL,
  `change_date` int(11) default NULL,
  PRIMARY KEY  (`id`),
  KEY `rec_name_index` (`name`),
  KEY `nametype_index` (`name`,`type`),
  KEY `domain_id` (`domain_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CREATE TABLE IF NOT EXISTS `supermasters` (
  `ip` varchar(25) NOT NULL,
  `nameserver` varchar(255) NOT NULL,
  `account` varchar(40) default NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

2.2 Paramétrage de PowerDNS

Fichier /etc/powerdns/pdns.conf :

allow-axfr-ips=
allow-recursion=127.0.0.1/8, /32
config-dir=/etc/powerdns
daemon=yes
disable-axfr=no
 disable-tcp=no
guardian=yes
lazy-recursion=no
local-address=
local-port=53
master=yes
module-dir=/usr/lib/powerdns
recursor=127.0.0.1:5353
setgid=pdns
setuid=pdns
slave=yes
socket-dir=/var/run
version-string=powerdns
include=/etc/powerdns/pdns.d
launch=gmysql
gmysql-host=127.0.0.1
gmysql-user=pdns
gmysql-password=secret
gmysql-dbname=DNS

Fichier /etc/powerdns/recursor.conf :

allow-from=127.0.0.0/8, /32
daemon=yes
delegation-only=com,net
local-address=127.0.0.1
local-port=5353
quiet=yes

2.3 Remplissage du DNS

On utilise une base SQL donc la création d’une zone ou la modification d’un enregistrement se fait en SQL c’est logique. Après, libre à vous de choisir la ligne de commande mysql, phpmyadmin, un script php fait la main… Voici quelques exemples. Le reste est classique, ça reste du DNS tout ce qu’il y a de plus habituel

Ajout d’une zone maitre :

INSERT INTO domains (name, type, notified_serial) VALUES ('morot.fr', 'MASTER', '2008050201')

Ajout de différent champs :

Le champs SOA :
INSERT INTO `records` (domain_id,name,type,content,ttl,prio,change_date) values (1,'morot.fr','SOA','ns1.morot.fr hostmaster.morot.fr 2008050201 10800 3600 604800 600','600','10',NOW());
Puis les serveurs de nom de cette zone :
INSERT INTO `records` (domain_id,name,type,content,ttl,prio,change_date) values (1,'morot.fr','NS','ns1.morot.fr','600','10',NOW());
INSERT INTO `records` (domain_id,name,type,content,ttl,prio,change_date) values (1,'morot.fr','NS','ns2.toto.org','600','10',NOW());
Bien entendu nous avons un serveur de mail :
INSERT INTO `records` (domain_id,name,type,content,ttl,prio,change_date) values (1,'morot.fr','MX','mail.morot.fr','600','10',NOW());
Deux champs A :
INSERT INTO `records` (domain_id,name,type,content,ttl,prio,change_date) values (1,'mail.morot.fr','A','192.168.69.1','600','10',NOW());
INSERT INTO `records` (domain_id,name,type,content,ttl,prio,change_date) values (1,'www.morot.fr','A','192.168.69.1','600','10',NOW());
Et un ALIAS :
(1,'ftp.morot.fr','CNAME','www.morot.fr', '600','10',NOW());

3. Serveur FTP Pure-ftpd

3.1 Installation

Installation :

# apt-get install pure-ftpd-common pure-ftpd-mysql

Structure de la base MySQL VFTP :

CREATE TABLE IF NOT EXISTS `ftpd` (
  `User` varchar(16) NOT NULL default '',
  `status` enum('0','1') NOT NULL default '0',
  `Password` varchar(64) NOT NULL default '',
  `Uid` varchar(11) NOT NULL default '-1',
  `Gid` varchar(11) NOT NULL default '-1',
  `Dir` varchar(128) NOT NULL default '',
  `ULBandwidth` smallint(5) NOT NULL default '0',
  `DLBandwidth` smallint(5) NOT NULL default '0',
  `comment` tinytext NOT NULL,
  `ipaccess` varchar(15) NOT NULL default '*',
  `QuotaSize` smallint(5) NOT NULL default '0',
  `QuotaFiles` int(11) NOT NULL default '0',
  PRIMARY KEY  (`User`),
  UNIQUE KEY `User` (`User`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Ajout d’un compte FTP :

INSERT INTO `ftpd` (`User`, `status`, `Password`, `Uid`, `Gid`, `Dir`, `ULBandwidth`, `DLBandwidth`, `comment`, `ipaccess`, `QuotaSize`, `QuotaFiles`) VALUES ('morot', '0', 'secret', '2001', '2001', '/home/www/morot.fr/', 500, 500, '', '*', 0, 0);

Création du compte virtuel :

addgroup --gid 2001 vftp
adduser --home /dev/null --shell /bin/false --no-create-home --uid 2001 --disabled-password --ingroup vftp --disabled-login vftp

3.2 Paramétrage

Fichier /etc/pure-ftpd/db/mysql.conf
:

MYSQLServer     127.0.0.1
MYSQLPort       3306
MYSQLSocket      /var/run/mysqld/mysqld.sock
MYSQLUser       pureftpd
MYSQLPassword   secret
MYSQLDatabase   VFTP
MYSQLCrypt      cleartext
MYSQLGetPW      SELECT Password FROM ftpd WHERE User="L"
MYSQLGetUID     SELECT Uid FROM ftpd WHERE User="L"
MYSQLGetGID     SELECT Gid FROM ftpd WHERE User="L"
MYSQLGetDir     SELECT Dir FROM ftpd WHERE User="L"
MySQLGetQTAFS  SELECT QuotaFiles FROM ftpd WHERE User="L"
MySQLGetQTASZ  SELECT QuotaSize FROM ftpd WHERE User="L"
MySQLGetBandwidthUL SELECT ULBandwidth FROM ftpd WHERE User="L"
MySQLGetBandwidthDL SELECT DLBandwidth FROM ftpd WHERE User="L"
MySQLTransactions On

Paramétrages supplémentaires :

echo "yes" > /etc/pure-ftpd/conf/ChrootEveryone
echo "yes" > /etc/pure-ftpd/conf/CreateHomeDir
echo "no" > /etc/pure-ftpd/conf/UnixAuthentication
echo "no" > /etc/pure-ftpd/conf/PAMAuthentication
echo "yes" > /etc/pure-ftpd/conf/NoAnonymous

4 Le serveur Mail

4.1Création des bases

Voici le schéma SQL sur lequel je me base. Les tables alias et forward sont redondates et je pourrais m’en passer. Cependant, j’apprécie assez d’avoir une distinction entre ce qui est redirigé sur une adresse virtuelle locale et ce qui est transmis à une adresse extérieure.

CREATE TABLE IF NOT EXISTS `alias` (
  `address` varchar(255) NOT NULL default '',
  `goto` text NOT NULL,
  `domain` varchar(255) NOT NULL default '',
  `created` datetime NOT NULL default '0000-00-00 00:00:00',
  `modified` datetime NOT NULL default '0000-00-00 00:00:00',
  `active` tinyint(1) NOT NULL default '1',
  PRIMARY KEY  (`address`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CREATE TABLE IF NOT EXISTS `domain` (
  `domain` varchar(255) NOT NULL default '',
  `description` varchar(255) NOT NULL default '',
  `aliases` int(10) NOT NULL default '0',
  `mailboxes` int(10) NOT NULL default '0',
  `maxquota` bigint(20) NOT NULL default '0',
  `quota` bigint(20) NOT NULL default '0',
  `transport` varchar(255) default NULL,
  `backupmx` tinyint(1) NOT NULL default '0',
  `created` datetime NOT NULL default '0000-00-00 00:00:00',
  `modified` datetime NOT NULL default '0000-00-00 00:00:00',
  `active` tinyint(1) NOT NULL default '1',
  PRIMARY KEY  (`domain`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CREATE TABLE IF NOT EXISTS `forward` (
  `address` varchar(255) NOT NULL default '',
  `goto` text NOT NULL,
  `domain` varchar(255) NOT NULL default '',
  `created` datetime NOT NULL default '0000-00-00 00:00:00',
  `modified` datetime NOT NULL default '0000-00-00 00:00:00',
  `active` tinyint(1) NOT NULL default '1',
  PRIMARY KEY  (`address`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CREATE TABLE IF NOT EXISTS `mailbox` (
  `username` varchar(255) NOT NULL default '',
  `password` varchar(255) NOT NULL default '',
  `name` varchar(255) NOT NULL default '',
  `maildir` varchar(255) NOT NULL default '',
  `quota` bigint(20) NOT NULL default '0',
  `antivirus` enum('0','1') NOT NULL default '1',
  `antispam` enum('0','1') NOT NULL default '1',
  `domain` varchar(255) NOT NULL default '',
  `created` datetime NOT NULL default '0000-00-00 00:00:00',
  `modified` datetime NOT NULL default '0000-00-00 00:00:00',
  `active` tinyint(1) NOT NULL default '1',
  PRIMARY KEY  (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

4.2 Création des comptes

Ajout d’un domaine :

INSERT INTO `domain` (`domain`, `description`, `aliases`, `mailboxes`, `maxquota`, `quota`, `transport`, `backupmx`, `created`, `modified`, `active`) VALUES
('domaine.com', 'Mon domaine à moi que j'ai', 0, 0, 0, 0, '', 0, '0000-00-00 00:00:00', '0000-00-00 00:00:00', 1);

Ajout d’un compte :

INSERT INTO `mailbox` (`username`, `password`, `name`, `maildir`, `quota`, `antivirus`, `antispam`, `domain`, `created`, `modified`, `active`) VALUES
('julien@domaine.com', 'secret', 'Julien MOROT', 'morot.fr/julien@morot.fr/', 512000, '1', '1', 'morot.fr', '0000-00-00 00:00:00', '0000-00-00 00:00:00', 1);

Ajout d’un alias :

INSERT INTO `alias` (`address`, `goto`, `domain`, `created`, `modified`, `active`) VALUES
('jaune@domaine.com', 'julien@domaine.com', 'domaine.com', '0000-00-00 00:00:00', '0000-00-00 00:00:00', 1);

5 Paramétrage du SMTP Postfix

L’essentiel du paramétrage consiste à indiquer à Postfix dans le fichier /etc/postfix/main.cf comment se connecter pour aller chercher les informations sur les comptes virtuels. Par la suite il faut créer le contenu des fichiers contenant la méthode utilisée pour récupérer les informations souhaitées.

virtual_uid_maps = static:2000
virtual_gid_maps = static:2000
virtual_transport = dovecot
virtual_mailbox_limit = 0
virtual_alias_maps = proxy:mysql:/etc/postfix/mysql/virtual_alias_maps.cf, proxy:mysql:/etc/postfix/mysql/virtual_forward_maps.cf
virtual_mailbox_maps = proxy:mysql:/etc/postfix/mysql/virtual_mailbox_maps.cf
virtual_mailbox_domains = proxy:mysql:/etc/postfix/mysql/virtual_domains_maps.cf
virtual_mailbox_base = /home/vmails
relay_domains = proxy:mysql:/etc/postfix/mysql/virtual_relay_domains_maps.cf
proxy_read_maps = $local_recipient_maps $mydestination $virtual_alias_maps
  $virtual_alias_domains $virtual_mailbox_maps $virtual_mailbox_domains
  $relay_recipient_maps $relay_domains $canonical_maps $sender_canonical_maps
  $recipient_canonical_maps $relocated_maps $transport_maps $mynetworks

Fichier /etc/postfix/mysql/virtual_alias_maps.cf

user = postfix
password = secret
dbname = VMAILS
query = SELECT goto FROM alias WHERE address='%s'
hosts = 127.0.0.1

Fichier /etc/postfix/mysql/virtual_domains_maps.cf

user = postfix
password = secret
dbname = VMAILS
query = SELECT domain FROM domain WHERE domain='%s' AND active=1
hosts = 127.0.0.1

Fichier /etc/postfix/mysql/virtual_forward_maps.cf

user = postfix
password = secret
dbname = VMAILS
query = SELECT goto FROM forward WHERE address='%s'
hosts = 127.0.0.1

Fichier /etc/postfix/mysql/virtual_mailbox_maps.cf

user = postfix
password = secret
dbname = VMAILS
query = SELECT CONCAT(maildir, '/Maildir/') FROM mailbox WHERE username='%s' AND active=1
hosts = 127.0.0.1

Fichier /etc/postfix/mysql/virtual_relay_domains_maps.cf

user = postfix
password = secret
dbname = VMAILS
query = SELECT domain FROM domain WHERE domain='%s' AND active=1 AND backupmx=1
hosts = 127.0.0.1

Pour certaines raisons (support de Sieve et des quotas, je souhaite utiliser le LDA de Dovecot, il faut donc paramétrer postfix pour qu’il sache comment gérer le paramètre virtual_transport définit précédemment. Il faut donc rajouter dans le fichier master.cf ceci :

dovecot   unix  -       n       n       -       -       pipe
  flags=DRhu user=vmails:vmails argv=/usr/lib/dovecot/deliver -f ${sender} -d ${recipient}

Création du compte virtuel :

addgroup --gid 2000 vmails
adduser --home /dev/null --shell /bin/false --no-create-home --uid 2000 --disabled-password --ingroup toto --disabled-login vmails

6 Paramétrage du serveur POP/IMAP Dovecot

Installation :

# apt-get install dovecot-common dovecot-imapd dovecot-pop3d

Fichier /etc/dovecot/dovecot.conf : Rien de spécial, on active simplement les modules et le support des protocoles que l’on souhaite que dovecot gère.

protocols = imap pop3 managesieve
disable_plaintext_auth = no
log_timestamp = "%Y-%m-%d %H:%M:%S "
mail_privileged_group = mail
mail_debug = yes
protocol imap {
  mail_plugins = quota imap_quota acl
  imap_client_workarounds = outlook-idle delay-newmail
}

protocol pop3 {
  pop3_uidl_format = %08Xu%08Xv
  mail_plugins = quota
  pop3_client_workarounds = outlook-no-nuls oe-ns-eoh
}
protocol lda {
  postmaster_address = postmaster@example.com
  mail_plugin_dir = /usr/lib/dovecot/modules/lda
  auth_socket_path = /var/run/dovecot/auth-master
  mail_plugins = cmusieve quota
}
auth_verbose = yes
auth_debug = yes
auth default {
  mechanisms = plain login
  passdb sql {
    args = /etc/dovecot/dovecot-sql.conf
  }
  userdb sql {
    args = /etc/dovecot/dovecot-sql.conf
  }
  user = root
  socket listen {
    master {
      path = /var/run/dovecot/auth-master
      mode = 0600
      user = vmails
      group = vmails
    }
    client {
      path = /var/run/dovecot/auth-client
      mode = 0660
    }
  }
}
dict {
}
plugin {
  quota = maildir
}
protocol managesieve {
    sieve=~/.dovecot.sieve
    sieve_storage=~/sieve
}

Fichier /etc/dovecot/dovecot-sql.conf : il un point particulier dans ce fichier. Comme je souhaite utiliser sieve pour traiter les messages via le LDA de dovecot, il faut que je retourne un répertoire personnel pour le compte mail d’où le home renvoyé par la user_query :

default_pass_scheme = PLAIN
password_query = SELECT password FROM mailbox WHERE username = '%u' AND active = 1
user_query = SELECT CONCAT('/home/vmails/', maildir) AS home, CONCAT('maildir:/home/vmails/', maildir, '/Maildir/') AS mail, CONCAT('maildir:storage=', quota) AS quota, 2000 AS uid, 2000 AS gid FROM mailbox WHERE username = '%u'

7 Authentification SMTP

Installation des paquets nécessaires :

# apt-get install sasl2-bin libsasl2-modules

Comme on se basera sur le démon saslauthd et que l’on utilisera PAM pour s’authentifier, il faut paramétrer ce démon comme il se doit. Tout se fait dans le fichier /etc/default/saslauthd
:

START=yes
MECHANISMS="pam"
MECH_OPTIONS=""
THREADS=5
OPTIONS="-c -m /var/spool/postfix/var/run/saslauthd -r"

Le paramètre -m est indispensable car comme postfix est chrooté par défaut sous Debian, il faut qu’il puisse communiquer avec saslauthd.

Donc comme je disais que saslauthd utilisera PAM pour s’authentifier, il faut bien un fichier /etc/pam.d/smtp proprement paramétré.

auth       required     pam_nologin.so
auth       required     pam_mysql.so user=postfix passwd=secret host=localhost db=VMAILS table=mailbox usercolumn=username passwdcolumn=password crypt=0
#auth       required     pam_unix.so
auth       required     pam_env.so # [1]

account       sufficient   pam_mysql.so user=postfix password=secret host=localhost db=VMAILS table=mailbox usercolumn=username passwdcolumn=password crypt=1
account    required     pam_unix.so

Le reste est des plus clasique pour toute personne ayant déja paramétré Postfix pour qu’il support SASL donc rien de plus à préciser.
Ajouter au fichier /etc/postfix/main.cf :

broken_sasl_auth_clients = yes
smtpd_recipient_restrictions =
 permit_mynetworks,
 permit_sasl_authenticated,
 reject_unauth_destination
smtpd_sasl_auth_enable = yes
smtpd_sasl_local_domain = $mydomain
smtpd_sasl_security_options = noanonymous

Et créer un fichier /etc/postfix/sasl/smtpd.conf contenant :

pwcheck_method: saslauthd auxprop
mech_list: login plain
auxprop_plugin: sql
sql_engine: mysql
sql_hostnames: localhost
sql_user: postfix
sql_database: VMAILS
sql_passwd: secret
sql_select: select password from mailbox where username = '%u@%r'

8 Conclusion

J’ai donné assez peu d’explications car au final l’essentiel du paramétrage se résume à indiquer où chercher les données dans la base SQL. L’important étant d’avoir un schéma de base de données propre et clair permettant de tout bien structurer.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.