1. Overview

Most modern Linux distribution provides the Super User DOsudo utility. So, the privileges to perform particular tasks may be finely granted to users. Therefore, we can avoid the ‘root or nothing’ situation when permission to do one task is really permission to do everything. In this tutorial, we’ll learn how to check if a user has sudo privileges.

2. Group Membership

Let’s notice that in a Linux system, there may exist a special group for sudo users. However, its name depends on the Linux distribution. On Ubuntu, the name is usually sudo while on Fedora wheel. So, let’s check the membership of our users, joe and john, with groups:

$ groups joe
joe : joe wheel dialout

$ groups john
john : john

We can deduce that joe might be a sudo user, but john might not.

3. Checking /etc/sudoers

Assuming that we’re root or a sudo user, let’s check the content of the /etc/sudouser file directly with cat. Moreover, the information obtained in this way is conclusive:

$ sudo cat /etc/sudoers
[sudo] password for joe:
## Sudoers allows particular users to run various commands as
## the root user, without needing the root password.
##

# some output skipped

## Allow root to run any commands anywhere
root    ALL=(ALL)     ALL

# some output skipped

## Allows people in group wheel to run all commands
%wheel    ALL=(ALL)    ALL

# more ouput skipped

We can see that the user root is allowed to do anything. Next, the same is granted to members of the group wheel. And, as we know, joe belongs to this group, but john doesn’t.

4. Checks With sudo Available for Sudo Users

As privileged users, let’s list our permissions with the l switch to sudo:

$ sudo -l
[sudo] password for joe:
...
User joe may run the following commands on fedora35:
    (ALL) ALL

Next, let’s use the U option for john:

$ sudo -l -U john
[sudo] password for joe:
User john is not allowed to run sudo on fedora35.

Finally, let’s do a quick check for all user accounts in our system. We’re going to feed the sudo command with the output of getent, with the help of xargs:

$ getent passwd | cut -d: -f1 | xargs -n 1 sudo -l -U
[sudo] password for joe: 
...
User root may run the following commands on fedora35:
    (ALL) ALL
User bin is not allowed to run sudo on fedora35.
...
User joe may run the following commands on fedora35:
    (ALL) ALL
...
User john is not allowed to run sudo on fedora35.

5. Checks With sudo Available for Non-sudo User

Now let’s check our sudo status when we aren’t sudo users ourselves. First, let’s use the v option. Its goal is to update credentials and extend the timeout. However, it provides a meaningful message as well:

$ sudo -v
Sorry, user john may not run sudo on fedora35.

We can see that as a non-sudo user, we don’t need to provide a password. As the next option, we can use the l switch. In this case, we’re going to be asked for a password:

$ sudo -l
[sudo] password for john:
Sorry, user john may not run sudo on fedora35.

Finally, let’s just try to run some command, e.g., ls with sudo:

$ sudo ls
[sudo] password for john:
john is not in the sudoers file.  This incident will be reported.

Let’s notice that, in this case, we create additional clutter by sudo reporting the incident.

6. Checks in the Non-interactive Mode

Let’s check the user’s privileges in a more automatic way. It will be useful for scripts that are run by different users. For security reasons, we shouldn’t give the user’s password. So, let’s prepare the test_sudo script, where we’re going to source test functions from the ami_sudo file:

#!/bin/bash

source ami_sudo

if ami_sudo
then
    echo "I'm" $(whoami) "and I'm sudo user"
else
    echo "I'm" $(whoami) "and I'm non-sudo user"    
fi

The ami_sudo function exits with success (0) for a sudo user and with failure (1-255) otherwise. Since we’ll work as joe, we need to allow other users to execute the script with chmod ou+x test_sudo. Next, we’ll su john in the script’s folder. Finally, we’ll work on Fedora 35.

6.1. Checking Group Membership

As the first solution, let’s check the user’s groups:

ami_sudo() {
    groups $USER | grep -qi wheel #sudo on Ubuntu
    return # status of the last command = $?
}

Although very simple, the solution works with most default sudo configurations and has an advantage – we don’t need to care about the password. Let’s check the results:

[joe@fedora35 sudo]$ ./sudo_test

I'm joe and I'm sudo user

[john@fedora35 sudo]$ ./sudo_test

I'm john and I'm non-sudo user

6.2. Grepping the sudo Output

As a different approach, let’s use the sudo command. So, we can use the n switch to run sudo in the non-interactive mode. Then, we’re going to find some pattern in the command’s result to deduce the user’s privilege. Let’s check it, together with v, as joe:

