#!/bin/bash

# Copyright 2018, 2019 eomanis
# 
# This file is part of inherit-acl.
#
# inherit-acl is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3 as
# published by the Free Software Foundation.
#
# inherit-acl 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 inherit-acl.  If not, see <http://www.gnu.org/licenses/>.

# TODO Manual page

# Set shell options
set -o pipefail
set -o noclobber
set -o nounset
set -o errexit

# Semantic versioning
declare -r versionMajor=0
declare -r versionMinor=1
declare -r versionPatch=3
declare -r versionLabel=""

# Globals
# The absolute paths to some programs so that the caller hopefully
# cannot spoof them
declare -r programId="/usr/bin/id"
declare -r programSudo="/usr/bin/sudo"

getVersion () {
	echo -n "${versionMajor}.${versionMinor}.${versionPatch}"
	test "$versionLabel" != "" && echo "-$versionLabel" || echo ""
}

# No arguments: Print a help message
if test $# -eq 0; then
	echo -n "inherit-acl "; getVersion
	echo -n "
Usage:
inherit-acl path [path]...

For each given path, recursively applies the path's parent directory's
 - owning user
   (chown --reference parentDir --recursive --no-dereference -- path)
 - owning group
   (chgrp --reference parentDir --recursive --no-dereference -- path)
 - permissions
   (chmod --reference parentDir --recursive -- path)
 - default ACL
   (getfacl --default (...) -- parentDir | (...) \\
   | setfacl --recursive --physical --set-file - -- path
    - or, if the parent does not have a default ACL:
   setfacl --recursive --physical --remove-all -- path)

Runs its companion application \"inherit-acl-run\" as root using sudo;
the calling user's login name is given as the 1st argument:

  \$ $programSudo inherit-acl-run userLogin /path /another/path

Sudo is not used if \"inherit-acl\" is already running as root.

Access control is supposed to be done with sudo, e.g. to let the user
\"someuser\" run the companion application as root with that user
login only, an /etc/sudoers line like this is suggested:

  someuser ALL=(root) NOPASSWD: /usr/bin/inherit-acl-run someuser *

This way, user \"someuser\" is allowed to run the companion
application as root, but only with user login \"someuser\" as first
argument.

The companion application uses the user login to determine a
user-specific parent directory whitelist; paths whose parents are not
whitelisted will be skipped.

The whitelists are configured in the configuration file
\"/etc/inherit-acl.conf\" in the form of an associative bash array
having the user logins as keys and newline-separated lists of valid
parent directories expressed by their absolute paths as values:

  parentDirWhitelist[\"someuser\"]='
  /media/data/shared
  /media/data/transfer
  /media/data/ftp
  '

Empty lines in the array's values are ignored.
Also, the paths must be given without a trailing slash.

This whitelist would, for user \"someuser\", have this effect for
these paths given as arguments:
 - FALSE: \"/media/data\" (parent \"/media\" not whitelisted)
 - FALSE: \"/media/data/shared\" (parent \"/media/data\" not whitelisted)
 -  TRUE: \"/media/data/shared/someDir\"
 -  TRUE: \"/media/data/shared/someDir/someSubdir\"

Please be aware that although symbolic links will not be followed,
clever users may use hardlinks to circumvent their whitelist and process
any other file they may hardlink below their whitelisted parent
directories.
Assuming that hardlinking directories is not possible, and that hardlink
protection is enabled (check \"/proc/sys/fs/protected_hardlinks\"), this
encompasses all files that a user has write access for, and that reside
on the same file system as a whitelisted parent directory.
If this risk is unacceptable bind mounts might be used to prevent such
hardlinking, as it appears that they do not allow hardlinking to targets
beyond their mount point. 

Many file managers support calling custom scripts for selected items via
context menu. For such a case this custom script is suggested:

  #!/bin/bash
  /usr/bin/inherit-acl \"\$@\"

Exit codes:
An exit code of 1 communicates a fatal error, for example that no
arguments were given (like now) or that a required program is not
available, or that hardlink protection is not enabled on the system.

Greater exit codes only consider the last path in the argument list and
are as such:
 - Exit code is 2: The path was skipped (e.g. does not exist, parent
   directory could not be determined, parent directory not whitelisted
   for the given user login)
 - 4 is ORed into the exit code: Problems applying the owning user
   (chown returned with a non-zero exit code)
 - 8 is ORed into the exit code: Problems applying the owning group
   (chgrp returned with a non-zero exit code)
 - 16 is ORed into the exit code: Problems applying the permissions
   (chmod returned with a non-zero exit code)
 - 32 is ORed into the exit code: Problems reading or applying the ACL
   (getfacl or setfacl returned with a non-zero exit code)
"
	exit 1
fi

# Make sure the required programs are available
type "$programId" &> /dev/null || { echo "ERROR  Required program \"${programId}\" is not available" >&2; exit 1; }

# Determine the path to the companion application
companionApplicationPath="$0"-run
# Determine the current user's login
userLogin="$("$programId" -un)"

# Launch the companion application with the current user's login
if test "root" = "$userLogin"; then
	# We are already root: Do not use sudo
	"$companionApplicationPath" "$userLogin" "$@"
else
	# Other user: Use sudo or die trying
	type "$programSudo" &> /dev/null || { echo "ERROR  Required program \"${programSudo}\" is not available" >&2; exit 1; }
	"$programSudo" -- "$companionApplicationPath" "$userLogin" "$@"
fi