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}"
}