Mon CV

mailmanSecondaryMX.sh : un outil pour alimenter la table des adresses Mailman relayées sur un serveur MX (postfix) secondaire

26 août 2014

Si vous utilisez Postfix vous avez peut-être mis en place un deuxième serveur de mails Postfix qui joue le rôle de MX Secondaire.

Pour éviter que ce serveur MX secondaire soit un maillon faible dans votre lutte contre le SPAM, une des pratiques est de mettre en place les mêmes filtres que sur le serveur initial (greylisting, filtre anti-spam) et de mettre en place des tables d’adresses relayées.

Si vous utilisez Mailman sur le serveur de mail maître, vous aimeriez sans doute que les adresses utilisées par Mailman soient également relayées par votre serveur MX Secondaire.

J’ai écrit pour cela un outil qui est destiné à alimenter un serveur MX secondaire avec les adresses utilisées par l’outil Mailman en configuration avec domaines virtuels (cf. GNU Mailman Virtual domains).

Il va exporter la totalité de cette table, ou la filtrer en n’exportant que les entrées correspondant à un domaine particulier puis les déposer avec la commande scp sur le serveur MX secondaire et enfin lancer en SSH la commande postmap sur ce serveur secondaire.

Bug report de mailmanSecondaryMX.sh

Si vous voulez me faire remonter un bug : ouvrir un bug.

Pré-requis avant installation et utilisation (configuration Postfix et SSH)

1. Sur le serveur primaire sur lequel est exécuté l’outil mailmanSecondaryMX.sh

  • doit utiliser Mailman en configuration avec domaines vrituels (cf.GNU Mailman Virtual domains)
  • doit disposer d’un fichier virtual_alias_maps ourelay_recipient_maps pour Mailman correctement déclaré dans la configuration de Postfix (cf./etc/postfix/main.cf), par exemplerelay_recipient_maps = hash:/var/lib/mailman/data/virtual-mailman
  • la table virtual_alias_maps ourelay_recipient_maps pour Mailman doit être alimentée (fichier non vide)
  • le serveur MX primaire doit pouvoir accéder au serveur MX secondaire en SSH avec authentification par clé (voir ici comment configurer les clés SSH)

2. Sur le serveur MX secondaire :

  • le ou les domaines relayés doivent être déclarés dans la configuration de Postfix dans la table transport (par défaut la table transport /etc/postfix/transport), par exemple :domaine_relaye.com smtp:url_serveur_mx_primaire.domaine_relaye.com
  • le ou les domaines relayés doivent être déclarés dans la configuration de Postfix dans les relay_domains (par exemple : relay_domains = domaine_relaye.com)
  • table spécifique contenant les adresses relayées doit être déclarée en tant que relay_recipient_maps (par défaut, l’outil utilise la table /etc/postfix/mailmanSecondaryMXmais vous pouvez utiliser un autre fichier si besoin – voir paramètres du script). Par exemple :relay_recipient_maps = hash:/etc/postfix/mailmanSecondaryMX

Installation

Pour installer cet outil, téléchargez le script dans le dossier où vous voulez l’installer :

wget --no-check-certificate https://raw.github.com/yvangodard/mailmanSecondaryMX/master/mailmanSecondaryMX.sh
sudo chmod 755 mailmanSecondaryMX.sh

Pour une aide complète, installez le script et lancez le :

./mailmanSecondaryMX.sh help

 

Paramètres d’utilisation mailmanSecondaryMX.sh

mailmanSecondaryMX.sh doit être exécuté sur le serveur primaire où sont hébergées les listes de discussion de Mailman avec les paramètres suivantes :

./mailmanSecondaryMX.sh [-h] | -s <secondary server address>
                        [-r <relay recipient map>] [-u <remote user>]
                        [-R <remote postfix map>] [-c <remote postmap command>]
                        [-d <domains to extract>]
                        [-e <email report option>] [-E <email address>] [-j <log file>]

Paramètre obligatoire :

  • -s <secondary server address> : adresse du serveur MX secondaire de backup (IP ou URL, par exemple : -s mysecondarymx.server.com)

