HEX
Server: Apache/2.2.34 (Unix) mod_fastcgi/mod_fastcgi-SNAP-0910052141
System: Linux Kou-Etsu-Dou 4.4.59+ #25556 SMP PREEMPT Thu Mar 4 18:03:46 CST 2021 x86_64
User: hosam (1026)
PHP: 7.2.29
Disabled: NONE
Upload Files
File: /volume1/@appstore/MailPlus-Server/hook/ShareMailboxHook.sh
#!/bin/bash

#Include common scripts
. /var/packages/MailPlus-Server/target/backend_hook/hookUtils.conf

turnToLower()
{
	echo $@ | awk '{print tolower($0)}'
}

handleDovecotAcl()
{
	local isUserPattern=$1
	local isDeleteHook=$2
	local sharee=$3
	local newSharee=$4
	local newSharer=$5
	local mailbox=$6
	local utf8Mailbox
	local doveadm="${PRIVATE_LOCATION}/bin/doveadm"
	local mailboxPath
	local dovecotAcl
	local aclId
	local newAclId
	local aclRight

	if [[ "${mailbox^^}" != "INBOX" ]]; then
		mailboxPath=".${mailbox}"
	fi
	utf8Mailbox="$(${doveadm} mailbox mutf7 -7 "${mailbox}")"

	dovecotAcl="${sharerMaildir}/Maildir/${mailboxPath}/dovecot-acl"
	if [ ! -f "${dovecotAcl}" ]; then
		return 0
	fi

	if [ "${isUserPattern}" == 1 ]; then
		aclId="user=${sharee}"
		newAclId="user=${newSharee}"
	else
		aclId="group=${sharee}"
		newAclId="group=${newSharee}"
	fi
	while IFS= read -r aclLine
	do
		if [[ "${aclLine}" != "${aclId} "* ]]; then
			continue
		fi

		#remove old item
		"${doveadm}" acl delete -u "${newSharer}" "${utf8Mailbox}" "${aclId}" || err_log "failed to remove ${aclId} of ${utf8Mailbox} for ${newSharer}"

		if [ "${isDeleteHook}" -eq 1 ]; then
			continue
		fi

		#add new item
		aclRight=$(echo "${aclLine}" | cut -d ' ' -f 2 | grep -o . | sort | tr -d "\n") # get right and sort by alphabetic order
		if [ "${aclRight}" == "lrs" ]; then
			#Read Only
			"${doveadm}" acl set -u "${newSharer}" "${utf8Mailbox}" "${newAclId}" lookup read write-seen || err_log "failed to add ${newSharee} RO right of ${utf8Mailbox} for ${newSharer}"
		elif [ "${aclRight}" == "eiklprstw" ]; then
			#Read Write
			"${doveadm}" acl set -u "${newSharer}" "${utf8Mailbox}" "${newAclId}" create expunge insert lookup post read write write-deleted write-seen || err_log "failed to add ${newSharee} RW right of ${utf8Mailbox} for ${newSharer}"
		elif [ "${aclRight}" == "aeiklprstwx" ]; then
			"${doveadm}" acl set -u "${newSharer}" "${utf8Mailbox}" "${newAclId}" admin create delete expunge insert lookup post read write write-deleted write-seen || err_log "failed to add ${newSharee} admin right of ${utf8Mailbox} for ${newSharer}"
			#Admin
		else
			#unknown setting
			err_log "unknown right ${aclRight} of ${newSharee} of ${utf8Mailbox} for ${newSharer}"
			continue
		fi
	done < "${dovecotAcl}"
}

handleDovecotAclList()
{
	local isUserPattern=$1
	local isDeleteHook=$2
	local sharee=$3
	local newSharee=$4
	local newSharer=$5
	local sharerMaildir
	local dovecotAclList
	local mailbox

	sharerMaildir=$(${MAILPLUS_SERVER_BACKEND_BINARY} --getMailDir "${newSharer}")

	if [ $? -ne 0 ]; then
		err_log "failed to get maildir for user [${newSharer}]"
		return 1
	fi

	dovecotAclList="${sharerMaildir}/Maildir/dovecot-acl-list"
	if [ -n "${sharerMaildir}" -a -f "${dovecotAclList}" ]; then
		#sharer sharing file exist
		while IFS= read -r mailboxLine
		do
			mailbox=$(echo "${mailboxLine}" | cut -d ' ' -f 2-) #format 1609430400 INBOX
			handleDovecotAcl "${isUserPattern}" "${isDeleteHook}" "${sharee}" "${newSharee}" "${newSharer}" "${mailbox}"
		done < "${dovecotAclList}"
	fi
}

