104

When running a script via sudo or su I want to get the original user. This should happen regardless of multiple sudo or su runs inside of each other and specifically sudo su -.

evan
  • 12,307
  • 7
  • 37
  • 51

10 Answers10

148

Results:

Use who am i | awk '{print $1}' OR logname as no other methods are guaranteed.

Logged in as self:

evan> echo $USER
evan
evan> echo $SUDO_USER

evan> echo $LOGNAME
evan
evan> whoami
evan
evan> who am i | awk '{print $1}'
evan
evan> logname
evan
evan>

Normal sudo:

evan> sudo -s
root> echo $USER
root
root> echo $SUDO_USER
evan
root> echo $LOGNAME
root
root> whoami
root
root> who am i | awk '{print $1}'
evan
root> logname
evan
root>

sudo su - :

evan> sudo su -
[root ]# echo $USER
root
[root ]# echo $SUDO_USER

[root ]# echo $LOGNAME
root
[root ]# whoami
root
[root ]# who am i | awk '{print $1}'
evan
[root ]# logname
evan
[root ]#

sudo su -; su tom :

evan> sudo su -
[root ]# su tom
tom$ echo $USER
tom
tom$ echo $SUDO_USER

tom$ echo $LOGNAME
tom
tom$ whoami
tom
tom$ who am i | awk '{print $1}'
evan
tom$ logname
evan
tom$
evan
  • 12,307
  • 7
  • 37
  • 51
  • 1
    In that case you can just use `who | awk '{print $1}'` – SiegeX Jan 04 '11 at 20:42
  • 3
    ... if you're the only one logged in (and it's only once). – Dennis Williamson Jan 04 '11 at 21:02
  • 9
    all you need is 2 arguments: `who am i` is the same as `who smells bad`. Also, it only works if `STDIN` is associated with a TTY. So if you run `echo "hello" | who am i` it simply won't work. – tylerl Jul 04 '11 at 00:16
  • This is for use inside of a script. Why would I run `echo "hello" | who am i` from inside a script?? – evan Jul 04 '11 at 00:54
  • 1
    You wouldn't run `echo "hello" | who am i` normally, unless your script is running in an environment where there is no terminal. Then you might see the error that `who am i` isn't working because there is some sort of problem with the non-readable stdin, at which case you might try piping in data to `who am i` out of desperation to satisfy it's stdin requirement. tylerl is just noting that he's already been down that path, and the pipe won't work because stdin must be both readable and associated with a TTY. – Edwin Buck Nov 22 '11 at 16:45
  • Is there a way to make it work when the script is executed via ssh without a shell, i.e. `ssh bart@example.com ./myscript.sh`? – Bart van Heukelom Dec 07 '11 at 16:31
  • @BartvanHeukelom - this is if you don't know who is going to be using the script. In your case, you know, so just pass in the user as a parameter to your script. `ssh bart@example.com ./myscript.sh bart` – evan Dec 07 '11 at 19:02
  • 4
    @even True, though I'd like it to require as little configuration as possible, so I'm using `logname` now, which as it turns out does work, where `who am i` does not. – Bart van Heukelom Dec 07 '11 at 19:10
  • `logname` has the same issue as @tylerl pointed out regarding `who am i`: It only works if STDIN is a TTY. Fortunately, that shouldn't be an issue in practical cases. Another option is `stat -c %U "$(readlink /proc/self/fd/0)"` with the same caveat, but I see no advantage over just sticking with `logname`. – Wildcard Nov 13 '18 at 20:24
21

There's no perfect answer. When you change user IDs, the original user ID is not usually preserved, so the information is lost. Some programs, such as logname and who -m implement a hack where they check to see which terminal is connected to stdin, and then check to see what user is logged in on that terminal.

This solution often works, but isn't foolproof, and certainly shouldn't be considered secure. For example, imagine if who outputs the following:

tom     pts/0        2011-07-03 19:18 (1.2.3.4)
joe     pts/1        2011-07-03 19:10 (5.6.7.8)

tom used su to get to root, and runs your program. If STDIN is not redirected, then a program like logname will output tom. If it IS redirected (e.g. from a file) as so:

logname < /some/file

Then the result is "no login name", since the input isn't the terminal. More interestingly still, though, is the fact that the user could pose as a different logged in user. Since Joe is logged in on pts/1, Tom could pretend to be him by running

logname < /dev/pts1

Now, it says joe even though tom is the one who ran the command. In other words, if you use this mechanism in any sort of security role, you're crazy.

tylerl
  • 30,197
  • 13
  • 80
  • 113
  • 2
    If you're running the script yourself (as evidenced by the commands used), security isn't the issue. If it is, you have a lot more of an issue since they also have sudo access. The person could just copy the script and modify it any way they like. This is simply a way to get the logged in name for use in a script. Or am I missing something about what you're saying? – evan Jul 04 '11 at 00:36
  • 1
    @evan: Having sudo access does not imply the ability to overwrite files. – Jonathan Hall May 16 '13 at 18:29
  • @Flimzy In what case does root not have the ability to overwrite a files? – evan May 18 '13 at 17:37
  • 1
    @evan: Any time your sudo access doesn't give you access to a shell, or any other command that can overwrite files, obviously. – Jonathan Hall May 18 '13 at 22:37
  • @evan sudo access isn't always (not should it be in most admin cases) total root access. It's a set of configurable restricted execution contexts. – DylanYoung Aug 13 '19 at 15:32
  • @tylerl Attempting to impersonate another user by redirecting their tty towards logname fails on my centos 6.7 with `bash: /dev/pts/0: Permission denied`, implying this is good-enough security-wise as well. And centos 6.7 is **old**... Am I missing anything? – Irfy Apr 25 '22 at 21:06
8

This is a ksh function I wrote on HP-UX. I don't know how it will work with Bash in Linux. The idea is that the sudo process is running as the original user and the child processes are the target user. By cycling back through parent processes, we can find the user of the original process.

#
# The options of ps require UNIX_STD=2003.  I am setting it
# in a subshell to avoid having it pollute the parent's namespace.
#
function findUser
{
    thisPID=$$
    origUser=$(whoami)
    thisUser=$origUser
    while [ "$thisUser" = "$origUser" ]
    do
        ( export UNIX_STD=2003; ps -p$thisPID -ouser,ppid,pid,comm ) | grep $thisPID | read thisUser myPPid myPid myComm
        thisPID=$myPPid
    done
    if [ "$thisUser" = "root" ]
    then
        thisUser=$origUser
    fi
    if [ "$#" -gt "0" ]
    then
        echo $origUser--$thisUser--$myComm
    else
        echo $thisUser
    fi
    return 0
}

I know the original question was from a long time ago but people (such as me) are still asking and this looked like a good place to put the solution.

askmish
  • 6,464
  • 23
  • 42
user1683793
  • 1,213
  • 13
  • 18
8

If we could arrange the process spawn hierarchy into a tree, then we could look for the user who spawned the process at the root of that tree. Luckily the pstree command does that arrangement for us.

pstree -lu -s $$ | grep --max-count=1 -o '([^)]*)' | head -n 1 | sed 's/[()]//g'

pstree shows running processes as a tree. The tree is rooted at a pid, here given as $$, which in bash expands to the process id of the current shell. So the first part of the command lists all the ancestor processes of the current shell with some funny formatting. The rest of the command discards the funny formatting to pick out the name of the user that owns the oldest ancestor process.

The main improvement over the other pstree-based answer here is that extraneous parentheses are not included in the output.

6

How about using logname(1) to get the user's login name?