Paramètres facultatifs :

  • -r <relay recipient map> : chemin complet de la table des adresses de Mailman sur le serveur primaire (par défaut, le chemin /var/lib/mailman/data/virtual-mailman est utilisé)
  • -u <remote user> : utilisateur autorisé à se connecter en SHH par clé au serveur secondaire (par défaut, le compte root est utilisé)
  • -R <remote postfix map> : chemin complet, sur le serveur secondaire, de la table des adresses de Mailman du serveur primaire à relayer sur le serveur secondaire (par défaut, l’outil utilise /etc/postfix/mailmanSecondaryMX)
  • -c <remote postmap command> : chemin complet, sur le serveur secondaire, de la commande  postmap de Postfix (par défaut, l’outil utilise /usr/sbin/postmap)
  • -d <domains to extract> : domaines à traiter. Par défaut : -d alldomains. Pour limiter l’export à certains domaines, il faut les entrer en les séparant par le signe %. Par exemple @mydomain.com ou my.domain.com%@second.domain.net. Pour tous les domaines, utiliser -d alldomains
  • -e <option de rapport par email> : paramètre d’envoi de rapport d’exécution par email, soit ‘onerror‘, ‘forcemail‘ ou ‘nomail‘ (par défaut : ‘nomail‘)
  • -E <adresse email pour les rapports> : adresse email à laquelle sera envoyé le rapport. Cette option doit être utilisée si les options ‘-e forcemail‘ ou ‘-e onerror‘ sont utilisées
  • -j <fichier journal> : cette option active la journalisation à la place de la sortie standard. Spécifiez en argument le chemin du fichier de log (par exemple : ‘/var/log/mailmanSecondaryMX.log‘) ou utilisez ‘default‘ (/var/log/mailmanSecondaryMX.log)

A la première utilisation, pensez à faire manuellement un postmap /etc/postfix/mailmanSecondaryMX sur le serveur secondaire.

 

Le code source du script mailmanSecondaryMX.sh pour les plus vicieux

Voici donc le script :

#! /bin/bash

#------------------------------------------#
#           mailmanSecondaryMX             #
#------------------------------------------#
#                                          #
#  Extract Postfix's virtual_alias_maps to #
#        a secondary MX Server             #
#                                          #
#              Yvan Godard                 #
#          godardyvan@gmail.com            #
#                                          #
#      Version 0.3 -- august, 29 2014      #
#             Under Licence                #
#     Creative Commons 4.0 BY NC SA        #
#                                          #
#          http://goo.gl/znEUU4            #
#                                          #
#------------------------------------------#

# Variables initialisation
VERSION="mailmanSecondaryMX v0.3 - 2014, Yvan Godard [godardyvan@gmail.com]"
help="no"
SCRIPT_DIR=$(dirname $0)
SCRIPT_NAME=$(basename $0)
RELAY_RECIPIENT_MAP=/var/lib/mailman/data/virtual-mailman
EXTRACTED_MAP=$(mktemp /tmp/mailmanSecondaryMX_map.XXXXX)
EXTRACTED_MAP_TEMP=$(mktemp /tmp/mailmanSecondaryMX_map_temp.XXXXX)
REMOTE_SERVER_ADDRESS=""
REMOTE_SERVER_USER="root"
REMOTE_SERVER_POSTMAP_CMD="/usr/sbin/postmap"
REMOTE_RELAY_RECIPIENT_MAP="/etc/postfix/mailmanSecondaryMX"
DOMAINS="alldomains"
DOMAINS_LIMIT=0
LIST_DOMAINS=$(mktemp /tmp/mailmanSecondaryMX_domains.XXXXX)
EMAIL_REPORT="nomail"
EMAIL_LEVEL=0
LOG="/var/log/mailmanSecondaryMX.log"
LOG_TEMP=$(mktemp /tmp/mailmanSecondaryMX_tmpLog.XXXXX)
LOG_ACTIVE=0
EMAIL_ADDRESS=""
SSH_TEST=0

