#!/bin/sh
#
# dbmail_acl.sh - Manage ACLs for shared mailboxes
#
# Any system that this script is expected to interact with should have a 
# dbmail configuration file listed below
#
# Adam Kosmin, 8/10/06
# Stef Bon, 20/07/08
#
# :set ts=2
#
#

#    This file is dbmail-acl.sh.

#    dbmail-acl.sh is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.

#    dbmail-acl.sh is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.

#    You should have received a copy of the GNU General Public License
#    along with dbmail-acl.sh.  If not, see <http://www.gnu.org/licenses/>.


script_version=0.8

# Changelog
# 0.8 - add support for subscribing to other folders than INBOX
#     - add listing mailboxes owned by an user
# 0.7 - column now used for formatting
# 0.6 - list operation now displays orphaned users and mailboxes ACLs by default.

_dbmail_mysqldatabase=1
_dbmail_mysqluser=2
_dbmail_mysqlpassword=3
_dbmail_mysqlhost=4
_dbmail_conffile=5
_dbmail_driver=6

DBMAIL[_dbmail_mysqldatabase]=""
DBMAIL[_dbmail_mysqluser]=""
DBMAIL[_dbmail_mysqlpassword]=""
DBMAIL[_dbmail_mysqlhost]=""
DBMAIL[_dbmail_conffile]="/etc/dbmail.conf"
DBMAIL[_dbmail_driver]=""

MYSQL_CLIENT=""

scriptname=$(basename $0)


usage()
{
	echo "$scriptname $script_version"
	echo "Usage:"
	echo "$scriptname add|del user owner [mailbox]"
	echo " Mailbox (like INBOX, Trash and Sent) of owner for user to get access to/remove access from,"
	echo " defaults to INBOX"
	echo " To get access to all mailboxes of owner, enter an asterix :"
	echo " $scriptname add admin sbon '*'"
	echo " for example will subscribe and add access for admin to all mailboxes of sbon"
	echo " (quotes or a backslash is necessary, to avoid expansion in the shell)"
	echo ""
	echo "$scriptname list [command]"
	echo " command is acl or mailbox. If command is omited, acl is the default, compatible with version 0.7"
	echo ""
	echo "$scriptname list acl [userid]"
	echo " List ACL's for userid. If no userid show all."
	echo ""
	echo "$scriptname list mailbox [userid]"
	echo " List all mailboxes for userid. If no userid, then all mailboxes."
	echo ""
	exit 1
}

get_user_idnr()
{
local userid=$1

    # determine the numerical id of username
    # note that this could also be done direct via the database witha query
    
    $DBMAIL_USERS -l $userid | awk -F: '{print $3}'
    
}

function get_user_id()
{
local user_idnr=$1

# return the username of user with idnr

$MYSQL_CLIENT -N -e "SELECT userid FROM dbmail_users WHERE user_idnr = $user_idnr;"

}

function get_mailbox_idnr()
#
# returns the mailbox_idnr(s) given the idnr of the owner and the name of the mailbox
# if mailboxname is not empty this will result in an unique nr
# if mailboxname is empty this will return all nrs of mailboxes of owner
#
{
local owner_idnr=$1
local name=$2

if [ -z "$name" ]; then

    name="INBOX"
    
    
fi
    
if [ "$name" = "*" ]; then


    $MYSQL_CLIENT -N -e "SELECT mailbox_idnr FROM dbmail_mailboxes WHERE owner_idnr = $owner_idnr;"
    
else

    $MYSQL_CLIENT -N -e "SELECT mailbox_idnr FROM dbmail_mailboxes WHERE name = '$name' and owner_idnr = $owner_idnr;"

fi

}

function get_mailbox_name()
#
# returns the name of the mailbox given the mailbox_idnr
#
{
local mailbox_idnr=$1

$MYSQL_CLIENT -N -e "SELECT name FROM dbmail_mailboxes WHERE mailbox_idnr = $mailbox_idnr;"


}

function get_mailbox_names()
#
# returns the names of the mailboxes given the owner idnr. Generally it should return more than one name.
# 
{
local owner_idnr=$1

$MYSQL_CLIENT -N -e "SELECT name FROM dbmail_mailboxes WHERE owner_idnr = $owner_idnr;"


}

function get_mailbox_owner_idnr()
#
# returns the owner idnr given the mailbox idnr
# 
{
local mailbox_idnr=$1

$MYSQL_CLIENT -N -e "SELECT owner_idnr FROM dbmail_mailboxes WHERE mailbox_idnr = $mailbox_idnr;"


}

function get_mailbox_owner_name()
# 
# returns the name of the owner given the mailbox_idnr
# (a link between two tables is used here, the data is not in one table) 
{
local mailbox_idnr=$1

$MYSQL_CLIENT -N -e "SELECT userid FROM dbmail_users du, dbmail_mailboxes dm WHERE du.user_idnr = dm.owner_idnr and dm.mailbox_idnr = $mailbox_idnr;"

}


