#!/bin/sh -efu
# Command format:
# <Target> <Action> <Arguments>
#
# <Target> := Package|@Group
# <Action> := add|del|leader|replace
#
# Superuser additional actions:
# <Action> := create|delete
#

. girar-sh-functions

. shell-error
. shell-quote
. shell-args

[ -n "${GIRAR_USER-}" ] ||
        fatal 'GIRAR_USER undefined'

quiet=
if [ "${1-}" = '--quiet' ]; then
	quiet="$1"
	shift
fi

repository="$1"; shift
dir="$1"; shift

repository="$(girar-normalize-repo-name "$repository")"
cd "$dir"

writelog()
{
	[ "$#" -eq 1 ] || return 0
	printf >&2 '< %s\n' "$(printf %s "$cmd_info" |tr -s '[:space:]' ' ')"
	printf >&2 '> %s\n' "$(printf %s "$1" |tr -s '[:space:]' ' ')"
}

abort()
{
	writelog "ERROR: ${*-}"
	exit 1
}

ignore()
{
	[ -z "$quiet" ] || return 0
	writelog "IGNORE: ${*-}"
}

ok()
{
	[ -z "$quiet" ] || return 0
	writelog "OK: ${*-}"
}

change()
{
	# remove duplicates
	local o list=
	for o; do
		[ -n "$list" -a -z "${list##* $o *}" ] ||
			list="$list $o "
	done
	set -- $list

	local tail=
	while [ $# -gt 0 ]; do
		case "${1-}" in
			@qa|@everybody|@nobody) tail="$tail $1"; shift ;;
			*) break ;;
		esac
	done

	if [ -z "${*-}" ]; then
		if [ "$action_type" = pkg ]; then
			set -- @nobody
		else
			abort 'ACL entry cannot be made empty'
		fi
	else
		set -- $@ $tail
	fi

	if [ "$*" = "$prev_owners" ]; then
		ignore 'Nothing to change'
		return
	fi

	local qitem
	quote_sed_regexp_variable qitem "$item"
	sed -i "s/^\($qitem[[:space:]]\).*/\1$*/" "$listfile" &&
		ok "$item: $*" ||
		abort "$item: $*"
}

show_cmd_usage()
{
	local msg="USAGE: <package|@group> $action"
	case "$action" in
		add|create|del) msg="$msg <owners ...>" ;;
		delete) ;;
		leader) msg="$msg <owner>" ;;
		replace) msg="$msg <old-owner> <new-owner>" ;;
	esac
	writelog "$msg"
	exit 1
}

check_usage()
{
	[ -n "$new_owners" ] ||
		show_cmd_usage

	[ -n "$prev_owners" ] ||
		abort "$item: $item_type not found in ACL file"
}

check_new_members()
{
	local i qi
	for i; do
		if [ "${i#@}" = "$i" ]; then
			[ -d "$GIRAR_PEOPLE_QUEUE/${i##*/}" ] ||
				abort "$i: Login name not found"
		elif [ "$i" != '@everybody' ]; then
			quote_sed_regexp_variable qi "$i"
			[ -n "$(sed -n "/^$qi[[:space:]]/p" "list.groups.$repository")" ] ||
				abort "$i: Group not found in ACL file"
		fi
	done
}

do_add()
{
	check_usage
	check_new_members $new_owners

	local o owners=

	if [ "$action_type" = grp ]; then
		# Check new owners for nested groups.
		for o in $new_owners; do
			[ -n "${o##@*}" ] ||
				abort "Nested group \`$o' detected"
		done
	fi

	# Merge old and new owners, remove @nobody if any.
	for o in $prev_owners $new_owners; do
		[ "$o" = '@nobody' ] ||
			owners="$owners $o"
	done

	change $owners
}

do_del()
{
	check_usage

	local o owners=

	new_owners=" $new_owners "
	# Filter out new owners from old owners.
	for o in $prev_owners; do
		[ -z "${new_owners##* $o *}" ] ||
			owners="$owners $o"
	done

	change $owners
}