help () {
	echo -e "$VERSION\n"
	echo -e "This tool is designed to export a Postfix's virtual_alias_maps to a secondary MX Server."
	echo -e "SSH access to this second server should be possible without password (key authentication)."
	echo -e "This tool is licensed under the Creative Commons 4.0 BY NC SA licence."
	echo -e "\nDisclamer:"
	echo -e "This tool is provide without any support and guarantee."
	echo -e "\nSynopsis:"
	echo -e "./${SCRIPT_NAME} [-h] | -s <secondary server address>"
	echo -e "                        [-r <relay recipient map>] [-u <remote user>]"
	echo -e "                        [-R <remote postfix map>] [-c <remote postmap command>]"
	echo -e "                        [-d <domains to extract>]"
	echo -e "                        [-e <email report option>] [-E <email address>] [-j <log file>]"
	echo -e "\n\t-h:                              prints this help then exit"
	echo -e "\nMandatory option:"
	echo -e "\t-s <secondary server address>:   the address of the secondary MX Server (IP or URL, e.g.: 'mysecondarymx.server.com')"
	echo -e "\nOptional options:"
	echo -e "\t-r <relay recipient map>:        the full path of relay recipient map, as defined here http://goo.gl/39uDsc,"
	echo -e "\t                                 for the Mailman server (default: '${RELAY_RECIPIENT_MAP}')"
	echo -e "\t-u <remote user>:                the remote user who can connect through SSH to the secondary MX server,"
	echo -e "\t                                 and can lauch Postmap command (e.g.: 'myuser', default: '${REMOTE_SERVER_USER}'."
	echo -e "\t-R <remote postfix map>:         the full name of the remote Postfix map filename (e.g.: '/etc/postfix/map1',"
	echo -e "\t                                 default '${REMOTE_RELAY_RECIPIENT_MAP}''). This map file must be defined"
	echo -e "\t                                 in '/etc/postfix/main.cf' as a 'relay_recipient_maps ='"
	echo -e "\t-c <remote postmap command>:     the full path of sencondary MX server 'postmap' command (default: '${REMOTE_SERVER_POSTMAP_CMD}')"
	echo -e "\t-d <domains to extract>:         domains that must be processed with or without '@', separated by '%'"
	echo -e "\t                                 (e.g.: '@mydomain.com' or 'my.domain.com%@second.domain.net') or use 'alldomains'."
	echo -e "\t                                 For all domains, please enter '-d alldomains'. By default: '${DOMAINS}'"
	echo -e "\t-e <email report option>:        settings for sending a report by email, must be 'onerror', 'forcemail' or 'nomail',"
	echo -e "\t                                 default: '${EMAIL_REPORT}'."
	echo -e "\t-E <email address>:              email address to send the report, must be filled if '-e forcemail' or '-e onerror' options is used"
	echo -e "\t-j <log file>:                   enables logging instead of standard output. Specify an argument for the full path to the log file"
	echo -e "\t                                 (e.g.: '$LOG') or use 'default' ($LOG)"
	exit 0
}

error () {
	echo -e "\n*** Error ***"
	echo -e ${1}
	echo -e "\n"${VERSION}
	alldone 1
}

alldone () {
	# Sleep to avoid multiple instances
	[ $1 -ne 2 ] && sleep 30
	# Redirect standard outpout
	exec 1>&6 6>&-
	# Logging if needed 
	[ $LOG_ACTIVE -eq 1 ] && cat $LOG_TEMP >> $LOG
	# Print current log to standard outpout
	[ $LOG_ACTIVE -ne 1 ] && cat $LOG_TEMP
	[ $EMAIL_LEVEL -ne 0 ] && [ $1 -ne 0 ] && cat $LOG_TEMP | mail -s "[ERROR] ${SCRIPT_NAME} on ${HOSTNAME}" ${EMAIL_ADDRESS}
	[ $EMAIL_LEVEL -eq 2 ] && [ $1 -eq 0 ] && cat $LOG_TEMP | mail -s "[OK] ${SCRIPT_NAME} on ${HOSTNAME}" ${EMAIL_ADDRESS}
	# Remove temp files/folder
	[ -f ${EXTRACTED_MAP} ] && rm -R ${EXTRACTED_MAP}
	[ -f ${EXTRACTED_MAP_TEMP} ] && rm -R ${EXTRACTED_MAP_TEMP}
	[ -f ${LIST_DOMAINS} ] && rm -R ${LIST_DOMAINS}
	[ -f ${LOG_TEMP} ] && rm -R ${LOG_TEMP}
	# Remove lock file
	[ $1 -eq 0 ] && [ -e ${LOCKFILE} ] && rm ${LOCKSYNC}
	exit ${1}
}

