#!/bin/bash
# v1.12
# 2025-10-29

# Defaults

. /etc/ntools/mail-config

function usage() {
cat << EOF
usage $0 [options] [username | mail@address]

This script modify a mail user in LDAP

OPTIONS
  -h         Show this message
  -a <alias> Mail alias in <user@domain> format
             for replace -a "alias1[ alias2[ ...]]"
             for changes -a "+/-alias1[ +/-alias2[ ...]]"
  -r <addr>  Mail Recipient BCC in <user@domain> format, or '-' for delete
  -s <addr>  Mail Sender BCC in <user@domain> format, or '-' for delete
  -u <name>  System username (forexample: <user_domain>)
  -n <name>  Username
  -p <pass>  Password
  -d <dom>   Domain
  -D <dn>    LDAP Manager DN
  -w <pass>  LDAP Manager password
  -U <dn>    LDAP Users DN
  -G <dn>    LDAP Groups DN
  -M <dn>    LDAP Domains DN
  -b         Run in non-interactive mode
  -v         Verbose
  -e         Enable user
  -x         Disable user
EOF
	exit
}

function check_mail_address {
	local MA
	MA="$1"

	local ADDRESS
	ADDRESS=$(ldapsearch -x -w "${LDAP_BIND_PASSWORD}" -b "${LDAP_USERS_DN}" -D "${LDAP_BIND_DN}" -LLL "(&(objectClass=mailAccount)(mailAddress=${MA}))" mailAddress | grep -e '^mail')
	[ -n "${ADDRESS}" ] && { echo "Error! EMail $MA already exists."; exit 1; }

	# Check aliases
	ADDRESS=$(ldapsearch -x -w "${LDAP_BIND_PASSWORD}" -b "${LDAP_USERS_DN}" -D "${LDAP_BIND_DN}" -LLL "(&(objectClass=mailAccount)(mailAlias=${MA}))" mailAddress | grep -e '^mail' | cut -d':' -f2 | sort | xargs)
	[ -n "$ADDRESS" ] && { echo "Error! EMail ${MA} already used as alias for ${ADDRESS}."; exit 1; }
}

function get_password {
	local n
	n=8
	local PWGEN
	PWGEN=$(pwgen "$n" 2 | xargs | tr ' ' '-')
	echo "${PWGEN}"
}
LDIFDATA=''

function Add2LDIF {
	[ -n "${LDIFDATA}" ] && LDIFDATA="${LDIFDATA}-"
	PARAM="$1"
	[ -z "$PARAM" ] && return
	LDIFDATA="${LDIFDATA}add: ${PARAM}
"
	OLD_IFS="$IFS"
	IFS=' '
	read -ra A <<< "$2"
	IFS="$OLD_IFS"
	for var in "${A[@]}"; do
		LDIFDATA="${LDIFDATA}${PARAM}: $var
"
done
}

function Delete2LDIF {
	[ -n "${LDIFDATA}" ] && LDIFDATA="${LDIFDATA}-
"
	PARAM="$1"
	[ -z "$1" ] && return
	LDIFDATA="${LDIFDATA}delete: ${PARAM}
"
	OLD_IFS="$IFS"
	IFS=' '
	read -ra A <<< "$2"
	IFS="$OLD_IFS"
	for var in "${A[@]}"; do
		LDIFDATA="${LDIFDATA}${PARAM}: $var
"
done
}

function Replace2LDIF {
	[ -n "${LDIFDATA}" ] && LDIFDATA="${LDIFDATA}-
"
	LDIFDATA="${LDIFDATA}replace: $1
$1: $2
"
}

