LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (http://www.linuxquestions.org/questions/programming-9/)
-   -   Check other users permissions (with proposed solution) (http://www.linuxquestions.org/questions/programming-9/check-other-users-permissions-with-proposed-solution-4175411655/)

ph0n3s 06-15-2012 12:54 PM

Check other users permissions (with proposed solution)
 
So, in bash, as root, I want to check if <user> has <permission> on <file|directory>.

In bash, of course there is 'test' to determine if the current <user> (EUID) has execute permissions on <file>:

Code:

[ -x /pathto/file ]
But its not the current user I am curious about, I want to know if a different user has the permissions.

The best I could come up with was this:

Code:

#!/bin/bash
#
# @(#)$Id: permission,v 1.3 2012/06/15 03:20:13 rwilliams final root $
# @(#)$Purpose: Determine if <user> has <permission> on <file|directory>$
# @(#)$Comment: this script is intended to be sourced, from a parent shell$
# @(#)$Usage: permission <user> <permission> <file|directory>$
# @(#)$Return: 0 TRUE, 1 FALSE, 99 NOT PROCESSED$
#

# Automatically export defined functions
set -a

permission() {
  { [ -n "$1" ] && [ -n "$2" ] && [ -n "$3" ]; } || return 99;
  chk="{ [ -${2} ${3} ] && exit 0; } || exit 1;"
  sudo -u ${1} bash -c "${chk}"
  return $?
}

# Skip testing if no command-line parameters were given
{ [ -n "$1" ] && [ -n "$2" ] && [ -n "$3" ]; } \
&& permission $1 $2 $3 && exit $?

Of course, the permission function needs to CLEAN it's parameters. :)
It works, but I was wondering, is there an alternative/better way??

Thanks!
- Rand

pan64 06-15-2012 01:31 PM

I think it is a good approach, but there are other ways to work with: you can check the number of arguments, $# will help you. Also you do not need double check if $# is good enough you can skip the check in the function permission.







__________________________________
Happy with solution ... mark as SOLVED
If someone helps you, or you approve of what's posted, click the "Add to Reputation" button, on the left of the post.

ph0n3s 06-15-2012 02:01 PM

${#@} got it, thanks!

Still wondering if there other ways to check for another users permissions on a file/dir.

pan64 06-15-2012 02:33 PM

I think the best you can do is to check if that user is capable to do that. Therefore sudo -u $user <test> is the real way to check. I do not know why do you want another solution.




__________________________________
Happy with solution ... mark as SOLVED
If someone helps you, or you approve of what's posted, click the "Add to Reputation" button, on the left of the post.

ph0n3s 06-15-2012 04:14 PM

It's such a common task to check (non-ACL) permissions, before performing some action.
I feel there should be some simple, direct, builtin (api-calling) compliant command to do it.
Perhaps an enhancement to bash or sh-POSIX.

Sudo is so heavy handed to use to perform such a simple test, which outght to be exposed at the filesystem-api or shell level. I feel using sudo for this is like driving a nail with a sledgehammer. It's certainly indirect, and also has security risks, but was the only quick solution I could find.

I can think of worse alternatives, I just can't think of any better. :(

Nominal Animal 06-15-2012 04:41 PM

Quote:

Originally Posted by ph0n3s (Post 4704357)
It's such a common task to check (non-ACL) permissions, before performing some action.

Common maybe, but erroneous. It is unreliable, just plain wrong.

The correct way to do it is to simply do the action. Error handling can trivially find out why some part of the sequence could not be done; insufficient access permissions are just one possible reason. Checking beforehand is no substitute, since the permissions may change between checking and the actual access.

When you realize this, it suddenly becomes obvious why there is no API for this.

The only issue for programmers is that you need to have full error handling, preferably one that can gracefully fail with informative error messages. From experience, it is not hard; it becomes second nature very quickly when you always do it (always do it and do it right!). Learning how to do it was a lot of work.

ph0n3s 06-15-2012 05:40 PM

Yes, there's no substitute for good exception handling.

But, in this case I need to provide sanity checks before any action is attempted. I cannot allow the script to run halfway through and fail. Exception handling not withstanding, because even if an exception is caught, there is no 'rollback' concept. So, the intention of this script is to ensure a minimum level of sanity and verify dependencies are satisfied before cascading actions commence.

Here is my simplified version, using runuser instead of sudo,
not sure if it is an improvement... And still need to clean the parameters...

Code:

#!/bin/bash
#
# @(#)$Id: permission,v 1.4 2012/06/15 22:07:35 rilliams final root $
# @(#)$Purpose: Determine if <user> has <permission> on <file|directory>$
# @(#)$Comment: this script is intended to be sourced, from a parent shell$
# @(#)$Usage: permission <user> <permission> <file|directory>$
# @(#)$Return: 0 TRUE, 1 FALSE, 99 NOT PROCESSED$
#
set -a
permission() {
  [ ${#@} -eq 3 ] || return 99;
  runuser $1 -c "[ -$2 $3 ] || exit 1;"; }
permission $1 $2 $3 && exit $?;


Nominal Animal 06-15-2012 06:39 PM

Quote:

Originally Posted by ph0n3s (Post 4704405)
But, in this case I need to provide sanity checks before any action is attempted. I cannot allow the script to run halfway through and fail.

Then your design is faulty. That is exactly what a robust tool must be able to do: gracefully exit if an error occurs midway through.

I may sound harsh, but that is not my intention. I know that the world is full of examples and designs where you first do some checks to see whether the task ahead might fail, and then just do the work assuming it cannot fail -- or if it fails, the tool fails catastrophically. That design is just wrong. A good programmer can do better.

I personally prefer to use C (GNU C99 where available, C99 elsewhere), so I'm definitely not talking about exceptions either. Just robust error handling, where any function call can fail.

Quote:

Originally Posted by ph0n3s (Post 4704405)
there is no 'rollback' concept.

That means there will be no guarantees anyway about whether your tool works or not. The extra checks, then, are there only to catch the most common errors.

Let me repeat: that design is not robust. It is prone to failure. The correct solution is to do the work in a way which allows you to abort cleanly if an error occurs. I have personally not yet encountered a case where such a solution does not exist. In some cases they might be prohibitively expensive or slow, but I have not encountered such.

Quote:

Originally Posted by ph0n3s (Post 4704405)
So, the intention of this script is to ensure a minimum level of sanity and verify dependencies are satisfied before cascading actions commence.

All right. If your tool is run with elevated privileges, you can use simply
Code:

sudo -u "$USER" -- test -r "$FILE" && echo "$USER can read $FILE"
sudo -u "$USER" -- test -w "$FILE" && echo "$USER can write to $FILE"
sudo -u "$USER" -- test -x "$FILE" && echo "$USER can execute $FILE"
sudo -u "$USER" -- cd "$DIR" &>/dev/null && echo "$USER can enter $DIR"

and so on.

In the general case, I would use a separate helper script. Save
Code:

#!/bin/bash
[ "$(id -u)" = "0" ] || exec sudo -n -- "$0" "$@"

User="$1"
shift 1

if ! id -u "$User" &>/dev/null ; then
    printf '%s: No such user.\n' "$User" >&2
    exit 1
fi

exec sudo -n -u "$User" -- /usr/bin/test "$@"

as say /usr/local/bin/test-user and add the necessary ALL ALL = NOPASSWD: /usr/local/bin/test-user line to e.g. /etc/sudoers.d/test-user or /etc/sudoers file. Note that the script should NOT be setuid; mode 0755 (-rwxr-xr-x) and owned by root:root is best.

The end result is that the above script can be used just like test, except with the target username added as the first parameter. Examples:
Code:

test-user "$user" -d "$dir" -a -w "$dir" && echo "$user can create files in directory $dir"
test-user "$user" -w "$file" && echo "$user can write to $file"

The details: The second line in the script makes the script re-execute itself as root when run by a non-privileged user.

The script then checks using the id utility if the user exists. This check is for courtesy only: the following sudo will catch any invalid usernames anyway. (It helps catch installation problems, if you forget to add the line to sudoers.)

The final line uses sudo to switch to the desired user. Remember, the script is running privileged (as root) at this point. All other parameters -- except for the user name -- are given to the explicitly executed /usr/bin/test utility as-is. The above script is therefore just as secure as test is; there is no need to try to filter or modify any of the parameters. (If I used the shell built-in test or conditional operators, it would be a different matter.)

The main difference between our approaches is that you first look for something that works, then try to make it safe. I have the opposite approach; for me, security is an integral, inseparable part of my tools. They're not always safe, of course -- I err way too often --, but usually I manage to warn my users about the risks, and inform them about any deficiencies of the approach. I do not see security features as something that can be added on later.

I hope you find this useful. I apologise if I sound harsh, but your approach to the issue is something that I see almost constantly causing trouble. I wish programmers did better. The fact that you are aware of the issues, and discuss them here, tells me you have the skill -- or if not the skills yet, then definitely the capability the learn them! -- to handle this stuff right. Otherwise I would not have brought it up at all.

ntubski 06-15-2012 06:54 PM

Quote:

Originally Posted by Nominal Animal (Post 4704435)
The details: The second line in the script makes the script re-execute itself as root when run by a non-privileged user.
...
and add the necessary ALL ALL = NOPASSWD: /usr/local/bin/test-user line to e.g. /etc/sudoers.d/test-user or /etc/sudoers file.

Why does the script need to run as root? Wouldn't it make more sense to add ALL ALL = NOPASSWD: /usr/bin/test to the sudoers file?

Nominal Animal 06-16-2012 10:31 AM

Quote:

Originally Posted by ntubski (Post 4704442)
Why does the script need to run as root? Wouldn't it make more sense to add ALL ALL = NOPASSWD: /usr/bin/test to the sudoers file?

If you use it unmodified, it does not, and indeed the script could be simplified by using your suggestion.

However, I assume at some point the OP wishes to test if a user can enter a directory, which is best tested using cd, as I showed in my post. In that case test alone is insufficient. I was being sneaky ;)

ntubski 06-16-2012 10:48 AM

Quote:

Originally Posted by Nominal Animal (Post 4704918)
However, I assume at some point the OP wishes to test if a user can enter a directory, which is best tested using cd, as I showed in my post. In that case test alone is insufficient. I was being sneaky ;)

Ah, hadn't noticed that.

Quote:

Originally Posted by Nominal Animal
All right. If your tool is run with elevated privileges, you can use simply
Code:

...
sudo -u "$USER" -- cd "$DIR" &>/dev/null && echo "$USER can enter $DIR"


However, cd is a builtin shell command so:
Code:

~$ USER=root
~$ DIR=/
~$ sudo -u "$USER" -- cd "$DIR" &>/dev/null && echo "$USER can enter $DIR" || echo "$USER can't enter $DIR"
root can't enter /
~$ sudo -u "$USER" -- cd "$DIR"
sudo: cd: command not found


Nominal Animal 06-16-2012 11:01 AM

Quote:

Originally Posted by ntubski (Post 4704930)
Ah, hadn't noticed that. However, cd is a builtin shell command

:doh: Ouch. Teaches me right for not testing my snippets.

The correct solution is of course
Code:

sudo -n -u "$USER" -- bash -c "cd '$DIRSQ'" &>/dev/null
where DIRSQ is a non-empty string with all single quotes escaped using '"'"' . In other words,
Code:

SQ="'"
ESQ="'\"'\"'"
[ -n "$DIR" ] || exit 1
sudo -n -u "$USER" -- bash -c "cd '${DIR//$SQ/$ESQ}'" &>/dev/null || exit 1


ntubski 06-16-2012 11:29 AM

Quote:

Originally Posted by Nominal Animal (Post 4704938)
:doh: Ouch. Teaches me right for not testing my snippets.

Yup, I've noticed that when I post untested code it's nearly always wrong.

Quote:

where DIRSQ is a non-empty string with all single quotes escaped using '"'"' .
We can avoid some escaping, I think:
Code:

sudo -n -u "$USER" -- bash -c 'cd "$0"' "$DIR" &>/dev/null
Quote:

-c string
Read and execute commands from string after processing the options, then exit. Any remaining arguments are assigned to the positional parameters, starting with $0.
From the Bash Reference Manual - Invoking Bash

Nominal Animal 06-16-2012 02:09 PM

Quote:

Originally Posted by ntubski (Post 4704957)
We can avoid some escaping, I think:
Code:

sudo -n -u "$USER" -- bash -c 'cd "$0"' "$DIR" &>/dev/null

That is very, very neat. The "$0" feels very weird to me, but it does work, just like the documentation says. I just had to check for myself! It even works in dash the exact same way.

Let's say I needed this sort of a tool to let my users check if they have set their file and directory accesses correctly. I prefer to use group-based access controls, so this is not that far fetched. Here is the skeleton of the script I'd use. Note: this one is intended to be used by humans interactively, not so much as a scripting tool.

I'm using [ instead of [[ (as well as avoiding certain other Bashisms) because this is the sort of thing I might wish to run using dash instead, in certain situations. Also, this is untested, so it probably contains bugs.
Code:

#!/bin/bash

# Make sure we have root privileges.
[ "$(id -u)" != "0" ] || exec sudo -n -- "$0" "$@"

# Usage requested?
if [ $# -lt 1 ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
    exec >&2
    echo "Usage: $0 [ -h | --help ]"
    echo "      $0 USER -drw DIR(s)..."
    echo "      $0 USER -frwx FILE(s)..."
    echo "Where:"
    echo "      -d DIR    USER can enter DIR"
    echo "      -w ITEM  USER can write to ITEM"
    echo "      -r ITEM  USER can read ITEM"
    echo "      -f FILE  FILE is a regular file"
    echo "      -x FILE  USER can execute FILE"
    echo ""
    echo "If all tests are successful, this will return success (exit status 0)."
    echo "If any of the tests fail, this will return failure (exit status 1)."
    echo "If USER is not valid user, this will return failure (exit status 2)."
    echo "In case of other errors, this will return failure (exit status 3)."
    echo ""
    exit 3
fi

# Use POSIX/C locale to avoid problems with non-UTF-8 file names in UTF-8 locales.
export LANG=C
export LC_ALL=C

User="$1"
shift 1

# Courtesy user check
if ! id -u "$User" &>/dev/null ; then
    echo "$User: No such user." >&2
    exit 2
fi

while [ $# -gt 0 ]; do
    Opts="${1#-}"
    if [ "$Opts" = "$1" ]; then
        echo "$1: Unsupported test." >&2
        exit 3
    fi
    shift 1

    if [ -z "$1" ]; then
        echo "No files or directories specified for $Opts." >&2
        exit 3
    fi

    case "$Opts" in
      d) sudo -n -u "$User" -- bash -c 'cd "$0"' "$1" &>/dev/null || exit 1
        shift 1
        ;;
      f) sudo -n -u "$User" -- test -f "$1" &>/dev/null || exit 1
        shift 1
        ;;
      r) sudo -n -u "$User" -- test -r "$1" &>/dev/null || exit 1
        shift 1
        ;;
      w) sudo -n -u "$User" -- test -w "$1" &>/dev/null || exit 1
        shift 1
        ;;
      x) sudo -n -u "$User" -- test -x "$1" &>/dev/null || exit 1
        shift 1
        ;;
      *) echo "-$Opts: Unsupported test." >&2
        exit 3
        ;;
    esac

    # Opts may contain more than one test. We handled the first one, so remove it.
    Opts="${Opts#?}"