handlUserSubAndPidxFile()
{
	local isDeleteHook=$1
	local newSharee=$2
	local sharerShortName=$3
	local newSharer=$4
	local escappedSharer=$5
	local escappedNewSharer=$6
	local doveadm="${PRIVATE_LOCATION}/bin/doveadm"
	local shareeMaildir
	local subscriptionFile
	local subscriptionLine
	local oldPath
	local newPath
	local mailbox
	local utf8Mailbox

	shareeMaildir=$(${MAILPLUS_SERVER_BACKEND_BINARY} --getMailDir "${newSharee}")
	if [ ! -d "${shareeMaildir}/Maildir" ]; then
		return
	fi

	#handle subscription
	subscriptionFile="${shareeMaildir}/Maildir/subscriptions"
	if [ -e "${subscriptionFile}" ]; then
		while IFS= read -r subscriptionLine
		do
			#unsubscribe and subscribe
			mailbox="${subscriptionLine#synology_internal_shared_namespace.$escappedSharer.}"
			utf8Mailbox="$(${doveadm} mailbox mutf7 -7 "${mailbox}")"
			oldPath="${subscriptionLine:0:-${#mailbox}}${utf8Mailbox}"
			"${doveadm}" mailbox unsubscribe -u "${newSharee}" "${oldPath}" || err_log "failed to unsubscribe [${oldPath}] for user [${newSharee}]"
			if [ "${isDeleteHook}" -eq 0 ]; then
				newPath="synology_internal_shared_namespace.${escappedNewSharer}.${utf8Mailbox}"
				"${doveadm}" mailbox subscribe -u "${newSharee}" "${newPath}" || err_log "failed to subscribe [${newPath}] for user [${newSharee}]"
			fi
		done < <(grep -iP "synology_internal_shared_namespace\t${escappedSharer}\t" "${subscriptionFile}" | sed 's/\t/./g')
	fi

	#handle personal index
	if [ -d "${shareeMaildir}/Maildir/shared/${sharerShortName}" ]; then
		if [ "${isDeleteHook}" -eq 0 ]; then
			mv "${shareeMaildir}/Maildir/shared/${sharerShortName}" "${shareeMaildir}/Maildir/shared/${newSharer}" || err_log "failed to move ${shareeMaildir}/Maildir/shared/${sharerShortName} to ${shareeMaildir}/Maildir/shared/${newSharer}"
		elif [[ "$(realpath "${shareeMaildir}/Maildir/shared/${sharerShortName}")" == "$(realpath "${shareeMaildir}")"* ]]; then
			#check path is under Maildir before rm
			rm -rf "${shareeMaildir}/Maildir/shared/${sharerShortName}" || err_log "failed to remove ${shareeMaildir}/Maildir/shared/${sharerShortName}"
		fi
	fi

}