while getopts ":ha:r:s:u:n:p:d:D:w:U:G:M:bvex" OPTION; do
	case $OPTION in
		h)
			usage
		;;
		a)
			MAIL_ALIAS="$OPTARG"
		;;
		r)
			MAIL_RECIPIENT_BCC="$OPTARG"
		;;
		s)
			MAIL_SENDER_BCC="$OPTARG"
		;;
		u)
			SYS_USER_NAME="$OPTARG"
		;;
		n)
			USER_NAME="$OPTARG"
		;;
		p)
			USER_CLEARTEXT_PASS="$OPTARG"
		;;
		d)
			MAIL_DOMAIN="$OPTARG"
		;;
		D)
			LDAP_BIND_DN="$OPTARG"
		;;
		w)
			LDAP_BIND_PASSWORD="$OPTARG"
		;;
		U)
			LDAP_USERS_DN="$OPTARG"
		;;
		G)
			LDAP_GROUPS_DN="$OPTARG"
		;;
		M)
			LDAP_DOMAINS_DN="$OPTARG"
		;;
		b)
			IS_NIMODE=1
		;;
		v)
			IS_VERBOSE=1
		;;
		e)
			[ -n "$IS_ACTIVE" ] && usage
			IS_ACTIVE='TRUE'
			Replace2LDIF "active" "${IS_ACTIVE}"
			MODIFIED=1
		;;
		x)
			[ -n "$IS_ACTIVE" ] && usage
			IS_ACTIVE='FALSE'
			Replace2LDIF "active" "${IS_ACTIVE}"
			MODIFIED=1
		;;
		\?)
			echo "Invalid option: -$OPTARG" >&2
			usage
		;;
		:)
			echo "Option -$OPTARG requires an argument." >&2
			usage
		;;
	esac
done

shift $((OPTIND -1))

MAIL_ADDRESS="$1"

[ -z "$2" ] || usage

while [ -z "${LDAP_BIND_DN}" ]; do
	[ "${IS_NIMODE}" ] && { echo "Error! LDAP Bind DN missed.";exit 1; }
	read -rp "LDAP Manager dn: " LDAP_BIND_DN
done

if [ -z "${LDAP_BIND_PASSWORD}" ]; then
	read -rp "LDAP Manager Password: " -s LDAP_BIND_PASSWORD
	echo
fi

while [ -z "${LDAP_USERS_DN}" ]; do
	[ "${IS_NIMODE}" ] && { echo "Error! LDAP Users DN missed.";exit 1; }
	read -rp "LDAP Users dn: " LDAP_USERS_DN
done

while [ -z "${LDAP_GROUPS_DN}" ]; do
	[ "${IS_NIMODE}" ] && { echo "Error! LDAP Users DN missed.";exit 1; }
	read -rp "LDAP Groups dn: " LDAP_GROUPS_DN
done

while [ -z "${LDAP_DOMAINS_DN}" ]; do
	[ "${IS_NIMODE}" ] && { echo "Error! LDAP Domains DN missed.";exit 1; }
	read -rp "LDAP Domains dn: " LDAP_DOMAINS_DN
done

if [ -n "${MAIL_ADDRESS}" ]; then
	USER_NAME=$(echo "${MAIL_ADDRESS}" | cut -d@ -f1)
	[ -z "${USER_NAME}" ] && { echo "Error! ${MAIL_ADDRESS} - wrong email address.";exit 1; }
	[ -z "${MAIL_DOMAIN}" ] && MAIL_DOMAIN=$(echo "${MAIL_ADDRESS}" | cut -s -d@ -f2)
	[ -z "${MAIL_DOMAIN}" ] && MAIL_ADDRESS=''
fi

DOMAINS=$(ldapsearch -x -w "${LDAP_BIND_PASSWORD}" -b "${LDAP_DOMAINS_DN}" -D "${LDAP_BIND_DN}" -LLL "(objectClass=mailDomain)" mailDomain | grep -e '^mail' | cut -d':' -f2 | sort)

if [ -z "${DOMAINS}" ]; then
	echo "Mail domains not found. Please run 'maildomain add ...' first."
	exit 1
fi

if [ -n "${MAIL_DOMAIN}" ]; then
	for cdom in ${DOMAINS}; do
		[ "$cdom" == "${MAIL_DOMAIN}" ] && { DOMAINEXISTS=1; break; }
	done
	[ "${DOMAINEXISTS}" ] || { echo "Error! Domain ${MAIL_DOMAIN} not exists.";exit 1; }