done

# Success.
exit 0

This should allow you to test e.g. USER -dw DIR to check if USER can enter and create new files in DIR . It should be easy to add new tests, too. I did not design it to support logical operators (-o, -a) or grouping, though; I think it is better if the caller does those themselves.

Hmm.. it might be useful to add echo or printf to the tests, to let the user know the detailed result of each test?

ph0n3s 06-18-2012 11:19 AM

Hey Nominal, pan64, Ntubski,

This is great!
Thank you for the thorough discussion and recommendations!
I'll update my code.

[OT]
An aside , regarding the exception handling, et al:

Consider that we in the software-engineering field further our trade.

For mission and life-critical systems we are (more and more) mandated to incorporate 'formal (verification) methods' (esp. in Europe, getting better in the Americas).

That is, We MUST (formally, mathematically) show that a system or subsystem, yes even each and every script, must satisfy it's logical pre-conditions and logical post-conditions, WITHOUT execution (in the case of the verification), and also DURING execution. (the pre-conditions: I was calling sanity-checking-before-operation).

It's not 1970's, where formal systems and CASE tools were big and clunky, it's now, where flight systems and medical devices can kill, if we don't check that what we are about to do is safe -

... just a thought, you know what i'm sayin, and i see what your sayin, too.
[/OT]


All times are GMT -5. The time now is 08:38 PM.