do_leader()
{
	set -- $new_owners
	[ $# -eq 1 ] ||
		show_cmd_usage
	local new_leader="$1"; shift

	check_usage
	check_new_members $new_owners

	if [ "$leader" = "$new_leader" ]; then
		ignore 'Nothing to change'
		return
	fi

	change $new_leader $prev_owners
}

do_replace()
{
	set -- $new_owners
	[ $# -eq 2 ] ||
		show_cmd_usage
	local old new
	old="$1"; shift
	new="$1"; shift

	check_usage
	check_new_members $new

	if [ "$old" = "$new" ]; then
		ignore 'Nothing to change'
		return
	fi

	local o found= owners=
	for o in $prev_owners; do
		case "$o" in
			$old)
				found=1
				owners="$owners $new"
				;;
			*)
				owners="$owners $o"
				;;
		esac
	done

	[ -n "$found" ] ||
		abort "Owner \`$old' not found in ACL entry"

	change $owners
}

do_create()
{
	[ -n "$new_owners" ] ||
		show_cmd_usage

	if [ -n "$prev_owners" ]; then
		ignore "$item: $item_type already exist in ACL file"
		return
	fi

	if [ "${item#@}" = "$item" ]; then
		printf %s "$item" |grep -Exqs '[A-Za-z0-9][A-Za-z0-9._+-]*[A-Za-z0-9+]' ||
			abort "$item: Invalid package name"
	else
		printf %s "$item" |grep -Exqs '@[a-z][a-z0-9_]*[a-z0-9]' ||
			abort "$item: Invalid group name"
	fi

	check_new_members $new_owners

	girar-check-superuser "$repository" ||
		abort "$item $action: Permission denied"

	printf '%s\t%s\n' "$item" "$new_owners" >> "$listfile" &&
		sort -u -o "$listfile" "$listfile" &&
		ok "$item: $new_owners" ||
		abort "$item: Failed to create"
}

do_delete()
{
	[ -z "$new_owners" ] ||
		show_cmd_usage

	if [ -z "$prev_owners" ]; then
		ignore "$item: $item_type not found in ACL file"
		return
	fi

	girar-check-superuser "$repository" ||
		abort "$item $action: Permission denied"

	local qitem
	quote_sed_regexp_variable qitem "$item"
	sed -i "/^$qitem[[:space:]]/d" "$listfile" &&
		ok "$item: Removed" ||
		abort "$item: Failed to remove"
}

while read item action new_owners; do
	case "$item" in
		''|\#*)
			continue
			;;
		@*)
			item_type='Group'
			action_type='grp'
			listfile="list.groups.$repository"
			;;
		*)
			item_type='Package'
			action_type='pkg'
			listfile="list.packages.$repository"
			;;
	esac

	# Handle command aliases.
	case "$action" in
		rem) action=del ;;
	esac

	cmd_info="$item $action $new_owners"

	# Check action name.
	case "$action" in
		add|create|del|delete|leader|replace) ;;
		*)
			abort "$item $action: Invalid action"
			;;
	esac

	# Check new_owners
	[ -z "$(printf %s "$new_owners" |LANG=C tr -d '[@a-z_0-9[:space:]]')" ] ||
		abort "$item $action: $new_owners: Invalid argument(s)"
	for a in $new_owners; do
		printf %s "$a" |egrep -qs '^@?[a-z_0-9]+$' ||
			abort "$item $action: $new_owners: Invalid argument(s)"
	done

	# Check perms
	if [ del = "$action" -a "$GIRAR_USER" = "$new_owners" ]; then
		# $GIRAR_USER is allowed to del self from the list (ALT#19215)
		msg="$(girar-check-acl-item "$item" \
			"list.packages.$repository" \
			"list.groups.$repository")"
	elif [ 'create' != "$action" -a 'delete' != "$action" ]; then
		msg="$(girar-check-acl-leader "$GIRAR_USER" "$item" \
			"$repository" .)"
	fi ||
		abort "$item $action: $msg"

	quote_sed_regexp_variable qitem "$item"
	prev_owners="$(sed -n "s/^$qitem[[:space:]]\+//p" "$listfile")"

	# Get current leader
	leader="${prev_owners%% *}"

	# First failure breaks the loop
	do_$action
done