while getopts "hs:u:c:d:r:R:e:E:j:" OPTION
do
	case "$OPTION" in
		h)	help="yes"
						;;
		s)	REMOTE_SERVER_ADDRESS=${OPTARG}
						;;
		r) 	RELAY_RECIPIENT_MAP=${OPTARG}
						;;
		u)	REMOTE_SERVER_USER=${OPTARG}
						;;
		R)	REMOTE_RELAY_RECIPIENT_MAP=${OPTARG}
						;;
	    c) 	REMOTE_SERVER_POSTMAP_CMD=${OPTARG}
						;;
		d) 	DOMAINS=${OPTARG}
			if [[ ${DOMAINS} != "alldomains" ]] 
				then
				echo ${DOMAINS} | perl -p -e 's/%/\n/g' | perl -p -e 's/ //g' | awk '!x[$0]++' >> ${LIST_DOMAINS}
				DOMAINS_LIMIT=1
			elif [[ ${DOMAINS} = "alldomains" ]]
				then
				DOMAINS_LIMIT=0
			fi
						;;
        e)	EMAIL_REPORT=${OPTARG}
                        ;;                             
        E)	EMAIL_ADDRESS=${OPTARG}
                        ;;
        j)	[ $OPTARG != "default" ] && LOG=${OPTARG}
			LOG_ACTIVE=1
                        ;;
	esac
done

# Verifiy mandatory option
if [[ ${REMOTE_SERVER_ADDRESS} = "" ]]
	then
        help
        alldone 1
fi

# Redirect standard outpout to temp file
exec 6>&1
exec >> ${LOG_TEMP}

# Start temp log file
echo -e "\n****************************** `date` ******************************\n"
echo -e "$0 started with options:"
echo -e "\t-s ${REMOTE_SERVER_ADDRESS} (secondary server address)"
echo -e "\t-r ${RELAY_RECIPIENT_MAP} (local relay recipient map)"
echo -e "\t-u ${REMOTE_SERVER_USER} (remote user)"
echo -e "\t-R ${REMOTE_RELAY_RECIPIENT_MAP} (remote postfix map)"
echo -e "\t-c ${REMOTE_SERVER_POSTMAP_CMD} (remote postmap command)"
if [[ ${DOMAINS_LIMIT} = "1" ]]; then
	echo -e "\t-d (domains to extract):"
	for LINE in $(cat ${LIST_DOMAINS})
	do
		echo -e "\t   > ${LINE}"
	done
elif [[ ${DOMAINS_LIMIT} = "0" ]]; then
	echo -e "\t-d alldomains"
fi
echo -e "\t-e ${EMAIL_REPORT} (email report option)"
if [[ ${EMAIL_REPORT} != "nomail" ]] 
	then
	echo -e "\t-E ${EMAIL_ADDRESS} (email report address)"
fi
if [[ ${LOG_ACTIVE} != "0" ]]
	then
	echo -e "\t-j ${LOG} (log file)"
fi
echo ""

# Lock system
export LOCKSYNC="/var/run/${SCRIPT_NAME}.lock"
PID=$$
# If lock file exists
if [ -f "${LOCKSYNC}" ]; then
	echo "Previous lock file found..."
	PIDLOCK=`cat ${LOCKSYNC}`
	# Test if proccess is already running
    if [ -f /proc/${PIDLOCK}/exe ]; then
		# If yes ... kill the script
        echo -e "> Process still active. Please try again later.\nEnd."
        alldone 2
    else
		# If not, cleaning lock file
        echo "> Process is complete: cleaning lock file."
        rm ${LOCKSYNC}
    fi
fi
# Create lock file
echo "Create lock file "${LOCKSYNC}
echo ${PID} > ${LOCKSYNC}