else
	while [ -z "${MAIL_DOMAIN}" ]; do
		[ "${IS_NIMODE}" ] && { echo "Error! Mail Domain missed.";exit 1; }
		echo "Select mail domain from list:"
		DOMARR=()
		for cdom in ${DOMAINS}; do
			ACTIVE=$(ldapsearch -x -w "${LDAP_BIND_PASSWORD}" -b "${LDAP_DOMAINS_DN}" -D "${LDAP_BIND_DN}" -LLL "(&(objectClass=mailDomain)(mailDomain=${cdom}))" active | grep -e '^active' | cut -d':' -f2)
			STATE='unknown'
			[ "${ACTIVE}" == " TRUE" ] && STATE='active'
			[ "${ACTIVE}" == " FALSE" ] && STATE='inactive'
			echo "${#DOMARR[*]}: $cdom [${STATE}]"
			DOMARR+=("$cdom")
		done
		read -rp "Enter number of domain [0]: " MAIL_DOMAIN_NUMBER
		[ -z "${MAIL_DOMAIN_NUMBER}" ]  && MAIL_DOMAIN_NUMBER=0
		[[ "${MAIL_DOMAIN_NUMBER}" =~ ^[0-9]+$ ]] && MAIL_DOMAIN="${DOMARR[${MAIL_DOMAIN_NUMBER}]}"
	done
fi

while [ -z "${USER_NAME}" ]; do
	[ "${IS_NIMODE}" ] && { echo "Error! User name missed.";exit 1; }
	read -rp "User name for @${MAIL_DOMAIN}: " USER_NAME
done
USER_NAME="${USER_NAME,,}"

[ -z "${MAIL_ADDRESS}" ] && MAIL_ADDRESS="${USER_NAME}@${MAIL_DOMAIN}"
#check_mail_address "$MAIL_ADDRESS"

[ -z "${SYS_USER_NAME}" ] && SYS_USER_NAME="${USER_NAME}_${MAIL_DOMAIN}"

FINDNAME=$(ldapsearch -x -w "${LDAP_BIND_PASSWORD}" -b "${LDAP_USERS_DN}" -D "${LDAP_BIND_DN}" -LLL "(&(objectClass=mailAccount)(uid=${SYS_USER_NAME}))" uid | grep -e '^uid')
[ -z "$FINDNAME" ] && { echo "Error! User $SYS_USER_NAME not exists."; exit 1; }

if [ -z "${USER_CLEARTEXT_PASS}" ]; then
	if ! [ "${IS_NIMODE}" ]; then
		read -rp "Password for ${MAIL_ADDRESS} <Enter for nochange or 'gen' for pwgen>: " -s CUR_PASS
		echo
		if [ -n "${CUR_PASS}" ]; then
			MODIFIED=1
			if [ "${CUR_PASS}" == "gen" ]; then
				USER_CLEARTEXT_PASS=$(get_password)
				echo "New password: ${USER_CLEARTEXT_PASS}"
			else
				USER_CLEARTEXT_PASS="${CUR_PASS}"
			fi
		fi
	fi
fi

if [ -n "${USER_CLEARTEXT_PASS}" ]; then
	USER_PASS=$(slappasswd -h '{SSHA}' -s "${USER_CLEARTEXT_PASS}")
	CHANGE_DATE=$(($(date +%s)/86400))
	Replace2LDIF "userPassword" "${USER_PASS}"
	Replace2LDIF "shadowLastChange" "${CHANGE_DATE}"
fi
unset USER_CLEARTEXT_PASS

# Mail Aliases
MAIL_ALIAS_OLD=$(ldapsearch -o ldif-wrap=no -x -w "${LDAP_BIND_PASSWORD}" -b "${LDAP_USERS_DN}" -D "${LDAP_BIND_DN}" -LLL "(&(objectClass=mailAccount)(uid=${SYS_USER_NAME}))" mailAlias |grep -e '^mail' | cut -d':' -f2 | sed -e 's/^[[:space:]]*//' | sort | xargs)

if [ -z "${MAIL_ALIAS}" ]; then
	MAIL_ALIAS="${MAIL_ALIAS_OLD}"