function get_mailbox_acl_users()
#
# returns the user_idnrs with acls for a mailbox_idnr
#
{
local mailbox_idnr=$1

$MYSQL_CLIENT -N -e "SELECT user_id FROM dbmail_acl where mailbox_id = $mailbox_idnr;"

}

function get_all_mailboxes_with_acl()
{
# get all the mailboxes with acls
# simply go to the dbmail_acl table

# this is better since it will just grab all mailbox ids listed in the dbmail_acl table
# unfortunatley, we won't be able to sort alphabetically like we did before.

$MYSQL_CLIENT -N -e "SELECT DISTINCT mailbox_id FROM dbmail_acl ORDER BY mailbox_id;"

}

function get_all_acls()
{
#
# returns the acl flags for a certain user_idnr to a mailbox_idnr
#
local mailbox_idnr=$1
local user_idnr=$2

$MYSQL_CLIENT -N -e "SELECT lookup_flag, read_flag, seen_flag, write_flag, insert_flag, post_flag, create_flag, delete_flag, administer_flag FROM dbmail_acl WHERE mailbox_id = $mailbox_idnr and user_id = $user_idnr;"

}


acl_add()
#
# add all the rights for a user (user_idnr) to a mailbox (mailbox_idnr)
#
# to do: add different roles, like admin (=full control), guest (=only read) and backup (=read and delete) 
#
{
local user_idnr=$1
local mailbox_idnr=$2

# first check the acl does not exist already

if [ -z "$(get_all_acls $mailbox_idnr $user_idnr)" ]; then

    echo "Adding (full) acl rights for $user_idnr to $mailbox_idnr."

    $MYSQL_CLIENT -e "INSERT INTO dbmail_acl values ($user_idnr, $mailbox_idnr, 1, 1, 1, 1, 1, 1, 1, 1, 1);"
    
else

    echo "(full) ACL already exists."
    
fi

}

acl_del()
{
local user_idnr=$1
local mailbox_idnr=$2

# first check the acl does exists

if [ -n "$(get_all_acls $mailbox_idnr $user_idnr)" ]; then

    echo "Removing (full) acl rights for $user_idnr to $mailbox_idnr."

    $MYSQL_CLIENT -e "DELETE FROM dbmail_acl WHERE user_id = $user_idnr and mailbox_id = $mailbox_idnr;"
    
else

    echo "(full) ACL does not exist."
    
fi

}

acl_purge()
{
local user_idnr=$1

$MYSQL_CLIENT -e "DELETE FROM dbmail_acl WHERE user_id = $user_idnr;"

}

function get_subscription()
{
local user_idnr=$1
local mailbox_idnr=$2

$MYSQL_CLIENT -N -e "SELECT * from dbmail_subscription WHERE user_id = $user_idnr and mailbox_id = $mailbox_idnr;"

}

subscription_add()
{
local user_idnr=$1
local mailbox_idnr=$2

if [ -z "$(get_subscription $user_idnr $mailbox_idnr)" ]; then

    echo "Adding subscription of $user_idnr to $mailbox_idnr."

    $MYSQL_CLIENT -e "INSERT INTO dbmail_subscription VALUES ($user_idnr, $mailbox_idnr);"
    
else

    echo "Subscription already exists."
    
fi
}

subscription_del()
{
local user_idnr=$1
local mailbox_idnr=$2

if [ -n "$(get_subscription $user_idnr $mailbox_idnr)" ]; then

    echo "Deleting subscription of $user_idnr from $mailbox_idnr."

    $MYSQL_CLIENT -e "DELETE FROM dbmail_subscription WHERE user_id = $user_idnr and mailbox_id = $mailbox_idnr;"
    
else

    echo "Subscription does not exists."
    
fi
}

subscription_purge()
{
local user_idnr=$1

$MYSQL_CLIENT -e "DELETE FROM dbmail_subscription WHERE user_id = $user_idnr;"
}