# Test of sending email parameter and check the consistency of the parameter email address
if [[ ${EMAIL_REPORT} = "forcemail" ]]
	then
	EMAIL_LEVEL=2
	if [[ -z $EMAIL_ADDRESS ]]
		then
		echo -e "You used option '-e ${EMAIL_REPORT}' but you have not entered any email info.\n\t> We continue the process without sending email."
		EMAIL_LEVEL=0
	else
		echo "${EMAIL_ADDRESS}" | grep '^[a-zA-Z0-9._-]*@[a-zA-Z0-9._-]*\.[a-zA-Z0-9._-]*$' > /dev/null 2>&1
		if [ $? -ne 0 ]
			then
    		echo -e "This address '${EMAIL_ADDRESS}' does not seem valid.\n\t> We continue the process without sending email."
    		EMAIL_LEVEL=0
    	fi
    fi
elif [[ ${EMAIL_REPORT} = "onerror" ]]
	then
	EMAIL_LEVEL=1
	if [[ -z $EMAIL_ADDRESS ]]
		then
		echo -e "You used option '-e ${EMAIL_REPORT}' but you have not entered any email info.\n\t> We continue the process without sending email."
		EMAIL_LEVEL=0
	else
		echo "${EMAIL_ADDRESS}" | grep '^[a-zA-Z0-9._-]*@[a-zA-Z0-9._-]*\.[a-zA-Z0-9._-]*$' > /dev/null 2>&1
		if [ $? -ne 0 ]
			then	
    		echo -e "This address '${EMAIL_ADDRESS}' does not seem valid.\n\t> We continue the process without sending email."
    		EMAIL_LEVEL=0
    	fi
    fi
elif [[ ${EMAIL_REPORT} != "nomail" ]]
	then
	echo -e "\nOption '-e ${EMAIL_REPORT}' is not valid (must be: 'onerror', 'forcemail' or 'nomail').\n\t> We continue the process without sending email."
	EMAIL_LEVEL=0
elif [[ ${EMAIL_REPORT} = "nomail" ]]
	then
	EMAIL_LEVEL=0
fi

# Test SSH connection
ssh -q -o "BatchMode=yes" ${REMOTE_SERVER_USER}@${REMOTE_SERVER_ADDRESS} "echo 2>&1" && SSH_TEST=1 || echo -e "Unable to access ssh://${REMOTE_SERVER_USER}@${REMOTE_SERVER_ADDRESS}."
if [[ ${SSH_TEST} = "0" ]]; then
	error "Your SSH connection to ${REMOTE_SERVER_USER}@${REMOTE_SERVER_ADDRESS} doesn't work.\nPlease verify parameters.\nPerhaps you need to set up SSH Keys as described here: http://goo.gl/475wy4."
fi

# Test if remote POSTMAP command exists
TEST_POSTMAP_CMD=$(ssh ${REMOTE_SERVER_ADDRESS} -l ${REMOTE_SERVER_USER} test -f ${REMOTE_SERVER_POSTMAP_CMD} && echo OK)
if [[ ! ${TEST_POSTMAP_CMD} = "OK" ]]; then
	error "Postmap command ${REMOTE_SERVER_USER}@${REMOTE_SERVER_ADDRESS}:${REMOTE_SERVER_POSTMAP_CMD} doesn't exist.\nPlease verify parameters."
fi

# Test RELAY_RECIPIENT_MAP file
if [[ ! -f ${RELAY_RECIPIENT_MAP} ]]; then
	error "Relay Recipient Map file '${RELAY_RECIPIENT_MAP}' to process doesn't exist.\nPlease verify parameters."
fi

# Test DOMAIN content
[[ ${DOMAINS_LIMIT} = "1" ]] && [[ -z $(cat ${LIST_DOMAINS}) ]] && echo "Option -d used without any domain. Process continues with '-d alldomains'." && DOMAINS="alldomains" && DOMAINS_LIMIT=0