else
	ADDA=$({ for a in ${MAIL_ALIAS}; do echo "$a"; done } | grep -e '^+' | xargs)
	REMA=$({ for a in ${MAIL_ALIAS}; do echo "$a"; done } | grep -e '^-' | xargs)

	AC=$(echo "${MAIL_ALIAS}" | wc -w)
	PC=$(echo "${ADDA} ${REMA}" | wc -w)
	if (( PC > 0 )); then
		if (( AC == PC )); then
			MAIL_ALIAS="${MAIL_ALIAS_OLD}"
			MAIL_ALIAS="$({ for a in ${MAIL_ALIAS}; do m=0; for r in ${REMA}; do if [ "$a" == "${r:1}" ]; then m=1; fi; done; if [[ "$m" -ne 1 ]]; then echo "$a"; fi; done; } | xargs)"
			MAIL_ALIAS="$({ for a in ${MAIL_ALIAS}; do echo "$a"; done; for a in ${ADDA}; do echo "${a:1}"; done; } | sort -u | xargs)"
		else
			echo "Error! Wrong aliases!"
			usage;
		fi
	fi
fi
if ! [ "${IS_NIMODE}" ]; then
	READTEXT="x"
	while [ -n "${READTEXT}" ]; do
		read -rp "Aliases [${MAIL_ALIAS}]: " READTEXT
		[ -z "${READTEXT}" ] && continue
		READTEXT="${READTEXT,,}"
		if [[ "${READTEXT}" =~ ^\- ]]; then
			READTEXT="$(echo "${READTEXT}" | cut -f1 -d' ')"
			MAIL_ALIAS="$({ for a in ${MAIL_ALIAS}; do if [ "$a" != "${READTEXT:1}" ]; then echo "$a"; fi; done; } | xargs)"
		elif [[ "${READTEXT}" =~ ^\+ ]]; then
			READTEXT="$(echo "${READTEXT}" | cut -f1 -d' ')"
			MAIL_ALIAS="$({ for a in ${MAIL_ALIAS}; do echo "$a"; done; echo "${READTEXT:1}"; } | sort -u | xargs)"
		else
			MAIL_ALIAS="$({ for a in ${MAIL_ALIAS}; do echo "$a"; done; for a in ${READTEXT}; do echo "$a"; done; } | sort -u | xargs)"
		fi
	done
fi

ALIST="$(diff -u <(for a in ${MAIL_ALIAS_OLD}; do echo "$a"; done | sort) <(for a in ${MAIL_ALIAS}; do echo "$a"; done | sort) | grep -e '^+' | cut -c2- | tail -n +2 | xargs)"
RLIST="$(diff -u <(for a in ${MAIL_ALIAS_OLD}; do echo "$a"; done | sort) <(for a in ${MAIL_ALIAS}; do echo "$a"; done | sort) | grep -e '^-' | cut -c2- | tail -n +2 | xargs)"

if [ -n "$ALIST" ]; then
	MODIFIED=1
	AMODIFIED=1
	Add2LDIF "mailAlias" "${ALIST}"
fi
if [ -n "$RLIST" ]; then
	MODIFIED=1
	AMODIFIED=1
	Delete2LDIF "mailAlias" "${RLIST}"
fi

