21

Here's my source:

#!/bin/bash

echo "Running script to free general cached memory!"
echo "";
echo "Script must be run as root!";
echo "";
echo "Clearing swap!";
swapoff -a && swapon -a;
echo "";
echo "Clear inodes and page file!";
echo 1 > /proc/sys/vm/drop_caches;
echo "";

It clears caches and stuff, and it echoes that it needs to be run as root in the terminal. I basically just want the script to cease running if it detects it's not being executed as root.

Example:

"Running script to free general cached memory!"
"Warning: script must be run as root or with elevated privileges!"
"Error: script not running as root or with sudo! Exiting..."

If run with elevated privileges, it just runs as normal. Any ideas? Thanks!

4 Answers4

56
#!/bin/sh

if [ "$(id -u)" -ne 0 ]; then
        echo 'This script must be run by root' >&2
        exit 1
fi

cat <<HEADER
Host:          $(hostname)
Time at start: $(date)

Running cache maintenance...
HEADER

swapoff -a && swapon -a
echo 1 >/proc/sys/vm/drop_caches

cat <<FOOTER
Cache maintenance done.
Time at end:   $(date)
FOOTER

The root user has UID 0 (regardless of the name of the "root" account). If the effective UID returned by id -u is not zero, the user is not executing the script with root privileges. Use id -ru to test against the real ID (the UID of the user invoking the script).

Don't use $EUID in the script as this may be modified by an unprivileged user:

$ bash -c 'echo $EUID'
1000

$ EUID=0 bash -c 'echo $EUID'
0

If a user did this, it would obviously not lead to privilegie escalation, but may lead to commands in the script not being able to do what they are supposed to do and files being created with the wrong owner etc.