acl_list()
{
local userid=$1

# get all the mailboxes with acls

shared=$(get_all_mailboxes_with_acl)

if [ -z "$shared" ]; then 

    echo "No shared mailboxes were found."
    exit
    
fi

output="OWNER MAILBOX USERID LOOKUP READ SEEN WRITE INSERT POST CREATE DELETE ADMIN"

for mailbox_idnr in $shared; do

    mailbox_owner_name=$(get_mailbox_owner_name $mailbox_idnr)
    
    if [ -n "$userid" ]; then
    
	if [ "$userid" != "$mailbox_owner_name" ]; then
	
	    continue
	    
	fi
	
    fi
    
    mailbox_name=$(get_mailbox_name $mailbox_idnr)

    user_idnrs=$(get_mailbox_acl_users $mailbox_idnr)

    # There may be no users, so we should check for an orphaned mailbox here

    if [ -z "$user_idnrs" ]; then
    
	 output="${output}\n${mailbox_owner_name:-($mailbox_idnr)} ${mailbox_name:-($mailbox_idnr)}"
	 continue
	 
    fi

    for user_idnr in $user_idnrs; do
    
	rights=$(get_all_acls $mailbox_idnr $user_idnr)
	    
	if [ -n "$rights" ]; then
	
	    # At this point, we should know the mailbox id, the first user id, and their acls.
	    # we should report now

	    username=$(get_user_id $user_idnr)
	    output="${output}\n${mailbox_owner_name:-($mailbox_idnr)} ${mailbox_name:-($mailbox_idnr)} ${username:-($uid)}"
		
	    for right in $rights; do
				
		output="${output} $right"
	    
	    done
	    output="${output}\n"
		    
	fi

    done
    
done

printf "${output}" | column -t

}


function get_conf_option()
{
# an all purposes configurationreader
# reads values like:
# driver=mysql
# it can handle sections like [LDAP] or [GLOBAL]
# it can also be used to read the smb.conf file for example
local testfile="$1"
local searchitem="$2"
local section="$3"
local section_found
local IFS_KEEP
local allsearches
local bytenumber_section
local bytenumber_item
local tmpsearch

if [ ! -f $testfile -o -z "$testfile" ]; then

    echo "File not found."
    return
    
fi

if [ -z "$searchitem" ]; then

    echo "No searchitem."
    return
    
fi

if [ -z "$section" ]; then

    grep --max-count=1 "^[[:blank:]]*$searchitem[[:blank:]]*\=" ${testfile} | awk -F '=' '{ print $2 }' | sed "s@^[[:blank:]]*@@" | sed "s@[[:blank:]]*\$@@"
    
else


    section_found=$(cat $testfile | grep --max-count=1 --byte-offset "^[[:blank:]]*\[$section\]")

    # echo "section_found:$section_found"

    if [ -z "$section_found" ]; then

	echo "No section found with this name."
    
	return
    
    fi

    allsearches=$(cat $testfile | grep --byte-offset "^[[:blank:]]*$searchitem[[:blank:]]*\=")
    
    # echo "alls: $allsearches"

    bytenumber_section=$(echo $section_found | awk -F ':' '{ print $1 }')
    
    IFS_KEEP=$IFS
    IFS=$'\n'

    for tmpsearch in $allsearches; do
	
	bytenumber_item=$(echo $tmpsearch | awk -F ':' '{ print $1 }')
	    
        if [ $bytenumber_item -gt $bytenumber_section ]; then
	    
	    item_found=$tmpsearch
    	    break
	    
	fi
    
    done

    IFS=$IFS_KEEP

    echo "$item_found" | awk -F '=' '{ print $2 }' | sed "s@^[[:blank:]]*@@" | sed "s@[[:blank:]]*\$@@"
    
fi

}


read_conf()
{
#
# read all relevant values from the dbmail configuration
#

if [ ! -f "${DBMAIL[_dbmail_conffile]}" ] ; then

    echo "Error: configuration file ${DBMAIL[_dbmail_conffile]} not found. Cannot continue."
    exit
    
elif [ ! -r "${DBMAIL[_dbmail_conffile]}" ] ; then

    echo "Error: ${DBMAIL[_dbmail_conffile]} is not readable."
    exit
    
fi

DBMAIL[_dbmail_mysqluser]=$(get_conf_option "${DBMAIL[_dbmail_conffile]}" user DBMAIL)
DBMAIL[_dbmail_mysqlpassword]=$(get_conf_option "${DBMAIL[_dbmail_conffile]}" pass DBMAIL)
DBMAIL[_dbmail_mysqlhost]=$(get_conf_option "${DBMAIL[_dbmail_conffile]}" host DBMAIL)
DBMAIL[_dbmail_mysqldatabase]=$(get_conf_option "${DBMAIL[_dbmail_conffile]}" db DBMAIL)
DBMAIL[_dbmail_driver]=$(get_conf_option "${DBMAIL[_dbmail_conffile]}" driver DBMAIL)


if [ -z "${DBMAIL[_dbmail_driver]}" ]; then

    echo "Error: SQL driver not set."
    exit
    
elif [ ! "${DBMAIL[_dbmail_driver]}" = "mysql" ]; then

    echo "Error: this script does not support other databases than MySQL."
    exit
    
fi


if [ -z "${DBMAIL[_dbmail_mysqldatabase]}" ]; then

    echo "Error: MySQL database not set."
    exit
    
fi

if [ -z "${DBMAIL[_dbmail_mysqlhost]}" ]; then

    echo "Error: MySQL host not set."
    exit
    
fi

if [ -z "${DBMAIL[_dbmail_mysqluser]}" ]; then

    echo "Error: MySQL user not set."
    exit
    
fi

if [ -z "${DBMAIL[_dbmail_mysqlpassword]}" ]; then

    echo "Error: MySQL password not set."
    exit
    
fi

DBMAIL_USERS=$(which dbmail-users 2>>/dev/null)

if [ -z "$DBMAIL_USERS" ]; then

    echo "Error: binary dbmail-users not found."
    exit
    
fi

if [ -z "$MYSQL_CLIENT" ]; then

    MYSQL_CLIENT=$(which mysql 2>>/dev/null)
    
fi

if [ -z "$MYSQL_CLIENT" ]; then

    echo "Error: binary mysql not found."
    exit
    
elif [ ! -x "$MYSQL_CLIENT" ]; then

    echo "Error: mysql client $MYSQL_CLIENT not executable."
    exit
    
fi

MYSQL_CLIENT="$MYSQL_CLIENT -u${DBMAIL[_dbmail_mysqluser]} -p${DBMAIL[_dbmail_mysqlpassword]} -h ${DBMAIL[_dbmail_mysqlhost]} ${DBMAIL[_dbmail_mysqldatabase]}"

}