#handle subscription and personal index for shared mailbox
handleSubAndPidx()
{
	local isUserPattern=$1
	local isDeleteHook=$2
	local newSharee=$3
	local sharer=$4
	local newSharer=$5
	local escappedSharer=${sharer//./\/}
	local escappedNewSharer=${newSharer//./\/}
	local sharereMaildir
	local subscriptionFile
	local userEnumTarget
	local gid
	local lowerCaseName

	escappedSharer=${escappedSharer//\\/\\\\}

	if [ "${SERVER_TYPE}" == "ldap" ]; then
		#remove all character after @ to make it as the shortname
		sharer=${sharer%@*}
		escappedSharer=${escappedSharer%@*}
		newSharer=${newSharer%@*}
		escappedNewSharer=${escappedNewSharer%@*}
	fi

	if [ "${isUserPattern}" -eq 1 ]; then
		handlUserSubAndPidxFile "${isDeleteHook}" "${newSharee}" "${sharer}" "${newSharer}" "${escappedSharer}" "${escappedNewSharer}"
	else
		if [ -z "${SERVER_TYPE}" -a "${newSharee}" == "users" ]; then
			#local group users is special
			userEnumTarget=local
		elif [ "${SERVER_TYPE}" == "domain" ]; then
			gid=$(synogroup -get "${newSharee}" | grep "Group ID:\s*\[[0-9]*\]" | sed 's/^.*\[\([0-9]*\)\]$/\1/')
			lowerCaseName=$(echo "${newSharee}" | awk '{print tolower($0)}')
			if [ -n "${gid}" -a "$((${gid} & 0x1FFFFF))" -eq "513" ] || [[ "${lowerCaseName}" == *"\\domain users" ]]; then
				userEnumTarget=domain
			fi
		fi

		if [ -n "${userEnumTarget}" ]; then
			while IFS= read -r user
			do
				handlUserSubAndPidxFile "${isDeleteHook}" "${user}" "${sharer}" "${newSharer}" "${escappedSharer}" "${escappedNewSharer}"
			done < <(synouser -enum "${userEnumTarget}" | tail -n +2)
		else
			while IFS= read -r member
			do
				handlUserSubAndPidxFile "${isDeleteHook}" "${member}" "${sharer}" "${newSharer}" "${escappedSharer}" "${escappedNewSharer}"
			done < <(/usr/syno/sbin/synogroup -get "${newSharee}" | sed -n '/Group Members:/ { :a; n; p; ba; }' | sed 's/^[0-9]\{0,\}:\[\([^]]*\)\]$/\1/')
		fi
	fi
}

# $1 hook type
#	1:Local User Set
#	2:Local User Delete
#	3:Ldap/Domain User Set
#	4:Ldap/Domain User Delete
#	5:Local Group Set
#	6:Local Group Detele
#	7:Ldap/Domain Group Rename
#	8:Ldap/Domain Group Delete
UpdateShareMailbox()
{
	local hookType=$1
	local isUserHook=1
	local isDeleteHook=0
	local shareMailBoxSetting="$(GetMailAccountBaseDir)/db/shared-mailboxes"
	local hook_variable
	local org_name
	local new_name
	local orgPrefix
	local opResultPrefix="FAKE_OP_RESULT_"

	if [ ! -e "${shareMailBoxSetting}" ]; then
		return 0
	fi

	declare -A nameMap
	#For set/rename hook, name map record old name to new name relationship.
	#For delete hook, it is used as set to record whether the target is deleted

	if [ "${hookType}" -ne 5 ]; then
		if [ "${hookType}" -ge 5 ]; then
			#type 6, 7, 8
			isUserHook=0
			hook_variable=GROUP
		else
			hook_variable=USER
		fi

		if [ "$((hookType % 2))" -eq 0 ]; then
			#type 2, 4, 6, 8
			isDeleteHook=1
		else
			orgPrefix="ORIGIN_"
		fi

		if [ "${hookType}" -le 2 -o "${hookType}" -eq 6 ]; then
			#type 1, 2, 6
			opResultPrefix="${hook_variable}_OP_RESULT_"
		fi

		#generate old name => new name map
		local idx=1
		local cnt=$((NITEMS + 1))
		while [ "${idx}" != "${cnt}" ]
		do
			eval "org_name=\$${orgPrefix}${hook_variable}_NAME_${idx}" #for delete hook org_name would be the same as new_name
			eval "new_name=\$${hook_variable}_NAME_${idx}"
			eval "op_result=\$${opResultPrefix}_${idx}" # [ FAKE_OP_RESULT_{n} != 1 ] is true, for ldap/domain hook, it is always successful
			if [ "${op_result}" != 1 -a -n "${org_name}" -a -n "${new_name}" ]; then
				nameMap["$(turnToLower ${org_name})"]="${new_name}"
			fi
			idx=$((idx + 1))
		done
	else
		#type 5
		if [ "${RESULT}" -ne 0 ]; then
			#local group set fail, just return
			return 0
		elif [ -z "${ORIGIN_GROUP_NAME}" ]; then
			#new group added, just return
			return 0
		fi
		isUserHook=0
		nameMap["$(turnToLower ${ORIGIN_GROUP_NAME})"]="${GROUP_NAME}"
	fi

	#parse and handle shared-mailboxes setting
	local doveadm="${PRIVATE_LOCATION}/bin/doveadm"
	local userPattern="shared/shared-boxes/user/"
	local userPatternLen=${#userPattern}
	local groupPattern="shared/shared-boxes/group/"
	local groupPatternLen=${#groupPattern}
	local pattern
	local patternLen=0
	local isUserPattern
	local setting
	local sharee
	local sharer
	local newSharee
	local newSharer

	while IFS= read -r settingLine
	do
		isUserPattern=1
		if [[ "${settingLine}" == "${userPattern}"* ]]; then
			pattern=${userPattern}
			patternLen=${userPatternLen}
		elif [[ "${settingLine}" == "${groupPattern}"* ]]; then
			isUserPattern=0
			pattern=${groupPattern}
			patternLen=${groupPatternLen}
		else
			#is unknown key or value
			continue
		fi

		setting=${settingLine:patternLen} # extract sharee/sharer part
		sharee=$(echo "${setting}" | cut -d '/' -f 1) #we assum there is no / in user/group name
		sharer=$(echo "${setting}" | cut -d '/' -f 2-)
		newSharee=${sharee}
		newSharer=${sharer}

		if [ "${isDeleteHook}" -eq 0 ]; then
			#set/rename hook
			if [ "${isUserHook}" -eq 1 -a -n "${nameMap[$(turnToLower $sharer)]}" ]; then
				newSharer=${nameMap[$(turnToLower $sharer)]}
			fi

			if [ "${isUserHook}" == "${isUserPattern}" ]; then
				#user/group is changed and user change matches user rule, group change matches group rule
				if [ -n "${nameMap[$(turnToLower $sharee)]}" ]; then
					#sharer must be a user, so only map to new name if it is user hook
					newSharee=${nameMap[$(turnToLower $sharee)]}
				fi
				if [ "${newSharee}" != "${sharee}" ]; then
					#sharee name changed
					handleDovecotAclList "${isUserPattern}" "${isDeleteHook}" "${sharee}" "${newSharee}" "${newSharer}"
				fi
			fi

			if [ "${isUserHook}" -eq 1 -a "${sharer}" != "${newSharer}" ]; then
				#sharer name changed. sharer must be user
				"${doveadm}" dict unset "file:${shareMailBoxSetting}" "${settingLine}" || err_log "failed to remove old sharer setting ${settingLine}"
				"${doveadm}" dict set "file:${shareMailBoxSetting}" "${pattern}${newSharee}/${newSharer}" 1 || err_log "failed to add sharer setting ${${pattern}${newSharee}/${newSharer}}"
				handleSubAndPidx "${isUserPattern}" "${isDeleteHook}" "${newSharee}" "${sharer}" "${newSharer}"
			fi
		else
			#delete hook
			if [ "${isUserHook}" -ne 1 -o -z "${nameMap[$(turnToLower $sharer)]}" ]; then
				#sharer is not delete
				newSharer=
			fi

			if [ "${isUserHook}" != "${isUserPattern}" -o -z "${nameMap[$(turnToLower $sharee)]}" ]; then
				#sharee is not deleted
				newSharee=
			fi

			if [ "${isUserHook}" == "${isUserPattern}" -a -n "${newSharee}" ]; then
				#user/group is delete and user delete matches user rule, group delete matches group rule
				#sharee is deleted
				if [ -z "${newSharer}" ]; then
					#sharer is not deleted, or skip handling
					handleDovecotAclList "${isUserPattern}" "${isDeleteHook}" "${sharee}" "${newSharee}" "${sharer}"
				fi
			fi

			if [ "${isUserHook}" -eq 1 -a "${sharer}" == "${newSharer}" ]; then
				#sharer is deleted
				"${doveadm}" dict unset "file:${shareMailBoxSetting}" "${settingLine}" || err_log "failed to remove old sharer setting ${settingLine}"
				if [ -z "${newSharee}" ]; then
					#sharee is not deleted
					handleSubAndPidx "${isUserPattern}" "${isDeleteHook}" "${sharee}" "${sharer}" "${newSharer}"
				fi
			fi
		fi
	done < "${shareMailBoxSetting}"
}