Kusalananda
  • 333,661
  • I like this way a lot too! Thank you so much! – M. Knepper Aug 31 '17 at 15:14
  • 1
    Note: on Solaris 5.10, /bin/id -u gives /bin/id: illegal option -- u Usage: id [-ap] [user] – jrw32982 Sep 06 '17 at 17:44
  • 2
    @jrw32982 You should have /usr/xpg4/bin early in your $PATH to get access to POSIX utilities on Solaris. – Kusalananda Sep 06 '17 at 17:47
  • 1
    The point is that the -u option of id is not universal and therefore this solution is universal only with the caveat that a POSIX version of id must occur first in your PATH. – jrw32982 Sep 06 '17 at 21:25
  • 1
    @jrw32982 For your script to pick up the correct POSIX utilities on Solaris, modify $PATH at the top of the script so that /usr/xpg4/bin is before /bin. If you insist on using non-POSIX id, then obviously you will have to come up with a more portable solution. – Kusalananda Sep 06 '17 at 21:57
  • shouldn't it be "$(id -u)" -ne "0" with double quotes around the "0"? – Alexander Mills Jun 20 '18 at 01:49
  • @AlexanderMills It can be. It matters if $IFS includes a zero (it doesn't by default). The shell will set $IFS to a default value (space, tab, newline) when started and I'm not modifying that variable in the code. – Kusalananda Jun 20 '18 at 06:28
  • Ok, but with double quotes it should always work right? – Alexander Mills Jun 20 '18 at 06:39
  • @AlexanderMills Yes, but it seems as if I was wrong on in my previous comment; The 0 will not be affected by $IFS (so there should be no need to quote it). – Kusalananda Jun 20 '18 at 06:53
  • 1
    @Kusalananda Don't use $EUID - But a new, one-line version of id could be written that echos 0 and put before /usr/bin/id in PATH, no? – Harry Jul 13 '18 at 09:18
  • 2
    @Harry Yes, so the script could use PATH=$( getconf PATH ) or set some other explicit default path, or use an explicit path for calling id. – Kusalananda Jul 13 '18 at 09:25
22

I think what you want is rather to check that you have super-user privileges, that is, that your effective user id is 0.

zsh and bash make that available in the $EUID variable, so you can do:

if ((EUID != 0)); then
  echo >&2 "Error: script not running as root or with sudo! Exiting..."
  exit 1
fi

With any POSIX-like shells, you can use the id standard command:

if [ "$(id -u)" -ne 0 ]; then
  echo >&2 "Error: script not running as root or with sudo! Exiting..."
  exit 1
fi

Note that all of id -un or whoami or the $USERNAME variable in zsh will get you the first username for the uid. On systems that have other users with id 0, that may not be root even if the process is a descendant of one that has been authenticated as root.

$USER would usually give you the user that authenticated, but relying on it is quite brittle. It's not set by the shell, but is usually set by the authenticating command (like login, su (on GNU/Linux systems, not necessarily others), sudo, sshd (the one from openssh at least)...). It's not always though (changing the uid does not automagically set that variable, it has to be done explicitly by the application changing the uid) and it could also have been modified by some other process in the shell's ancestry. $LOGNAME, with the same caveat is more reliable as it's specified by POSIX (originally from FIPS 151-2)

Kusalananda
  • 333,661
12

You can use $USER or whoami to check the current user.

if [[ "$USER" != "root" ]]; then
    echo "Error: script not running as root or with sudo! Exiting..."
    exit 1
fi

if [[ $(whoami) != "root" ]]; then
    echo "Warning: script must be run as root or with elevated privileges!"
    exit 1
fi

if [[ $(id -u) != "0" ]]; then
    echo "Error: script not running as root or with sudo! Exiting..."
    exit 1
fi
jesse_b
  • 37,005
  • 1
    NP. I suggest literally just reading straight through the bash manual [https://www.gnu.org/software/bash/manual/bashref.html]. It's actually a surprisingly easy ready. For this answer in particular I suggest referencing: 3.2.4.2 Conditional Constructs 3.5.4 Command Substitution – jesse_b Aug 31 '17 at 02:01
  • 10
    The command id -u with or without a -n option is an alternative to using whoami. Without a -n and comparing to zero is a better match to the test the kernel is actually doing. All the kernel cares about is the id, not the name from the password file. – icarus Aug 31 '17 at 02:28
  • Would you just swap $USER = root with id -n = 0? – M. Knepper Aug 31 '17 at 03:53
  • @M.Knepper Or you can start using google : throwing "bash how to know current user" gave me this post as first result : https://stackoverflow.com/questions/19306771/get-current-users-username-in-bash – Walfrat Aug 31 '17 at 07:29
  • 6
    I believe that using $(id -u) is better. In some (malicious) cases, $USER could be wrong. – Basile Starynkevitch Aug 31 '17 at 08:15
  • The top command would have the highest performance, as bash doesn't need to fork to execute another process, however since these checks are usually 1 time per script, these performance boosts usually don't result in any boost from the user perspective – Ferrybig Aug 31 '17 at 11:24
  • I would suggest checking against the user id 0 rather than the name "root". But that's just because i've renamed the root user before. :P – cHao Sep 01 '17 at 01:04
  • 1
    For those who are interested in the link provided by @Jesse_b: there's a typo. It should be https://www.gnu.org/software/bash/manual/bashref.html – Kryten Sep 01 '17 at 14:17
  • 1
    Some systems do not use "root" as the name of the privileged account having uid 0. QNAP springs to mind as one. So you should only check uid 0, and not the root account name. – Chris Davies Sep 09 '17 at 09:20
11

What you actually want is to determine is whether you have access to do these operations. It's considered bad practice to check whether or not you're root in lieu of that.

If the system has been configured to allow a non-root user to modify swap and drop page cache, why shouldn't that user get to run your script?

Instead, you can simply try the operation and exit with a helpful message if it fails:

if ! ( swapoff -a && swapon -a )
then
  echo "Failed to clear swap (rerun as root?)" >&2
  exit 1
fi

if ! echo 1 > /proc/sys/vm/drop_caches
then 
  echo "Failed to free page cache (rerun as root?)" >&2
  exit 1
fi
  • A non-root user turning off swap? – Kusalananda Aug 31 '17 at 20:18
  • 2
    Yes. You can set this up with SELinux. Likewise, you can disable a root process from doing it. – that other guy Aug 31 '17 at 20:32
  • 1
    You might not want to exit after something fails, in case the user wants to do as much as they can with their current privileges. (But for a typical system where all of this requires root, exiting would be more useful / less noisy.) – Peter Cordes Sep 01 '17 at 01:31
  • 4
    I wouldn't go so far as to say it's "bad practice" to check whether or not you're root. Particularly if that's exactly what you want to know. I do take your point that it's best to check if you have sufficient permissions to do what you want. – Erik Bennett Sep 09 '17 at 14:29