if ! [ "$IS_NIMODE" ]; then
	SN=$(ldapsearch -o ldif-wrap=no -x -w "${LDAP_BIND_PASSWORD}" -b "${LDAP_USERS_DN}" -D "${LDAP_BIND_DN}" -LLL "(&(objectClass=mailAccount)(uid=${SYS_USER_NAME}))" sn | grep -e '^sn' | cut -d: -f2-)
	if [ "${SN:0:1}" == ':' ]; then
	        SN=$(echo "${SN:2}" | base64 -d)
	else
	        SN="${SN:1}"
	fi
	read -rp "Фамилия [${SN}]: " READTEXT
	if [ -n "${READTEXT}" ]; then
		SN="${READTEXT}"
		MODIFIED=1
		DISPNMODIFIED=1
		Replace2LDIF "sn" "${SN}"
	fi

	GN=$(ldapsearch -o ldif-wrap=no -x -w "${LDAP_BIND_PASSWORD}" -b "${LDAP_USERS_DN}" -D "${LDAP_BIND_DN}" -LLL "(&(objectClass=mailAccount)(uid=${SYS_USER_NAME}))" gn | grep -e '^givenName' | cut -d: -f2-)
	if [ "${GN:0:1}" == ':' ]; then
		GN=$(echo "${GN:2}" | base64 -d)
	else
		GN="${GN:1}"
	fi
	read -rp "Имя [${GN}]: " READTEXT
	if [ -n "${READTEXT}" ]; then
		if [ "${READTEXT}" == "-" ]; then
			GN=""
		else
			GN="${READTEXT}"
		fi
		MODIFIED=1
		DISPNMODIFIED=1
		if [ -n "${GN}" ]; then
			Replace2LDIF "givenName" "${GN}"
		else
			Delete2LDIF "givenName"
		fi
	fi

	INI=$(ldapsearch -o ldif-wrap=no -x -w "${LDAP_BIND_PASSWORD}" -b "${LDAP_USERS_DN}" -D "${LDAP_BIND_DN}" -LLL "(&(objectClass=mailAccount)(uid=${SYS_USER_NAME}))" initials | grep -e '^initials' | cut -d: -f2-)
	if [ "${INI:0:1}" == ':' ]; then
		INI=$(echo "${INI:2}" | base64 -d)
	else
		INI="${INI:1}"
	fi
	read -rp "Отчество [${INI}]: " READTEXT
	if [ -n "${READTEXT}" ]; then
		if [ "${READTEXT}" == "-" ]; then
			INI=""
		else
			INI="${READTEXT}"
		fi
		MODIFIED=1
		DISPNMODIFIED=1
		if [ -n "${INI}" ]; then
			Replace2LDIF "initials" "${INI}"
		else
			Delete2LDIF "initials"
		fi
	fi

	if [ -z "${DISPNMODIFIED}" ]; then
		DISPN=$(ldapsearch -o ldif-wrap=no -x -w "${LDAP_BIND_PASSWORD}" -b "${LDAP_USERS_DN}" -D "${LDAP_BIND_DN}" -LLL "(&(objectClass=mailAccount)(uid=${SYS_USER_NAME}))" displayName | grep -e '^displayName' | cut -d: -f2-)
		if [ "${DISPN:0:1}" == ':' ]; then
			DISPN=$(echo "${DISPN:2}" | base64 -d)
		else
			DISPN="${DISPN:1}"
		fi
		read -rp "Отображаемое имя [${DISPN}]: " READTEXT
		if [ -n "${READTEXT}" ]; then
			DISPN="${READTEXT}"
			MODIFIED=1
			if [ -n "${DISPN}" ]; then
				Replace2LDIF "displayName" "${DISPN}"
			else
				Delete2LDIF "displayName"
			fi
		fi
	else
		DISPN="${SN} ${GN} ${INI}"
		#Remove squeeze repeat spaces
		DISPN=$(echo "${DISPN}" | xargs)
		read -rp "Отображаемое имя [${DISPN}]: " READTEXT
		if [ -n "${READTEXT}" ]; then
			DISPN="${READTEXT}"
		fi
		MODIFIED=1
		if [ -n "${DISPN}" ]; then
			Replace2LDIF "displayName" "${DISPN}"
		else
			Delete2LDIF "displayName"
		fi
	fi

	MOBILE=$(ldapsearch -o ldif-wrap=no -x -w "${LDAP_BIND_PASSWORD}" -b "${LDAP_USERS_DN}" -D "${LDAP_BIND_DN}" -LLL "(&(objectClass=mailAccount)(uid=${SYS_USER_NAME}))" mobile | grep -e '^mobile' | cut -d: -f2-)
	if [ "${MOBILE:0:1}" == ':' ]; then
		MOBILE=$(echo "${MOBILE:2}" | base64 -d)
	else
		MOBILE="${MOBILE:1}"
	fi
	read -rp "mobile [${MOBILE}]: " READTEXT
	if [ -n "${READTEXT}" ]; then
		if [ "${READTEXT}" == "-" ]; then
			Delete2LDIF "mobile"
		else
			Replace2LDIF "mobile" "${READTEXT}"
		fi
		MODIFIED=1
	fi

	DESCRIPTION=$(ldapsearch -o ldif-wrap=no -x -w "$LDAP_BIND_PASSWORD" -b "${LDAP_USERS_DN}" -D "${LDAP_BIND_DN}" -LLL "(&(objectClass=mailAccount)(uid=$SYS_USER_NAME))" description | grep -e '^description' | cut -d: -f2-)
	if [ "${DESCRIPTION:0:1}" == ':' ]; then
		DESCRIPTION=$(echo "${DESCRIPTION:2}" | base64 -d)
	else
		DESCRIPTION="${DESCRIPTION:1}"
	fi
	read -rp "Description [${DESCRIPTION}]: " READTEXT
	if [ -n "${READTEXT}" ]; then
		MODIFIED=1
		if [ "${READTEXT}" == "-" ]; then
			Delete2LDIF "description"
		else
			Replace2LDIF "description" "${READTEXT}"
		fi
	fi

	RBCC=$(ldapsearch -o ldif-wrap=no -x -w "$LDAP_BIND_PASSWORD" -b "${LDAP_USERS_DN}" -D "${LDAP_BIND_DN}" -LLL "(&(objectClass=mailAccount)(uid=$SYS_USER_NAME))" mailRecipientBCC | grep -e '^mailRecipientBCC' | cut -d: -f2-)
	if [ "${RBCC:0:1}" == ':' ]; then
	        RBCC=$(echo "${RBCC:2}" | base64 -d)
	else
	        RBCC="${RBCC:1}"
	fi
	read -rp "RecipientBCC [${RBCC}]: " READTEXT
	if [ -n "${READTEXT}" ]; then
		MODIFIED=1
		if [ "${READTEXT}" == "-" ]; then
			Delete2LDIF "RecipientBCC"
		else
			if [ -n "${RBCC}" ]; then
				Replace2LDIF "mailRecipientBCC" "${READTEXT}"
			else
				Add2LDIF "mailRecipientBCC" "${READTEXT}"
			fi
		fi
	fi

	SBCC=$(ldapsearch -o ldif-wrap=no -x -w "$LDAP_BIND_PASSWORD" -b "${LDAP_USERS_DN}" -D "${LDAP_BIND_DN}" -LLL "(&(objectClass=mailAccount)(uid=$SYS_USER_NAME))" mailSenderBCC | grep -e '^mailSenderBCC' | cut -d: -f2-)
	if [ "${SBCC:0:1}" == ':' ]; then
	        SBCC=$(echo "${SBCC:2}" | base64 -d)
	else
	        SBCC="${SBCC:1}"
	fi
	read -rp "SenderBCC [${SBCC}]: " READTEXT
	if [ -n "${READTEXT}" ]; then
		MODIFIED=1
		if [ "${READTEXT}" == "-" ]; then
			Delete2LDIF "SenderBCC"
		else
			if [ -n "${SBCC}" ]; then
				Replace2LDIF "mailSenderBCC" "${READTEXT}"
			else
				Add2LDIF "mailSenderBCC" "${READTEXT}"
			fi
		fi
	fi

fi

[ -z "$MODIFIED" ] && { echo "Mail account not modified"; exit; }

LDIF="dn: uid=${SYS_USER_NAME},${LDAP_USERS_DN}
changetype: modify
$LDIFDATA"

if [ "$IS_VERBOSE" ]; then
	echo "--------------------"
	echo "Modifying ${LDIF}"
	echo "--------------------"
fi

if [ -n "$AMODIFIED" ]; then
	if [ -n "${MAIL_ALIAS_OLD}" ]; then
		echo "Old aliases: ${MAIL_ALIAS_OLD}"
	fi
fi
RESULT=$(echo "$LDIF" | ldapmodify -x -w "${LDAP_BIND_PASSWORD}" -D "${LDAP_BIND_DN}")
[ "$IS_VERBOSE" ] && echo "$RESULT"
unset LDAP_BIND_PASSWORD


