Scripts for Exim and Dovecot SNI with a (Let's Encrypt) user certificate
------DEPRECATED------
Starting with DirectAdmin version 1.515 this script is built into DirectAdmin itself (https://www.directadmin.com/features.php?id=2019).
If you've used my version of the script you need to clean up some files to prevent conflicts after you upgrade DirectAdmin to version 1.515 or later:
------ORIGINAL POST------
I've noticed several people are looking for a way to use their (Let's Encrypt) certificates in conjunction with Exim and Dovecot. So I thought I would share some of the scripts we've been using for some time now. These automatically create and update the mail configs for your domains and pointers/aliases, and should work with any regular, letsencrypt or wildcard certificates.
A few notes to take into account:
First run /usr/local/directadmin/custombuild/build exim to make sure you are using the latest Exim version and then modify the /etc/exim.variables.conf.custom file to include the following code. After saving the file run /usr/local/directadmin/custombuild/build exim_conf to rebuild your Exim config so the changes are merged.
Or use the following if you want more fallback to the default certificate if certain files are missing or removed.
Now place all of the following scripts in /usr/local/directadmin/scripts/custom/ and run /usr/local/directadmin/directadmin p afterwards to set the correct permissions. Windows users should also make sure to use Unix line endings before uploading these files.
domain_change_post.sh
domain_destroy_post.sh
domain_modify_post.sh
letsencrypt_post.sh
ssl_save_post.sh
user_destroy_pre.sh
mail_sni.sh
Now whenever you add, remove or update a certificate, either manually or through Let's Encrypt, the mail config will be updated and your users are able to use their own domain in their email clients.
------DEPRECATED------
Starting with DirectAdmin version 1.515 this script is built into DirectAdmin itself (https://www.directadmin.com/features.php?id=2019).
If you've used my version of the script you need to clean up some files to prevent conflicts after you upgrade DirectAdmin to version 1.515 or later:
- In /usr/local/directadmin/conf/directadmin.conf add mail_sni=1 (assuming you want to use this feature) and remove dovecot_sni= and exim_sni= if these are present.
- In /etc/exim.variables.conf.custom remove the tls_certificate= and tls_privatekey= variables.
- In /usr/local/directadmin/scripts/custom/ remove the following files (or the changes made in them): domain_change_post.sh, domain_destroy_post.sh, domain_modify_post.sh, letsencrypt_post.sh, ssl_save_post.sh, user_destroy_pre.sh, mail_sni.sh
- Remove all 95-<domainname>.conf files from /etc/dovecot/conf.d/ (rm -f /etc/dovecot/conf.d/95-*.conf)
- Rebuild exim_conf (/usr/local/directadmin/custombuild/build exim_conf)
- Rebuild dovecot_conf (/usr/local/directadmin/custombuild/build dovecot_conf)
- Rewrite all exim and dovecot sni configs (echo "action=rewrite&value=mail_sni" >> /usr/local/directadmin/data/task.queue)
------ORIGINAL POST------
I've noticed several people are looking for a way to use their (Let's Encrypt) certificates in conjunction with Exim and Dovecot. So I thought I would share some of the scripts we've been using for some time now. These automatically create and update the mail configs for your domains and pointers/aliases, and should work with any regular, letsencrypt or wildcard certificates.
A few notes to take into account:
- This was made on CentOS 7.2 for our own specific requirements, so feel free to modify it to serve yours.
- DirectAdmin recently added their own implementation for creating the Dovecot files in their latest prerelease (https://www.directadmin.com/features.php?id=1889). You can use both this script and the prerelease without any conflict as it automatically detects if you have the prerelease options enabled or not.
- The script chmods the actual certificate and key files to 644 so Exim can directly read them. If this is to insecure for you, you can change it to copy the files to /etc/virtual/<domainname>/mail.key and chown them to mail:mail (don't forget to change the Exim config accordingly).
- I've simplified the script by always creating a .cert.combined file, even if you don't use a cacert. The .cert.combined file will in that case just contain the certificate itself.
- In order to find the correct subdomains the script reads the certificate and then updates the mail config only if it finds the preconfigured subdomains otherwise the config is removed. By default these are mail or smtp for Exim and mail, imap or pop for dovecot. The domain itself is always included and wildcard certificates automatically include all the preconfigured subdomains. You can change these on line 6 and 7 of mail_sni.sh
First run /usr/local/directadmin/custombuild/build exim to make sure you are using the latest Exim version and then modify the /etc/exim.variables.conf.custom file to include the following code. After saving the file run /usr/local/directadmin/custombuild/build exim_conf to rebuild your Exim config so the changes are merged.
Code:
tls_certificate=${lookup{$tls_in_sni}nwildlsearch{/etc/virtual/snidomains}{/usr/local/directadmin/data/users/${extract{1}{:}{$value}}/domains/${extract{2}{:}{$value}}.cert.combined}{/etc/exim.cert}}
tls_privatekey=${lookup{$tls_in_sni}nwildlsearch{/etc/virtual/snidomains}{/usr/local/directadmin/data/users/${extract{1}{:}{$value}}/domains/${extract{2}{:}{$value}}.key}{/etc/exim.key}}
Code:
tls_certificate=${if exists{/etc/virtual/snidomains}{${lookup{$tls_in_sni}nwildlsearch{/etc/virtual/snidomains}{${if exists{/usr/local/directadmin/data/users/${extract{1}{:}{$value}}/domains/${extract{2}{:}{$value}}.cert.combined}{/usr/local/directadmin/data/users/${extract{1}{:}{$value}}/domains/${extract{2}{:}{$value}}.cert.combined}{/etc/exim.cert}}}{/etc/exim.cert}}}{/etc/exim.cert}}
tls_privatekey=${if exists{/etc/virtual/snidomains}{${lookup{$tls_in_sni}nwildlsearch{/etc/virtual/snidomains}{${if exists{/usr/local/directadmin/data/users/${extract{1}{:}{$value}}/domains/${extract{2}{:}{$value}}.key}{/usr/local/directadmin/data/users/${extract{1}{:}{$value}}/domains/${extract{2}{:}{$value}}.key}{/etc/exim.key}}}{/etc/exim.key}}}{/etc/exim.key}}
Now place all of the following scripts in /usr/local/directadmin/scripts/custom/ and run /usr/local/directadmin/directadmin p afterwards to set the correct permissions. Windows users should also make sure to use Unix line endings before uploading these files.
domain_change_post.sh
Code:
#!/bin/sh
#update mail SNI configs when changing domainname
if [[ ${newdomain} != ${domain} && ${ssl} = "ON" ]]; then
/usr/local/directadmin/scripts/custom/mail_sni.sh "disable" "${username}" "${domain}"
/usr/local/directadmin/scripts/custom/mail_sni.sh "enable" "${username}" "${newdomain}"
elif [ ${ssl} = "OFF" ]; then
#this shouldn't be required, but just to be sure
/usr/local/directadmin/scripts/custom/mail_sni.sh "disable" "${username}" "${domain}"
/usr/local/directadmin/scripts/custom/mail_sni.sh "disable" "${username}" "${newdomain}"
fi
exit 0;
domain_destroy_post.sh
Code:
#!/bin/sh
#remove mail SNI configs when removing a domainname
/usr/local/directadmin/scripts/custom/mail_sni.sh "disable" "${username}" "${domain}"
exit 0;
domain_modify_post.sh
Code:
#!/bin/sh
#enable or disable mail SNI configs based on the current SSL setting
if [ ${ssl} = "ON" ]; then
/usr/local/directadmin/scripts/custom/mail_sni.sh "enable" "${username}" "${domain}"
else
/usr/local/directadmin/scripts/custom/mail_sni.sh "disable" "${username}" "${domain}"
fi
exit 0;
letsencrypt_post.sh
Code:
#!/bin/sh
#enable or disable mail SNI configs when using letsencrypt
if [[ ${action} = "renew" ]]; then
/usr/local/directadmin/scripts/custom/mail_sni.sh "enable" "${username}" "${domain}"
#do nothing for action=request, the domainconfig file isn't updated at this point, instead have ssl_save_post.sh handle this case
elif [[ ${action} = "request" ]]; then
:
else
/usr/local/directadmin/scripts/custom/mail_sni.sh "disable" "${username}" "${domain}"
fi
exit 0;
ssl_save_post.sh
Code:
#!/bin/sh
#enable or disable mail SNI configs based on user input
if [[ (${type} = "create" && (${request} = "no" || ${request} = "letsencrypt")) || ${type} = "paste" || ${type} = "cacert" ]]; then
/usr/local/directadmin/scripts/custom/mail_sni.sh "enable" "${username}" "${domain}"
else
/usr/local/directadmin/scripts/custom/mail_sni.sh "disable" "${username}" "${domain}"
fi
exit 0;
user_destroy_pre.sh
Code:
#!/bin/sh
#disable mail SNI configs for all domains when removing the user
cat /usr/local/directadmin/data/users/${username}/domains.list | while read userdomain; do
/usr/local/directadmin/scripts/custom/mail_sni.sh "disable" "${username}" "${userdomain}"
done
exit 0;
mail_sni.sh
Code:
#!/bin/sh
#mail_sni.sh - version 1.2.1
#setup subdomains to use for Exim and Dovecot
#domain.tld and *.domain.tld will always be used
EXIM_SUBDOMAINS=(mail smtp)
DOVECOT_SUBDOMAINS=(mail imap pop)
#retrieve arguments
ACTION=$1
USERNAME=$2
MAINDOMAIN=$3
#for some reason pcregrep can't be found when called from letsencrypt_post.sh
PCREGREP_BIN="/usr/local/bin/pcregrep"
#check if Dovecot or Exim SNI is already enabled in DA
DOVECOT_SNI=`grep -m1 -e '^dovecot_sni=' "/usr/local/directadmin/conf/directadmin.conf" | cut -d= -f2`
EXIM_SNI=`grep -m1 -e '^exim_sni=' "/usr/local/directadmin/conf/directadmin.conf" | cut -d= -f2`
if [[ ${EXIM_SNI} = 1 ]] && [[ ${DOVECOT_SNI} = 1 ]]; then exit 0; fi
#config files
DOMAIN_CONF="/usr/local/directadmin/data/users/${USERNAME}/domains/${MAINDOMAIN}.conf"
POINTERS_CONF="/usr/local/directadmin/data/users/${USERNAME}/domains/${MAINDOMAIN}.pointers"
DOVECOT_CONF="/etc/dovecot/conf.d/95-${MAINDOMAIN}.conf"
SNIDOMAINS_CONF="/etc/virtual/snidomains"
touch ${SNIDOMAINS_CONF}
chown mail:mail ${SNIDOMAINS_CONF}
#get pointers and aliases for the domain
DOMAINS="${MAINDOMAIN}"
if [[ -s ${POINTERS_CONF} ]]; then
DOMAINS="${DOMAINS} `cat ${POINTERS_CONF} | $PCREGREP_BIN -o1 \"(.*)=\"`"
set -f; DOMAINS=($DOMAINS); set +f
fi
if [[ ${ACTION} = "enable" ]]; then
#remove any previous config so we have a fresh start
if [[ ${EXIM_SNI} != 1 ]]; then
sed -i "/^.*:${USERNAME}:${MAINDOMAIN}$/d" ${SNIDOMAINS_CONF}
fi
if [[ ${DOVECOT_SNI} != 1 ]]; then
rm -f ${DOVECOT_CONF}
fi
#get variables from the domain's config file
SSL=`grep -m1 -e '^ssl=' ${DOMAIN_CONF} | cut -d= -f2`
CERT=`grep -m1 -e '^SSLCertificateFile=' ${DOMAIN_CONF} | cut -d= -f2`
KEY=`grep -m1 -e '^SSLCertificateKeyFile=' ${DOMAIN_CONF} | cut -d= -f2`
CACERT=`grep -m1 -e '^SSLCACertificateFile=' ${DOMAIN_CONF} | cut -d= -f2`
#make sure required files are in place and SSL is turned on
if [[ ${SSL} = "ON" && (! -z ${CERT}) && (! -z ${KEY}) && (-s ${CERT}) && (-s ${KEY}) ]]; then
#create cert.combined file if it isn't being used
if [ -z ${CACERT} ]; then
cp -f ${CERT} "${CERT}.combined"
fi
#chmod certificate and key so Exim can access them
chmod 644 "${CERT}.combined" "${KEY}"
#get all subject names directly from the certificate
SUBJECTNAMES="`openssl x509 -text -in \"${CERT}\" | sed -nr '/^ {12}X509v3 Subject Alternative Name/{n;s/^ *//p}'`"
for DOMAIN in "${DOMAINS[@]}"; do
RESULT=false
#check if the domain itself is present as a subjectname
if [[ "`$PCREGREP_BIN -o1 \"DNS:(\Q${DOMAIN}\E)\" <<< ${SUBJECTNAMES}`" = "${DOMAIN}" ]]; then
#write Exim config
if [[ ${EXIM_SNI} != 1 ]]; then
echo "${DOMAIN}:${USERNAME}:${MAINDOMAIN}" >> ${SNIDOMAINS_CONF}
fi
#write Dovecot config
if [[ ${DOVECOT_SNI} != 1 ]]; then
echo -e "local_name ${DOMAIN} {\n\tssl_cert = <${CERT}.combined\n\tssl_key = <${KEY}\n}" >> ${DOVECOT_CONF}
fi
RESULT=true
fi
#get valid subdomains for the current domain
#set -f is used to prevent globbing with wildcard certificates
SUBDOMAINS="`$PCREGREP_BIN -o1 \"DNS:([^\s]+)\.\Q${DOMAIN}\E\" <<< ${SUBJECTNAMES}`"
set -f; SUBDOMAINS=($SUBDOMAINS); set +f
for SUBDOMAIN in "${SUBDOMAINS[@]}"; do
#write Exim config
if [[ ${EXIM_SNI} != 1 ]]; then
if [[ " ${EXIM_SUBDOMAINS[@]} " =~ " ${SUBDOMAIN} " ]] || [[ "${SUBDOMAIN}" = "*" ]]; then
echo "${SUBDOMAIN}.${DOMAIN}:${USERNAME}:${MAINDOMAIN}" >> ${SNIDOMAINS_CONF}
RESULT=true
fi
fi
#write Dovecot config
if [[ ${DOVECOT_SNI} != 1 ]]; then
if [[ " ${DOVECOT_SUBDOMAINS[@]} " =~ " ${SUBDOMAIN} " ]]; then
echo -e "local_name ${SUBDOMAIN}.${DOMAIN} {\n\tssl_cert = <${CERT}.combined\n\tssl_key = <${KEY}\n}" >> ${DOVECOT_CONF}
RESULT=true
elif [[ "${SUBDOMAIN}" = "*" ]]; then
for DOVECOT_SUBDOMAIN in "${DOVECOT_SUBDOMAINS[@]}"; do
echo -e "local_name ${DOVECOT_SUBDOMAIN}.${DOMAIN} {\n\tssl_cert = <${CERT}.combined\n\tssl_key = <${KEY}\n}" >> ${DOVECOT_CONF}
done
RESULT=true
fi
fi
done
#check if at least one cofig file has been created for this domain
if $RESULT; then
echo "Enabled mail SNI config for ${DOMAIN}<br>"
else
echo "Disabled mail SNI config for ${DOMAIN}<br>"
fi
done
else
echo "Disabled mail SNI config for ${DOMAINS[@]}<br>"
fi
elif [[ ${ACTION} = "disable" ]]; then
#clear the current config for the domain and pointers
if [[ ${EXIM_SNI} != 1 ]]; then
sed -i "/^.*:${USERNAME}:${MAINDOMAIN}$/d" ${SNIDOMAINS_CONF}
fi
if [[ ${DOVECOT_SNI} != 1 ]]; then
rm -f ${DOVECOT_CONF}
fi
echo "Disabled mail SNI config for ${DOMAINS[@]}<br>"
fi
#reload dovecot for changes to take effect
if [[ ${DOVECOT_SNI} != 1 ]]; then
if [[ -d "/etc/systemd/system" && ( -e "/bin/systemctl" || -e "/usr/bin/systemctl" ) ]]; then
systemctl reload dovecot.service
elif [[ -e "/etc/init.d/dovecot" ]]; then
/etc/init.d/dovecot reload
else
service dovecot reload
fi
fi
exit 0;
Now whenever you add, remove or update a certificate, either manually or through Let's Encrypt, the mail config will be updated and your users are able to use their own domain in their email clients.
Last edited: