#!/bin/bash
# This file is part of Take It Easy Linux.
# Take It Easy Linux is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# Take It Easy Linux is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with Take It Easy Linux. If not, see .
# Copyright © 2019 Ludovic Pouzenc
# Fonctions utilitaires pour tracer, installer, télécharger, configurer
t() {
echo -n "[ ] ${BASH_LINENO[0]} $*"
out=$($* 2>&1)
if [ $? -eq 0 ]
then echo -e "\r[\e[32m OK \e[0m]"
else echo -e "\r[\e[1m\e[31mFAILED\e[0m]"
echo "$out"
fi
}
inst() {
touch /tmp/installed
if grep -q -- "$*" /tmp/installed
then echo 'Already installed'
else t apt-get install --quiet --yes $* && echo $* >> /tmp/installed
fi
}
get() {
filename=$1
baseurl=$2
if [ -f /tmp/$filename ]
then echo 'Already downloaded'
else t wget -O /tmp/$filename $baseurl/$filename
fi
}
link() {
target=$1
linkpath=$2
[ -L $linkpath ] || ln -sf $target $linkpath
}
confwrite() {
file=$1
[ -s $file ] && cp -a $file $file.tmp
cat > $file
if [ -f $file.tmp ]
then if diff -q $file $file.tmp
then rm $file.tmp
else mv $file.tmp $file.orig
fi
fi
}
confset() {
file=$1
conf_keyword=$2
conf_valuematch=$3
conf_newvalue=$4
sed --in-place -e 's#^\(\s*\)\#\?\(\s*'$conf_keyword'\s*[ =]\+\s*\)'$conf_valuematch'\(.*\)$#\1\2'$conf_newvalue'\3#' $file
}
confenable() {
file=$1
conf_keyword=$2
sed --in-place -e 's#^\(\s*\)\#\?\(\s*'$conf_keyword'\s*[ =]\+\s*'$conf_valuematch'.*\)$#\1\2#' $file
}
confappend() {
file=$1
shift
line=$*
grep -q -- "$line" $file || echo $line >> $file
}
# Vérifications d'environnement
if [ $UID -ne 0 ]
then echo "Ce script est prévu pour être lancé par root uniquement"
echo "Utilisez 'su -' ou 'sudo -i' pour obtenir un shell root"
exit 1
fi
# Paramètres détéectés et utilisés par la suite du script
set -x
host=$(hostname)
domain=$(dnsdomainname)
lang=${LANG%.*}
set +x
echo 'Saisissez un mot de passe admin pour le webmail et rspamd ou ctrl+C pour annuler'
read -rsp 'Password: ' adminpass
echo
# Ajout des dépôts nécessaires et les clés GPG pour valider les paquets reçus
t confwrite /etc/apt/sources.list.d/backports.list <<"EOT"
deb http://ftp.debian.org/debian stretch-backports main
EOT
t confwrite /etc/apt/sources.list.d/rspamd.list <<"EOT"
deb [arch=amd64] http://rspamd.com/apt-stable/ stretch main
EOT
t get gpg.key https://rspamd.com/apt-stable
t apt-key add /tmp/gpg.key
# Prise en compte des nouveaux dépots
t apt-get update
# Préremplissage des questions posées à l'installation de certains paquets
t debconf-set-selections <<"EOT"
exim4-config exim4/dc_eximconfig_configtype select internet site; mail is sent and received directly using SMTP
exim4-config exim4/use_split_config boolean true
EOT
# Installation des paquets nécessaires
# Obtention de certificat TLS gratuits
t inst -t stretch-backports python-certbot-apache
# Redis utilisé par rainloop et rspamd (et la version 3 buggue avec rspam, "127.0.0.1: connection refusée")
t inst -t stretch-backports redis-server
# Programmes pour traiter et stocker les mails
t inst dovecot-lmtpd dovecot-imapd dovecot-sieve dovecot-managesieved exim4-daemon-heavy rspamd clamav-daemon
# Prérequis webmail RainLoop
t inst libapache2-mod-php php-curl php-iconv php-sqlite3 php-xml unzip
# Système, sécurité, monitoring
t inst apachetop fail2ban munin-node liblwp-useragent-determined-perl
read -t10 -p 'Installation de paquets terminée, configuration dans 10 secondes (ctrl+C pour annuler)' garbage
echo
# Configuration munin-node
rm /etc/munin/plugins/ntp_kernel_* 2>/dev/null
t link /usr/share/munin/plugins/netstat /etc/munin/plugins/
t link /usr/share/munin/plugins/apache_accesses /etc/munin/plugins/
t link /usr/share/munin/plugins/apache_processes /etc/munin/plugins/
t link /usr/share/munin/plugins/apache_volume /etc/munin/plugins/
t service munin-node restart
# Configuration redis (base de données mémoire simple pour webmail et rspamd)
t confset /etc/redis/redis.conf maxmemory '' 500mb
t confset /etc/redis/redis.conf maxmemory-policy 'noeviction' volatile-ttl
t confwrite /etc/sysctl.d/50-redis-overcommit_memory.conf <<"EOT"
vm.overcommit_memory = 1
EOT
echo 1 > /proc/sys/vm/overcommit_memory
t service redis-server restart
# Configuration apache2 (serveur web)
t confset /etc/apache2/mods-enabled/mpm_prefork.conf MaxRequestWorkers '[0-9]\+' 20
t confwrite /etc/apache2/sites-available/000-default.conf <
ServerAdmin postmaster@$domain
DocumentRoot /var/www/html
ErrorLog /var/log/apache2/error.log
CustomLog /var/log/apache2/access.log combined
# Gère les redirections depuis http vers https sur le nom canonique
RewriteEngine on
RewriteCond %{REQUEST_URI} !^/.well-known/acme-challenge/
RewriteRule ^/(.*) https://$host.$domain/\$1 [L,R=301]
# Accepte les requêtes challenge LetsEncrypt
Options None
AllowOverride None
Require all granted
# Ne pas publier les données du webmail (si mod_rewrite n'est pas actif)
Options None
AllowOverride None
Require all denied
EOT
t confwrite /etc/apache2/sites-available/default-ssl.conf <
ServerAdmin postmaster@$domain
ServerName $host.$domain
DocumentRoot /var/www/html
ErrorLog /var/log/apache2/error.log
CustomLog /var/log/apache2/access.log combined
Options None
AllowOverride None
Require all granted
# Ne pas publier les données du webmail
Options None
AllowOverride None
Require all denied
# Renvoi par défaut sur le webmail et (spam,rspamd) -> rspamd/
RewriteEngine on
RewriteRule ^/$ https://$host.$domain/webmail/ [L,R=302]
RewriteRule ^/spam$ https://$host.$domain/rspamd/ [L,R=302]
RewriteRule ^/rspamd$ https://$host.$domain/rspamd/ [L,R=302]
# Exposer l'interface de rspamd en https
ProxyPass /rspamd/ http://127.0.0.1:11334/
ProxyPassReverse /rspamd/ http://127.0.0.1:11334/
ProxyRequests Off
# Paramètres TLS de LetsEncrypt
Include /etc/letsencrypt/options-ssl-apache.conf
SSLCertificateFile /etc/letsencrypt/live/$domain/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/$domain/privkey.pem
EOT
t a2enmod proxy_http rewrite ssl
t a2ensite default-ssl
t confset /etc/php/7.0/apache2/php.ini upload_max_filesize '[0-9M]\+' 26M
t confset /etc/php/7.0/apache2/php.ini post_max_size '[0-9M]\+' 30M
# Pré-configuration certificats TLS (initialement : auto-signés, mais prêt pour LetsEncrypt)
t mkdir -p /etc/letsencrypt/live/$domain/
t cp -a /usr/lib/python3/dist-packages/certbot_apache/options-ssl-apache.conf /etc/letsencrypt/
t link /etc/ssl/certs/ssl-cert-snakeoil.pem /etc/letsencrypt/live/$domain/fullchain.pem
t link /etc/ssl/private/ssl-cert-snakeoil.key /etc/letsencrypt/live/$domain/privkey.pem
t service apache2 restart
# Déploiement webmail RainLoop
t get rainloop-community-latest.zip https://www.rainloop.net/repository/webmail
t mkdir -p /var/www/html/webmail
t unzip -qnd /var/www/html/webmail /tmp/rainloop-community-latest.zip
t chown -R root: /var/www/html/webmail
t mkdir -p /var/www/html/webmail/data/_data_/_default_/logs/fail2ban/
t touch /var/www/html/webmail/data/_data_/_default_/logs/fail2ban/auth-empty.txt
# Configuration webmail Rainloop
t php -- "$domain" "$lang" "$adminpass" "(autoconfig webmail)" <<"EOT"
Set('webmail', 'title', 'Webmail ' . $argv[1]);
$oConfig->Set('webmail', 'language', $argv[2]);
$oConfig->Set('webmail', 'attachment_size_limit', 25);
$oConfig->Set('contacts', 'enable', 'On');
$oConfig->SetPassword($argv[3]);
$oConfig->Set('login', 'default_domain', $argv[1]);
$oConfig->Set('logs', 'enable', 'On');
$oConfig->Set('logs', 'write_on_error_only', 'On');
$oConfig->Set('logs', 'filename', 'syslog');
$oConfig->Set('logs', 'auth_logging', 'On');
$oConfig->Set('cache', 'fast_cache_driver', 'redis');
echo 'Config ' . ( $oConfig->Save() ? 'Saved' : 'Error') . "\n";
?>
EOT
t confwrite /var/www/html/webmail/data/_data_/_default_/domains/$domain.ini < user=.* host=.* port=.*$
ignoreregex =
EOT
t confwrite /etc/fail2ban/jail.local <<"EOT"
[rainloop]
enabled = true
banaction = iptables-multiport
filter = rainloop
port = http,https
logpath = /var/www/html/webmail/data/_data_/_default_/logs/fail2ban/auth-*.txt
maxretry = 5
findtime = 1500
bantime = 900
EOT
t service fail2ban restart
# Configuration Dovecot (serveur IMAP de consultation mail, filtres sieve)
t confset /etc/dovecot/dovecot.conf verbose_proctitle '[a-z]\+' yes
t confset /etc/dovecot/conf.d/10-auth.conf disable_plaintext_auth '[a-z]\+' yes
t confset /etc/dovecot/conf.d/10-auth.conf auth_username_format '[%A-Za-z]\+' '%Ln'
t confenable /etc/dovecot/conf.d/10-logging.conf mail_log_events '\S\+'
t confenable /etc/dovecot/conf.d/10-logging.conf mail_log_fields '\S\+'
t confset /etc/dovecot/conf.d/10-mail.conf mail_location '\S\+' 'mdbox:~/mail'
t confset /etc/dovecot/conf.d/10-ssl.conf ssl 'no' required
t confset /etc/dovecot/conf.d/10-ssl.conf ssl_cert '[a-z/<.]\+' " {$max_received_linelength}{998}}
.endif
# Deny unless the address list headers are syntactically correct.
#
# If you enable this, you might reject legitimate mail.
.ifdef CHECK_DATA_VERIFY_HEADER_SYNTAX
deny
message = Message headers fail syntax check
!acl = acl_local_deny_exceptions
!verify = header_syntax
.endif
# require that there is a verifiable sender address in at least
# one of the "Sender:", "Reply-To:", or "From:" header lines.
.ifdef CHECK_DATA_VERIFY_HEADER_SENDER
deny
message = No verifiable sender address in message headers
!acl = acl_local_deny_exceptions
!verify = header_sender
.endif
# Deny if the message contains malware. Before enabling this check, you
# must install a virus scanner and set the av_scanner option in the
# main configuration.
#
# exim4-daemon-heavy must be used for this section to work.
#
# deny
# malware = *
# message = This message was detected as possible malware ($malware_name).
# Actually scan message agaist spam (and malware trough rspamd)
warn
logwrite = spam-check: starting
spam = Debian-exim:true
logwrite = spam-check: ending
# Defer message if spam scanner is not available
defer
condition = ${if eq{$spam_action}{}}
message = Please try again later, spam scanner unavailable
# FROM https://rspamd.com/doc/integration.html (+custom rule for classfied as virus)
# use greylisting available in rspamd v1.3+
defer message = Please try again later
condition = ${if eq{$spam_action}{soft reject}}
# Reject message that have classified as virus
deny message = Message classified as virus
condition = ${if and { { eq{$spam_action}{reject} } { match {$spam_report}{CLAM_VIRUS} } } }
# Reject message that have high-probability to be spam
deny message = Message classified as spam
condition = ${if eq{$spam_action}{reject}}
# Remove foreign headers
warn
remove_header = x-spam-bar : x-spam-score : x-spam-report : x-spam-status
# add spam-score and spam-report header when "add header" action is recommended by rspamd
warn
condition = ${if eq{$spam_action}{add header}}
add_header = X-Spam-Score: $spam_score ($spam_bar)
add_header = X-Spam-Report: $spam_report
# add x-spam-status header if message is not ham
warn
! condition = ${if match{$spam_action}{^no action\$|^greylist\$}}
add_header = X-Spam-Status: Yes
# This hook allows you to hook in your own ACLs without having to
# modify this file. If you do it like we suggest, you'll end up with
# a small performance penalty since there is an additional file being
# accessed. This doesn't happen if you leave the macro unset.
.ifdef CHECK_DATA_LOCAL_ACL_FILE
.include CHECK_DATA_LOCAL_ACL_FILE
.endif
# accept otherwise
accept
EOT
t confwrite /etc/exim4/conf.d/auth/50_dovecot_auth <<"EOT"
dovecot_authplain:
driver = dovecot
public_name = PLAIN
server_advertise_condition = ${if def:tls_cipher}
server_socket = /var/run/dovecot/auth-client
server_set_id = $auth1
EOT
t confwrite /etc/exim4/conf.d/transport/50_dovecot_lmtp <<"EOT"
dovecot_lmtp:
driver = lmtp
socket = /var/run/dovecot/lmtp
#maximum number of deliveries per batch, default 1
batch_max = 200
#allow suffixes/prefixes (default unset)
rcpt_include_affixes
EOT
t confset /etc/exim4/conf.d/main/02_exim4-config_options spamd_address '\S\+\s[0-9]\+' MAIN_SPAMD_ADDRESS
t service exim4 restart
# Afficher un texte explicatif en fin d'exécution
iface=$(ip -o r | sed -ne 's/^default .* dev \(\S\+\).*$/\1/p')
ip=$(ip -o a s dev $iface | sed -ne 's/^.*inet \([0-9.]\+\).*/\1/p')
cat <
URL mises en place :
https://$host.$domain/webmail/
https://$host.$domain/webmail/?admin
https://$host.$domain/rspamd/
Il est possible d'utiliser l'IP aussi (si le DNS n'est pas configuré)
https://$ip/webmail/
EOT