[joe@fedora35 ~]$ sudo -nv
sudo: a password is required

and as john:

[john@fedora35 ~]$ sudo -nv
Sorry, user john may not run sudo on fedora35.

So, let’s just look for the hostname in the returned message:

ami_sudo() {
    result=$(sudo -nv 2>&1)

    ! $(test -n "$result" && echo "$result" | grep -qi "$HOSTNAME")  #let's notice negation

    return $? 
}

Let’s analyze the ami_sudo function. First, let’s notice that when sudo reports errors, we need to redirect them to STDOUT with 2>&1. Next, if the sudo message is empty, test -n result returns false. This is the case of a sudo user who currently has authentication – no need for a password and no error message either. Subsequently, the commands after the && operator don’t execute.On the other hand, the result which isn’t empty may mean both a failed password request or a non-sudo user. However, in the latter case, the error message contains the hostname, so grep returns success.

6.3. Using Fake Password Function

As yet another way to avoid providing the password, we’re going to use the A switch. In this case, we can ask sudo to call the custom password-providing program. In addition, we need to set the path to this program in the SUDO_ASKPASS variable. So, let’s check it with /usr/bin/true as the fake askpass helper program. Of course, true always succeeds but doesn’t provide any password. However, let’s compare outputs for the sudo user joe:

[joe@fedora35 ~]$ SUDO_ASKPASS=/usr/bin/true sudo -vA

sudo: no password was provided
sudo: a password is required

and the non-sudo user john:

[john@fedora35 joe]$ SUDO_ASKPASS=/usr/bin/true sudo -vA

Sorry, user john may not run sudo on fedora35.

So, all we need to do is count the output lines – two for sudo users and one for others:

ami_sudo()
{
    sudo -k
    result=$(SUDO_ASKPASS=/usr/bin/true sudo -vA 2>&1 | wc -l)
    return "$((2 - $result))"
}

Let’s notice that we count sudo messages with wc. Next, we do some arithmetic, so the function returns 0 (success) for a sudo user and 1 (failure) otherwise. In addition, before the test, we use sudo -k to force ask for a password. We should be aware that this method is pretty fragile as it uses non-documented features of the command – how many lines of errors it returns.

6.4. Checking sudo Exit Code

In this approach, we’re going to check the exit code. sudo returns 0 on success and 1 otherwise. However, we should narrow the possible causes of failure, as the sudo may pass the exit code of the command it runs or signal an authentication error. Even the latter error may be due to a non-sudo user attempt or wrong password of a sudo user, or the wait period expiration. Let’s examine our last function:

ami_sudo() {
    output=$(timeout -s SIGKILL 2 sudo -v 2>&1)
    result=$? # grab the exit code

    case $result in
        0) echo "sudo user with password";;
        1) echo "non-sudo user";;
        137) echo "sudo user without cached credentials";;
    esac

    if [ $result -ne 1 ]
    then
        return 0
    else
        return 1
    fi        
}

First, we use sudo -v to run without command. Next, we put sudo inside timeout, which will kill it with the SIGKILL signal after 2 seconds. Now, we only need to care about three exit values:

  • 0: successful authentication of a sudo user with cached credentials
  • 1: failure for a non-sudo user – immediate return with error
  • 137: kill of sudo waiting for a password of a sudo user

Let’s notice that the method uses well-documented values of exit codes – both 0 and 1 for sudo and 137 for SIGKILL. The only part which doesn’t depend on us is the value of timeout. It must be shorter than that of sudo, but we cannot check it in advance. Finally, let’s notice that timeout comes with the GNU coreutils package. Now, as we add some output to ami_sudo, let’s check the results for joe:

[joe@fedora35 sudo]$ sudo -v # cache credentials
[sudo] password for joe: 

[joe@fedora35 sudo]$ ./sudo_test

sudo user with password
I'm joe and I'm sudo user

[joe@fedora35 sudo]$ sudo -k #drop credentials

[joe@fedora35 sudo]$ ./sudo_test

sudo user without cached credentials
I'm joe and I'm sudo user

and for john:

[john@fedora35 sudo]$ ./sudo_test

non-sudo user
I'm john and I'm not sudo user

7. Conclusion

In this tutorial, we learned how to check if a user might use sudo. We discussed ways for both sudo and non-user to achieve this goal. First, we looked through sudo groups and the sudo configuration file. Next, we examined different ways to apply the sudo command. Finally, we reviewed different tips and tricks for interpreting sudo output and exit code to use in the non-interactive mode.