# Step 1 : extract remote map
OLDIFS=$IFS; IFS=$'\n'
if [[ ${DOMAINS_LIMIT} = "1" ]]; then
	for RELAY_RECIPIENT in $(cat ${RELAY_RECIPIENT_MAP})
	do
		for DOMAIN in $(cat ${LIST_DOMAINS})
		do
			echo "${RELAY_RECIPIENT}" | grep ${DOMAIN} > /dev/null 2>&1
			[[ ${?} -eq 0 ]] && echo "${RELAY_RECIPIENT}" >> ${EXTRACTED_MAP_TEMP}
		done
	done
fi

[[ ${DOMAINS_LIMIT} = "1" ]] && [[ -z $(cat ${EXTRACTED_MAP_TEMP}) ]] && error "Nothing to extract. Please verify your parameters."

echo "# Relay recipient map file generated by ${0}" >> ${EXTRACTED_MAP}
echo "# on ${HOSTNAME}" >> ${EXTRACTED_MAP}
echo "# ${VERSION}" >> ${EXTRACTED_MAP}
echo "# `date`" >> ${EXTRACTED_MAP}
cat ${EXTRACTED_MAP_TEMP} | awk '!x[$0]++' >> ${EXTRACTED_MAP}

IFS=$OLDIFS

# Step 2 : send file to secondary MX Server
if [[ ${DOMAINS_LIMIT} = "0" ]]; then
	[[ -f ${EXTRACTED_MAP} ]] && rm ${EXTRACTED_MAP}
	echo -e "\n-> Sending original relay recipient map ${RELAY_RECIPIENT_MAP}...\n"
	rsync -cave ssh ${RELAY_RECIPIENT_MAP} ${REMOTE_SERVER_USER}@${REMOTE_SERVER_ADDRESS}:${REMOTE_RELAY_RECIPIENT_MAP}
	if [ $? -ne 0 ]; then
		ERROR_MESSAGE=$(echo $?)
		error "Error while sending file:\nrsync -cave ssh ${RELAY_RECIPIENT_MAP} ${REMOTE_SERVER_USER}@${REMOTE_SERVER_ADDRESS}:${REMOTE_RELAY_RECIPIENT_MAP}.\n${ERROR_MESSAGE}."
	else
		echo -e "-> Sending file to ${REMOTE_SERVER_ADDRESS}: OK"
	fi
elif [[ ${DOMAINS_LIMIT} = "1" ]]; then
	echo -e "\n-> Relay recipient map extracted to ${EXTRACTED_MAP}...\n"
	rsync -cave ssh ${EXTRACTED_MAP} ${REMOTE_SERVER_USER}@${REMOTE_SERVER_ADDRESS}:${REMOTE_RELAY_RECIPIENT_MAP}
	if [ $? -ne 0 ]; then
		ERROR_MESSAGE=$(echo $?)
		error "Error while sending file:\nrsync -cave ssh ${EXTRACTED_MAP} ${REMOTE_SERVER_USER}@${REMOTE_SERVER_ADDRESS}:${REMOTE_RELAY_RECIPIENT_MAP}.\n${ERROR_MESSAGE}."
	else
		echo -e "-> Sending file to ${REMOTE_SERVER_ADDRESS}: OK\n"
	fi
fi

# Step 3 : postmap 
ERROR_SSH=$(ssh $REMOTE_SERVER_USER@$REMOTE_SERVER_ADDRESS "$REMOTE_SERVER_POSTMAP_CMD $REMOTE_RELAY_RECIPIENT_MAP && echo \$?")
if [[ ${ERROR_SSH} -ne 0 ]]; then
	error "Error while running command:\nssh ${REMOTE_SERVER_USER}@${REMOTE_SERVER_ADDRESS} '${REMOTE_SERVER_POSTMAP_CMD} ${REMOTE_RELAY_RECIPIENT_MAP}'."
else
	echo -e "-> Postmap on remote server (${REMOTE_SERVER_POSTMAP_CMD} ${REMOTE_RELAY_RECIPIENT_MAP}): OK"
fi

echo ""
echo "***************************** ${SCRIPT_NAME} finished ******************************"
alldone 0
mailmanSecondaryMX.shview rawview file on GitHub

 

Evidemment, avis, commentaires et compléments sont les bienvenus !

Posted in Unix et LinuxTags: