#! /bin/bash
#------------------------------------------#
# LDAP2Mailman #
#------------------------------------------#
# #
# Synchronize LDAP Group to Mailman liste #
# (one way only) #
# #
# Yvan Godard #
# godardyvan@gmail.com #
# #
# Version 0.8 -- january, 10 2015 #
# Under Licence #
# Creative Commons 4.0 BY NC SA #
# #
# http://goo.gl/lriKvn #
# #
#------------------------------------------#
# Variables initialisation
VERSION="LDAP2Mailman v0.8 - 2015, Yvan Godard [godardyvan@gmail.com]"
help="no"
SCRIPT_DIR=$(dirname $0)
SCRIPT_NAME=$(basename $0)
LIST_MEMBERS=$(mktemp /tmp/ldap2mailman_email_list_members.XXXXX)
LIST_SENDERS=$(mktemp /tmp/ldap2mailman_email_list_senders.XXXXX)
LIST_USERS=$(mktemp /tmp/ldap2mailman_email_list_users.XXXXX)
LIST_CLEAN_MEMBERS=$(mktemp /tmp/ldap2mailman_email_list_clean_members.XXXXX)
LIST_CLEAN_SENDERS=$(mktemp /tmp/ldap2mailman_email_list_clean_senders.XXXXX)
ACTUAL_LIST_CONFIG=$(mktemp /tmp/ldap2mailman_actual_list_config.XXXXX)
NEW_LIST_CONFIG=$(mktemp /tmp/ldap2mailman_new_list_config.XXXXX)
NEW_LIST_CONFIG_TEMP=$(mktemp /tmp/ldap2mailman_new_list_config_temp.XXXXX)
URL="ldap://127.0.0.1"
DN_USER_BRANCH="cn=users"
MAILMAN_OPTIONS="-a=no -d=no -w=no -g=no"
MAILMAN_BIN="/usr/lib/mailman/bin"
EMAIL_REPORT="nomail"
EMAIL_LEVEL=0
LOG="/var/log/ldap2mailman.log"
LOG_ACTIVE=0
LOG_TEMP=$(mktemp /tmp/ldap2mailman_log.XXXXX)
WITH_LDAP_BIND="no"
help () {
echo -e "$VERSION\n"
echo -e "This tool is designed to synchronize email addresses from an LDAP group to a Mailman list."
echo -e "It works both with LDAP groups defined by objectClass posixGroup or groupOfNames."
echo -e "Mailman list must first be created in Mailman before using this tool."
echo -e "\nDisclamer:"
echo -e "This tool is provide without any support and guarantee."
echo -e "\nSynopsis:"
echo -e "./$SCRIPT_NAME [-h] | -d -t -g -l "
echo -e " [-s ] [-a ] [-p ]"
echo -e " [-u ] [-D ]"
echo -e " [-m ] [-b ]"
echo -e " [-e ] [-E ] [-j ]"
echo -e "\n\t-h: prints this help then exit"
echo -e "\nMandatory options:"
echo -e "\t-d : the base DN for each LDAP entry (e.g.: 'dc=server,dc=office,dc=com')"
echo -e "\t-t : the type of group you want to sync, must be 'posixGroup' or 'groupOfNames'"
echo -e "\t-g : the relative DN of the LDAP group to sync to Mailman list (e.g.: 'cn=mygroup,cn=groups' or 'cn=mygroup,ou=lists')"
echo -e "\t-l : the name of the existing list to populate on Mailman"
echo -e "\nOptional options:"
echo -e "\t-s : the LDAP server URL (default: '${URL}')"
echo -e "\t-a : LDAP administrator UID, if bind is needed to access LDAP (e.g.: 'diradmin')"
echo -e "\t-p : the password of the LDAP administrator (asked if missing)"
echo -e "\t-u : the relative DN of the LDAP branch that contains the users (e.g.: 'cn=allusers', default: '${DN_USER_BRANCH}')"
echo -e "\t-D : main domain if the user has multiple email addresses registered in the LDAP (e.g.: 'mydomain.fr')"
echo -e "\t-m : are the parameters passed to mailman's sync_members command (default: '${MAILMAN_OPTIONS}')"
echo -e "\t-b : path to the bin directory of your Mailman installation (default: '${MAILMAN_BIN}')"
echo -e "\t-e : settings for sending a report by email, must be 'onerror', 'forcemail' or 'nomail' (default: '${EMAIL_REPORT}')"
echo -e "\t-E : email address to send the report (must be filled if '-e forcemail' or '-e onerror' options is used)"
echo -e "\t-j : 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
}
function alldone () {
# Remove temp files
[ -f ${LIST_USERS} ] && rm ${LIST_USERS}
[ -f ${LIST_MEMBERS} ] && rm ${LIST_MEMBERS}
[ -f ${LIST_CLEAN_MEMBERS} ] && rm ${LIST_CLEAN_MEMBERS}
[ -f ${LIST_SENDERS} ] && rm ${LIST_SENDERS}
[ -f ${LIST_CLEAN_SENDERS} ] && rm ${LIST_CLEAN_SENDERS}
[ -f ${ACTUAL_LIST_CONFIG} ] && rm ${ACTUAL_LIST_CONFIG}
[ -f ${NEW_LIST_CONFIG} ] && rm ${NEW_LIST_CONFIG}
[ -f ${NEW_LIST_CONFIG_TEMP} ] && rm ${NEW_LIST_CONFIG_TEMP}
# 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 : ldap2mailman.sh] list ${LISTNAME} (LDAP group ${LDAPGROUP},${DNBASE})" ${EMAIL_ADDRESS}
[ ${EMAIL_LEVEL} -eq 2 ] && [ ${1} -eq 0 ] && cat ${LOG_TEMP} | mail -s "[OK : ldap2mailman.sh] list ${LISTNAME} (LDAP group $LDAPGROUP,$DNBASE)" ${EMAIL_ADDRESS}
rm ${LOG_TEMP}
exit ${1}
}
# Fonction utilisée plus tard pour les résultats de requêtes LDAP encodées en base64
function base64decode () {
echo ${1} | grep :: > /dev/null 2>&1
if [ $? -eq 0 ]
then
VALUE=$(echo ${1} | grep :: | awk '{print $2}' | openssl enc -base64 -d )
# ATTRIBUTE=$(echo ${1} | grep :: | awk '{print $1}' | awk 'sub( ".$", "" )' )
echo "${VALUE}"
else
VALUE=$(echo ${1} | grep : | awk '{print $2}')
echo "${VALUE}"
fi
}
optsCount=0
while getopts "hd:a:p:t:g:l:s:u:D:m:b:e:E:j:" OPTION
do
case "$OPTION" in
h) help="yes"
;;
d) DNBASE=${OPTARG}
let optsCount=$optsCount+1
;;
a) LDAPADMIN_UID=${OPTARG}
[[ ${LDAPADMIN_UID} != "" ]] && WITH_LDAP_BIND="yes"
;;
p) PASS=${OPTARG}
;;
t) LDAPGROUP_OBJECTCLASS=${OPTARG}
let optsCount=$optsCount+1
;;
g) LDAPGROUP=${OPTARG}
let optsCount=$optsCount+1
;;
l) LISTNAME=${OPTARG}
let optsCount=$optsCount+1
;;
s) URL=${OPTARG}
;;
u) DN_USER_BRANCH=${OPTARG}
;;
D) DOMAIN=${OPTARG}
;;
m) MAILMAN_OPTIONS=${OPTARG}
;;
b) MAILMAN_BIN=${OPTARG}
;;
e) EMAIL_REPORT=${OPTARG}
;;
E) EMAIL_ADDRESS=${OPTARG}
;;
j) [ ${OPTARG} != "default" ] && LOG=${OPTARG}
LOG_ACTIVE=1
;;
esac
done
if [[ ${optsCount} != "4" ]]
then
help
alldone 1
fi
if [[ ${help} = "yes" ]]
then
help
fi
if [[ ${WITH_LDAP_BIND} = "yes" ]] && [[ ${PASS} = "" ]]
then
echo "Password for ${LDAPADMIN_UID},${DN_USER_BRANCH},${DNBASE}?"
read -s PASS
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 for Mailman list ${LISTNAME}\n(LDAP group ${LDAPGROUP},${DNBASE})\n"
# 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 use 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 use 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
# Verification of LDAPGROUP_OBJECTCLASS parameter
[[ ${LDAPGROUP_OBJECTCLASS} != "posixGroup" ]] && [[ ${LDAPGROUP_OBJECTCLASS} != "groupOfNames" ]] && error "Parameter '-t ${LDAPGROUP_OBJECTCLASS}' is not correct.\n-t must be 'posixGroup' or 'groupOfNames'"
# Verification of presence of the Mailman list to populate
echo -e "\nTest for the presence of Mailman list"
if [ -f ${MAILMAN_BIN}/list_lists ]
then
${MAILMAN_BIN}/list_lists | grep -i ${LISTNAME} > /dev/null 2>&1
if [ $? -ne 0 ]
then
error "Mailman list you try to sync does not exist.\nPlease create the list before re-launching this tool."
else
echo -e "\t-> Mailman list seems correct."
fi
else
error "${MAILMAN_BIN}/list_lists is missing.\Check your Mailman installation."
fi
# Verification of LDAP_SERVER_URL parameter
[[ ${URL} = "" ]] && echo -e "You used option '-s' but you have not entered any LDAP url. Wi'll try to continue with url 'ldap://127.0.0.1'" && URL="ldap://127.0.0.1"
# LDAP connection test
echo -e "\nConnecting LDAP at $URL ...\n"
[[ ${WITH_LDAP_BIND} = "yes" ]] && LDAP_COMMAND_BEGIN="ldapsearch -LLL -H ${URL} -b ${LDAPGROUP},${DNBASE} -D uid=${LDAPADMIN_UID},${DN_USER_BRANCH},${DNBASE} -w ${PASS}"
[[ ${WITH_LDAP_BIND} = "no" ]] && LDAP_COMMAND_BEGIN="ldapsearch -LLL -H ${URL} -b ${LDAPGROUP},${DNBASE} -x"
${LDAP_COMMAND_BEGIN} > /dev/null 2>&1
if [ $? -ne 0 ]
then
error "Error connecting to LDAP server.\nPlease verify your URL and user/pass (if needed)."
fi
# Test if user list is not empty
if [[ ${LDAPGROUP_OBJECTCLASS} = "groupOfNames" ]]
then
if [[ -z $(${LDAP_COMMAND_BEGIN} member | grep member: | awk '{print $2}' | awk -F',' '{print $1}') ]]
then
error "User list on LDAP group is empty!"
else
${LDAP_COMMAND_BEGIN} member | grep member: | awk '{print $2}' | awk -F',' '{print $1}' >> ${LIST_USERS}
fi
elif [[ ${LDAPGROUP_OBJECTCLASS} = "posixGroup" ]]
then
if [[ -z $(${LDAP_COMMAND_BEGIN} memberUid | grep memberUid: | awk '{print $2}' | sed -e 's/^./uid=&/g') ]]
then
error "User list on LDAP group is empty"
else
${LDAP_COMMAND_BEGIN} memberUid | grep memberUid: | awk '{print $2}' | sed -e 's/^./uid=&/g' >> ${LIST_USERS}
fi
fi
# Processing each user
for USER in $(cat $LIST_USERS)
do
PRINCIPAL_EMAIL=""
echo "- Processing user: ${USER}"
EMAILS=$(mktemp /tmp/mailman_emails.XXXXX)
EMAILS_CLEAN_TEMP=$(mktemp /tmp/mailman_emails_clean_tmp.XXXXX)
EMAILS_CLEAN=$(mktemp /tmp/mailman_emails_clean.XXXXX)
SECONDARY_EMAILS=$(mktemp /tmp/mailman_secondry_emails.XXXXX)
[[ ${WITH_LDAP_BIND} = "yes" ]] && ldapsearch -LLL -H ${URL} -D uid=${LDAPADMIN_UID},${DN_USER_BRANCH},${DNBASE} -b ${DN_USER_BRANCH},${DNBASE} -w ${PASS} ${USER} mail | grep ^mail: >> ${EMAILS}
[[ ${WITH_LDAP_BIND} = "no" ]] && ldapsearch -LLL -H ${URL} -b ${DN_USER_BRANCH},${DNBASE} -x ${USER} mail | grep mail: >> ${EMAILS}
# Correction to support LDIF splitted lines, thanks to Guillaume Bougard (gbougard@pkg.fr)
perl -n -e 'chomp ; print "\n" unless (substr($_,0,1) eq " " || !defined($lines)); $_ =~ s/^\s+// ; print $_ ; $lines++;' -i "${EMAILS}"
# Decode if Base64 encoding is used
OLDIFS=$IFS; IFS=$'\n'
for LINE in $(cat ${EMAILS})
do
base64decode $LINE >> ${EMAILS_CLEAN_TEMP}
done
IFS=$OLDIFS
# test if address are correct
for LINE in $(cat ${EMAILS_CLEAN_TEMP})
do
echo "${LINE}" | grep '^[a-zA-Z0-9._-]*@[a-zA-Z0-9._-]*\.[a-zA-Z0-9._-]*$' > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo "\tThis address '${LINE}' does not seem valid.\n\t-> We do not use this address."
else
echo "${LINE}" >> ${EMAILS_CLEAN}
fi
done
LINES_NUMBER=$(cat ${EMAILS_CLEAN} | grep "." | wc -l)
echo -e "\tNumber of lines/emails: ${LINES_NUMBER}"
# If no email -> skip
if [[ -z $(cat ${EMAILS_CLEAN}) ]]
then
echo -e "\tPas d'email"
# If only one mail, keep this mail
elif [ ${LINES_NUMBER} -eq 1 ]
then
PRINCIPAL_EMAIL=$(head -n 1 ${EMAILS_CLEAN})
echo -e "\tOnly one email address: ${PRINCIPAL_EMAIL}"
# If multiples mails
elif [ ${LINES_NUMBER} -gt 1 ]
then
echo -e "\tMultiples email addresses found."
if [[ -z ${DOMAIN} ]]
# No main domain defined -> keep first mail
then
PRINCIPAL_EMAIL=$(head -n 1 ${EMAILS_CLEAN})
echo -e "\t-> Keep first one: ${PRINCIPAL_EMAIL}"
else
# Main domain defined -> search first mail in domain
cat ${EMAILS_CLEAN} | grep ${DOMAIN} > /dev/null 2>&1
if [ $? -ne 0 ]
then
PRINCIPAL_EMAIL=$(head -n 1 ${EMAILS_CLEAN})
echo -e "\t-> No email containing the main domain defined, we keep the first user email: ${PRINCIPAL_EMAIL}"
else
PRINCIPAL_EMAIL=$(cat ${EMAILS_CLEAN} | grep ${DOMAIN} | head -n 1)
echo -e "\t-> Email with main domain found: ${PRINCIPAL_EMAIL}"
fi
fi
# Creating list of address authorized to send email
cat ${EMAILS_CLEAN} | grep -v ${PRINCIPAL_EMAIL} >> ${SECONDARY_EMAILS}
echo -e "\tAllowed senders list:"
echo -e "\t-> $(cat ${SECONDARY_EMAILS} | perl -p -e 's/\n/ - /g' | awk 'sub( "...$", "" )')"
fi
# Add address to allowed senders list
echo ${PRINCIPAL_EMAIL} >> ${LIST_MEMBERS}
[[ ! -z $(cat ${SECONDARY_EMAILS}) ]] && cat ${SECONDARY_EMAILS} >> ${LIST_SENDERS}
# Remove email temp files
rm ${EMAILS}
rm ${EMAILS_CLEAN}
rm ${EMAILS_CLEAN_TEMP}
rm ${SECONDARY_EMAILS}
echo ""
done
echo -e "*************\nFor information, LIST_MEMBERS:"
cat ${LIST_MEMBERS}
echo "*************"
if [ -f ${LIST_SENDERS} ] && [[ ! -z $(cat ${LIST_SENDERS}) ]]
then
echo "For information, LIST_SENDERS:"
cat ${LIST_SENDERS}
echo "*************"
fi
# Installing $SCRIPT_DIR/clean-email-list.py if needed
if [ ! -f ${SCRIPT_DIR}/clean-email-list.py ]
then
echo -e "\nInstalling ${SCRIPT_DIR}/clean-email-list.py..."
wget -O ${SCRIPT_DIR}/clean-email-list.py --no-check-certificate https://raw.github.com/yvangodard/ldap2mailman/master/clean-email-list.py
if [ $? -ne 0 ]
then
ERROR_MESSAGE=$(echo $?)
error "Error while downloading https://raw.github.com/yvangodard/ldap2mailman/master/clean-email-list.py.\n${ERROR_MESSAGE}.\nYou need to solve this before re-launching this tool."
else
echo -e "\t-> Installation OK"
chmod +x ${SCRIPT_DIR}/clean-email-list.py
fi
fi
echo -e "\nProcessing the lists with ${SCRIPT_DIR}/clean-email-list.py"
if [ -f ${SCRIPT_DIR}/clean-email-list.py ]
then
${SCRIPT_DIR}/clean-email-list.py ${LIST_MEMBERS} > ${LIST_CLEAN_MEMBERS}
[ -f ${LIST_SENDERS} ] && [[ ! -z $(cat ${LIST_SENDERS}) ]] && ${SCRIPT_DIR}/clean-email-list.py ${LIST_SENDERS} > ${LIST_CLEAN_SENDERS}
else
error "${SCRIPT_DIR}/clean-email-list.py missing.\nTry to install it manually before re-launching this tool.\nGo to https://github.com/yvangodard/ldap2mailman."
fi
echo -e "\n*************"
echo "For information, LIST_CLEAN_MEMBERS:"
cat ${LIST_CLEAN_MEMBERS}
echo "*************"
if [ -f ${LIST_CLEAN_SENDERS} ] && [[ ! -z $(cat ${LIST_CLEAN_SENDERS}) ]]
then
echo "For information, LIST_CLEAN_SENDERS:"
cat ${LIST_CLEAN_SENDERS}
echo "*************"
fi
# Sync with Mailman
echo -e "\nList processing with command: ${MAILMAN_BIN}/sync_members ${MAILMAN_OPTIONS} -f ${LIST_CLEAN_MEMBERS} ${LISTNAME}"
if [ -f ${MAILMAN_BIN}/sync_members ]
then
echo -e "Result of the command: "
${MAILMAN_BIN}/sync_members ${MAILMAN_OPTIONS} -f ${LIST_CLEAN_MEMBERS} ${LISTNAME}
if [ $? -ne 0 ]
then
ERROR_MESSAGE1=$(echo $?)
error "Error with the command: ${MAILMAN_BIN}/sync_members.\n${ERROR_MESSAGE1}.\nPlease try solving this with Mailman's man."
fi
else
error "${MAILMAN_BIN}/sync_members.\nCheck your Mailman installation."
fi
if [ -f ${MAILMAN_BIN}/config_list ]
then
# If list is not empty we add allowed senders (variable accept_these_nonmembers = [])
if [ -f ${LIST_CLEAN_SENDERS} ] && [[ ! -z $(cat ${LIST_CLEAN_SENDERS}) ]]
then
echo ""
# Saving actual configuration of the list
${MAILMAN_BIN}/config_list -o ${ACTUAL_LIST_CONFIG} ${LISTNAME}
# Checking if there is already some allowed non members
grep "accept_these_nonmembers = \['" ${ACTUAL_LIST_CONFIG} > /dev/null 2>&1
if [ $? -ne 0 ]
then
echo "No old list of secondary emails detected in the variable 'accept_these_nonmembers' in the Mailman list"
else
echo "Saving emails actually set in variable 'accept_these_nonmembers' in the Mailman list:"
LIST=$(grep "accept_these_nonmembers = " ${ACTUAL_LIST_CONFIG} | sed -e 's/.*\[\(.*\)\].*/\1/' | sed "s/,/\n/g" | sed "s/ //g" | sed "s/'//g")
echo ${LIST} | perl -p -e 's/ / - /g'
for OLD_ADDRESS in ${LIST}
do
echo ${OLD_ADDRESS} >> ${LIST_CLEAN_SENDERS}
done
fi
NEW_LIST_SENDERS=$(cat ${LIST_CLEAN_SENDERS} | grep '.' | sed '/^$/d' | awk '!x[$0]++')
echo "accept_these_nonmembers = [" > ${NEW_LIST_CONFIG_TEMP}
for NEW_ADDRESS in ${NEW_LIST_SENDERS}
do
echo "'${NEW_ADDRESS}', " >> ${NEW_LIST_CONFIG_TEMP}
done
echo "]" >> ${NEW_LIST_CONFIG_TEMP}
echo -e "\t-> New config:"
cat ${NEW_LIST_CONFIG_TEMP} | perl -p -e 's/\n//g' > ${NEW_LIST_CONFIG}
cat ${NEW_LIST_CONFIG}
echo -e "\nImporting new emails list in the variable 'accept_these_nonmembers' in the Mailman list"
${MAILMAN_BIN}/config_list -i ${NEW_LIST_CONFIG} ${LISTNAME}
if [ $? -ne 0 ]
then
ERROR_MESSAGE=$(echo $?)
error "Error while running command: ${MAILMAN_BIN}/config_list -i ${NEW_LIST_CONFIG} ${LISTNAME}.\n${ERROR_MESSAGE}."
else
echo -e "\t-> Import OK"
fi
fi
else
echo ""
error "Error while running command: ${MAILMAN_BIN}/config_list.\nPlease try solving this with Mailman's man or check your Mailman installation"
fi
echo ""
echo "****************************** FINAL RESULT ******************************"
echo -e "$0 finished for Mailman list ${LISTNAME}\n(LDAP group ${LDAPGROUP},${DNBASE})"
alldone 0