sam
  • 61
  • 1
  • `logname(1)` doesn't work but `logname` does - adding the results above – evan Jan 05 '11 at 19:03
  • originally I had tried `$LOGNAME` but that didn't work. Also added to the results above. – evan Jan 05 '11 at 19:11
  • Does `logname` still require a tty? With my tests it always passes. (Maybe I to something wrong.) I am running linux with coreutils 8.26. – simohe Jan 23 '18 at 10:54
  • My logname (GNU coreutils) 8.28 on always returns "logname: no login name" (Ubuntu 18.04.2) – sondra.kinsey Mar 08 '19 at 09:52
6

On systems running systemd-logind, the systemd API provides this information. If you want to access this information from a shell script, need to use something like this:

$ loginctl session-status \
  | (read session_id ignored; loginctl show-session -p User $session_id)
User=1000

The session-status and show-ssession system commands of loginctl have different behavior without arguments: session-status uses the current session, but show-ssession uses the manager. However, using show-session is preferable for script use due to its machine-readable output. This is why two invocations of loginctl are needed.

Florian Weimer
  • 32,022
  • 3
  • 48
  • 92
3

user1683793's findUser() function ported to bash and extended so it returns usernames stored in NSS libraries as well.

#!/bin/bash

function findUser() {
    thisPID=$$
    origUser=$(whoami)
    thisUser=$origUser

    while [ "$thisUser" = "$origUser" ]
    do
        ARR=($(ps h -p$thisPID -ouser,ppid;))
        thisUser="${ARR[0]}"
        myPPid="${ARR[1]}"
        thisPID=$myPPid
    done

    getent passwd "$thisUser" | cut -d: -f1
}

user=$(findUser)
echo "logged in: $user"
asdfghjkl
  • 31
  • 1
  • 4
  • FYI: this function (and the one it was based on) won't cycle back through multiple shells spawned by sudo nested in each other. – asdfghjkl Dec 11 '13 at 16:31
2

cycling back and giving a list of users

based on user1683793's answer

By exlcuding non-TTY processes, I skip root as the initiator of the login. I'm not sure if that may exlcude too much in some case

#!/bin/ksh
function findUserList
{
    typeset userList prevUser thisPID thisUser myPPid myPid myTTY myComm
    thisPID=$$                 # starting with this process-ID
    while [ "$thisPID" != 1 ]  # and cycling back to the origin
    do
        (  ps -p$thisPID -ouser,ppid,pid,tty,comm ) | grep $thisPID | read thisUser myPPid myPid myTTY myComm
        thisPID=$myPPid
        [[ $myComm =~ ^su ]] && continue        # su is always run by root -> skip it
        [[ $myTTY == '?' ]] && continue         # skip what is running somewhere in the background (without a terminal)
        if [[ $prevUser != $thisUser ]]; then   # we only want the change of user
                prevUser="$thisUser"            # keep the user for comparing
                userList="${userList:+$userList }$thisUser"  # and add the new user to the list
        fi
        #print "$thisPID=$thisUser: $userList -> $thisUser -> $myComm " >&2
    done
    print "$userList"
    return 0
}

logname or who am i didn't give me the desired answer, especially not in longer lists of su user1, su user2, su user3, ...

I know the original question was from a long time ago but people (such as me) are still asking and this looked like a good place to put the solution.

ULick
  • 969
  • 6
  • 12
2

Alternative to calling ps multiple times: do one pstree call

pstree -lu -s $$ | grep --max-count=1 -o '([^)]*)' | head -n 1

output (when logged in as even): (evan)

pstree arguments:

  • -l: long lines (not shortening)
  • -u: show when user changes as (userName)
  • -s $$: show parents of this process

Get the first user change (which is login) with grep -o and head.

limitation:the command may not contain any braces () (it does not normally)

simohe
  • 613
  • 7
  • 20
1

You can get it from the owner of the controlling terminal. Here's an old C code for a "whowasi" utility: http://sivann.gr/software/whowasi.c

sivann
  • 2,083
  • 4
  • 29
  • 44