#
# Script starts here
#

# no input

[ -z "$1" ] && usage

read_conf

COMMAND="$1"


case "$COMMAND" in


	add|del)
	
	    USERID="$2"
	    
	    if [ -z "$USERID" ]; then
	    
		usage
		exit
		
	    fi
	    
	    user_idnr=$(get_user_idnr $USERID)
	    
	    if [ -z "$user_idnr" ]; then
	    
		echo "Error: no user $USERID found."
		exit
		
	    fi
	    
	    mailbox_owner="$3"

	    if [ -z "$mailbox_owner" ]; then
	    
		usage 
		
	    else
	    
		mailbox_owner_idnr=$(get_user_idnr "$mailbox_owner")
	    
		if [ -n "$4" ]; then
		
		    mailbox_name="$4"
		    
		    mailbox_idnrs=$(get_mailbox_idnr $mailbox_owner_idnr "$mailbox_name")
		    
		else
		
		    echo "Taking default mailbox INBOX"
		
		    mailbox_idnrs=$(get_mailbox_idnr $mailbox_owner_idnr)
		    
		fi
		
		if [ -z "$mailbox_idnrs" ]; then
		
		    echo "Error: mailbox $mailbox_name not found for $mailbox_owner."
		    exit
		    
		fi
		
		if [ "$COMMAND" = "add" ]; then
		
		    for mailbox_idnr in $(echo $mailbox_idnrs); do
		
			acl_add $user_idnr $mailbox_idnr

			subscription_add $user_idnr $mailbox_idnr
			
		    done
		    
		elif [ "$COMMAND" = "del" ]; then
		
		    for mailbox_idnr in $(echo $mailbox_idnrs); do

			acl_del $user_idnr $mailbox_idnr

			subscription_del $user_idnr $mailbox_idnr
			
		    done
		
		fi

	    fi
	    ;;


	list)
	
	    TYPE="$2"
	    
	    if [ -z "$TYPE" ]; then
	    
		TYPE="acl"
		
	    fi
	    
	    if [ "$TYPE" = "acl" ]; then
	
		USERID="$3"
	    
		if [ -n "$USERID" ]; then
		
	    	    user_idnr=$(get_user_idnr $USERID)
	    
		    if [ -z "$user_idnr" ]; then
	    
			echo "Error: no user $USERID found."
			exit
		
		    fi
		    
		    acl_list $USERID
		    
		else
		
		    acl_list	
		
		fi
		
		
	    elif [ "$TYPE" = "mailbox" ]; then
	    
		USERIDS="$3"
		
		if [ -z "$USERIDS" ]; then
		
		    USERIDS=$(dbmail-users -l | cut -d : -s -f 1)
		    
		fi
	    
		if [ -n "$USERIDS" ]; then
		
		    for USERID in $(echo $USERIDS); do
		
	    		user_idnr=$(get_user_idnr $USERID)
	    
			if [ -z "$user_idnr" ]; then
	    
			    echo "Error: no user $USERID found."
			    exit
		
			fi
		    
			# echo "Mailboxes owned by $USERID:"
		    
			for name in $(get_mailbox_names $user_idnr); do
			
			    if [ "$USERIDS" = "$USERID" ]; then
		    
				echo -e "$name"
				
			    else
			    
				echo -e "$USERID:$name"
				
			    fi
			
			done
			
		    done
		    		
		fi
	    
	    else 
	    
		echo "Second argument should be mailbox or acl. Acl is the default."
	    
		
	    fi
		
		
	    
	    ;;
	    
	*)
	    usage
	    ;;

esac

