How can I find out the original username a process was started with?


How can I find out the original username a process was started with?

There is a perl script that needs to run as root but we must make sure the user who runs the script did not log-in originally as user ‘foo’ as it will be removed during the script.
So how can I find out if the user, who might have su-ed several times since she logged in has not impersonated ‘foo’ at any time in that chain?
I found an interesting perl script that was calling the following two shell scripts, but I think that would only work on Solaris.
my $shell_parent =
`ps -ef | grep -v grep | awk \'{print \$2\” \”\$3}\’ | egrep \”^@_\” | awk \'{print \$2}’`;

my $parent_owner =
`ps -ef | grep -v grep | awk \'{print \$1\” \”\$2}\’ | grep @_ | awk \'{print \$1}\’`;

This needs to work on both Linux and Solaris and I’d rather eliminate the repeated calls to he the shell and keep the whole thing in Perl.


Solution 1:

Quick and dirty and (UNIX only):

my $user = (split /\s/,`who am i`)[0];

The who am i command returns the owner of the TTY – i.e. who you were when you logged in.

If you want to do this in pure perl:

use POSIX;
my $tty = POSIX::ttyname(1); # The tty we are running in
my $uid = (stat $tty)[4];    # The owner uid of that tty
my $user = getpwuid($uid);   # The user with that uid

This will return the correct user, even after multiple su’s. This usually freaks out your (less experienced) sysadmins.

Solution 2:

Here’s a Perl program that checks for direct setuid change:

#! /usr/bin/perl

sub callingUser() {
    my ($login, $pass, $uid, $gid) = getpwuid($<);
    return $login;

sub effectiveUser() {
    my ($login, $pass, $uid, $gid) = getpwuid($>);
    return $login;

printf("Real user name: %s\n", effectiveUser());
printf("Calling user name: %s\n", callingUser());

But since you mentioned that the setuid change may have occured anytime before, you probably have to parse the output of ps: I would do it using the following command. This command only uses features defined in POSIX, so I hope it is portable to all kinds of systems:

ps -e -o pid,ppid,user,ruser

Solution 3:

Maybe the following is what you want. The function hasBeenUser reads the process table and then follows the process chain from the current process down the parent process. If any of the processes on the way has a user or real user field equal to the username in question, the function returns a nonzero value.

#! /usr/bin/perl

sub hasBeenUser($) {
        my ($username) = @_;

        my $procs = {};
        open(PS, "ps -e -o pid,ppid,user,ruser |") or die;
        while (defined(my $line = <PS>)) {
                next unless $line =~ m"^(\d+)\s+(\d+)\s+(\S+)\s+(\S+)\s+$";
                my ($pid, $ppid, $user, $ruser) = (int($1), int($2), $3, $4);
                $procs->{$pid} = [$pid, $ppid, $user, $ruser];
        close(PS) or die;

        my $pid = $$;
        while (exists($procs->{$pid})) {
                my $proc = $procs->{$pid};
                delete $procs->{$pid}; # don't risk ending in an endless loop.
                warn "D: checking process $pid\n";
                if ($proc->[2] eq $username || $proc[3] eq $username) {
                        warn "E: process $pid was called by $username.\n";
                        return 1;
                last if $pid < 2;
                $pid = $proc->[1];
        return 0;

hasBeenUser("del"); # should return 0
hasBeenUser("root"); # should return nonzero

Solution 4:

I recognized a corner case when calling scripts from mc (at least in our RHEL’s), which results that the who am i does not output anything. To circumvent that, I produced the following one-liner in bash:

REALUSERNAME=$(ps uhp `ps -AjH | grep \`ps -u $USER fh | awk '{ print $0; if(index($0, "ps -u $USER fh")) exit 0;}' | tac | awk '{if(!index($0, "\\\\\_")){print $1; exit 0;}}'\` | awk '{print $3}'` | awk '{print $1}')

Essentially, this walks backwards on the tree output of ps -u $USER fh and then crops on the topmost username column.

Thoughts, better solutions are welcome 🙂