# How to get the login name of the user who invoked "shutdown"?



## Terry_Kennedy (Dec 15, 2018)

I have a script (let's call it "updown.sh") in /usr/local/etc/rc.d/ which does some host-specific housekeeping on startup and shutdown. One of the things it does is send email to root (forwarded to a distribution list) saying the system was shut down. I'm trying to add "by _username_" to that email. Here's the relevant part of the script:

```
...
        # Try something different here
        # echo "Host `hostname` has been shut down by `who am i | awk '{print $1}'` at `date`." | mail -s "Host `hostname` has been shut down" root 1>/dev/null 2>&1;
        # Third time's the charm?
        # echo "Host `hostname` has been shut down by `id -p | head -1 | cut -f 2` at `date`." | mail -s "Host `hostname` has been shut down" root 1>/dev/null 2>&1;
        echo "Host `hostname` has been shut down by $USER at `date`." | mail -s "Host `hostname` has been shut down" root 1>/dev/null 2>&1;
...
```
As you can see, I've tried 3 different things. The $USER method returns a null string, while the other two return "root" regardless of who invoked the shutdown script. I looked in the rc(8) manpage and didn't see anything. The info exists _somewhere_ as the broadcast message during the shutdown has the invoking user's login name.

Does anyone know where the invoking user's login name is stored and how to access it from a rc.d script?


----------



## kpa (Dec 15, 2018)

I believe it's only stored in the console message buffer so you might be out of luck here unless there's some method of accessing that buffer from a userland program.

You won't get the username that way because the shutdown process is started asynchronously via shutdown(8) and is actually controlled by init(8) from that on and there is no concept of user name when the shut down process is running.


----------



## T-Daemon (Dec 15, 2018)

If I'am not mistaken, the FreeBSD security event auditing could generate user shutdown logs.

Chapter 16. Security Event Auditing

16.1 Synopsis


> After reading this chapter, you will know:
> How to configure event auditing on FreeBSD for users and  processes.


16.2. Key Terms


> record: an audit log entry describing a security event. Records contain a record event type, information on the subject (user) performing the action, date and time information, information on any objects or arguments, and a success or failure condition. ....



Table 16.3.1. Default Audit Event Classes



Class Name

Description

Action

ex

exec

Audit program execution. Auditing of command line arguments and environmental variables is controlled via audit_control(5) using the argv and envv parameters to the policy setting.


----------



## ralphbsz (Dec 15, 2018)

I fear your basic problem is that shutdown can only be done by root.  I haven't looked at the shutdown source code for how that is implemented; the traditional technique is to check that the effective user ID is zero, or that the current process is a member of a certain group (commonly 0, wheel, or operator).  So either the real human who typed the command that caused the shutdown (somewhat indirectly, perhaps through your updown.sh script) was logged in as root (meaning: typed root's password into a login prompt, or used ssh to log in directly to the root account, perhaps using an ssh key if you allow that), or they used su or sudo to get there.

And therein lies the complexity of the situation.  Most likely, the user id of the process that is running the command is zero, so using techniques like the "id" command or looking at the $USER variable of the shell, you will only find out that they already are root (duh).  I think the best information will come from the tracking of who has logged in and out, which is what "who" does, using the UTX database (see apropos utx, and look with a binary editor at /var/run/utx*).  In some fashion the w and who commands can track back from the currently running process to who last logged in, and display that user name.  I think they work by comparing the tty that is stdin/stdout/stderr of the current process.  I just tried it, by running "who am i" with various forms of output redirection: it simply looks at stdin, so "who am i > /tmp/foo 2> /dev/null" works, while "who am i < /dev/null" does not.

And this is probably why your first trick failed: At the moment you ran your "who" command, stdin must have already been disconnected from the terminal.  On the other hand, looking at your 1-line commands, it only redirects stdout and stderr (I only see "|", "1>" and "2>&1" in there).  So I bet something earlier redirected stdin already.  I just checked, that that "something" is not sudo: I can create a tiny script that is just two lines (#!/bin/sh and who am i), run it through sudo, and it still works.

I fear the answer is: You need to check the whole pipeline from where the user types a command to where your "who" command actually runs for input redirection.

By the way, this also means that an evil user can foil your recording, by running "updown.sh < /dev/null".  If that really bugs you, one could check on that in the script and refuse to work.


----------



## Terry_Kennedy (Dec 15, 2018)

ralphbsz said:


> I fear your basic problem is that shutdown can only be done by root.  I haven't looked at the shutdown source code for how that is implemented; the traditional technique is to check that the effective user ID is zero, or that the current process is a member of a certain group (commonly 0, wheel, or operator).  So either the real human who typed the command that caused the shutdown (somewhat indirectly, perhaps through your updown.sh script) was logged in as root (meaning: typed root's password into a login prompt, or used ssh to log in directly to the root account, perhaps using an ssh key if you allow that), or they used su or sudo to get there.


Correct.


> And therein lies the complexity of the situation.  Most likely, the user id of the process that is running the command is zero, so using techniques like the "id" command or looking at the $USER variable of the shell, you will only find out that they already are root (duh).  I think the best information will come from the tracking of who has logged in and out, which is what "who" does, using the UTX database (see apropos utx, and look with a binary editor at /var/run/utx*).  In some fashion the w and who commands can track back from the currently running process to who last logged in, and display that user name.  I think they work by comparing the tty that is stdin/stdout/stderr of the current process.  I just tried it, by running "who am i" with various forms of output redirection: it simply looks at stdin, so "who am i > /tmp/foo 2> /dev/null" works, while "who am i < /dev/null" does not.


I ran the script after adding:

```
# debug stuff start
env > /tmp/shutdown.env
printenv > /tmp/shutdown.printenv
dmesg -a > /tmp/shutdown.dmesg
# debug stuff end
```
and the environment variables are pretty spartan:

```
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin
PWD=/
HOME=/
RC_PID=4547
```



> And this is probably why your first trick failed: At the moment you ran your "who" command, stdin must have already been disconnected from the terminal.  On the other hand, looking at your 1-line commands, it only redirects stdout and stderr (I only see "|", "1>" and "2>&1" in there).  So I bet something earlier redirected stdin already.  I just checked, that that "something" is not sudo: I can create a tiny script that is just two lines (#!/bin/sh and who am i), run it through sudo, and it still works.
> 
> I fear the answer is: You need to check the whole pipeline from where the user types a command to where your "who" command actually runs for input redirection.


The redirection is a red herring. This is running in the context of the rc(8) system, and the redirection is just there to avoid spamming the console. If you run this as a regular shell script ouside of the rc(8) system, any of the 3 methods works fine.


> By the way, this also means that an evil user can foil your recording, by running "updown.sh < /dev/null".  If that really bugs you, one could check on that in the script and refuse to work.


To some extent, that is true of everything in /etc/rc.d and /usr/local/etc/rc.d. Everything in updown.sh is either saving state (which generally requires privilege to collect the data, which will error out, and always requires privilege to write the saved state in a mode 700 directory, or the inverse). And if the user comments out all of that stuff, the only thing left is the email, which gives away the username that did it.


----------



## Terry_Kennedy (Dec 15, 2018)

kpa said:


> I believe it's only stored in the console message buffer so you might be out of luck here unless there's some method of accessing that buffer from a userland program.
> 
> You won't get the username that way because the shutdown process is started asynchronously via shutdown(8) and is actually controlled by init(8) from that on and there is no concept of user name when the shut down process is running.


Yes. I added some debugging (as shown in my earlier reply) and the environment is very bare:

```
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin
PWD=/
HOME=/
RC_PID=4547
```
The message buffer was a good clue. Since the whole rc(8) system is running as root, there's no problem looking at the buffer. And the shutdown message is in there, near the end:

```
Dec 15 13:42:15 hostname shutdown[4545]: power-down by terry:
```
So I just need to parse that buffer and pick out the username. It also lets me get the desired end state (power-down, reboot, etc.) and add that to the email.


----------



## Terry_Kennedy (Dec 15, 2018)

T-Daemon said:


> If I'am not mistaken, the FreeBSD security event auditing could generate user shutdown logs.


That's a lot of work to go through for what is pretty much a WIBNI. And I don't want to extend the shutdown interval to collect the data, and the shutdown may be because there's a problem with the system and using the minimal amount of "stuff" needed may be less likely to cause problems.


----------



## ralphbsz (Dec 15, 2018)

Exactly: the output redirection is a red herring.  I fear the question is: who is doing input redirection?  If only stdin was still connected to the terminal when your script run, then "who am i" should have worked.  And I don't know enough about how rc works for a quick guess.


----------



## rigoletto@ (Dec 15, 2018)

TL;DR if you want to get track of who issued shutdown/reboot may be better to add the users whom are allowed to do it to the `operator` group, then you will get a log like this:


```
Dec 12 04:00:37 workstation shutdown[9133]: reboot by rigoletto:
```

instead of this:


```
Dec 12 18:09:20 workstation shutdown[17069]: reboot by root:
```


----------



## Terry_Kennedy (Dec 15, 2018)

Rigoletto said:


> TL;DR if you want to get track of who issued shutdown/reboot may be better to add the users whom are allowed to do it to the `operator` group, then you will get a message like this:
> 
> 
> ```
> ...


That works for both `su` and `sudo` as well as for the operator group. I was just hoping there was a more "official" way of obtaining the information than rooting around in the dmesg(8) buffer.


----------



## T-Daemon (Dec 16, 2018)

Terry_Kennedy said:


> That's a lot of work to go through for what is pretty much a WIBNI.


Yes you are right. Overkill.

You are saying in post #1


Terry_Kennedy said:


> I have a script ... which does some host-specific housekeeping on startup and shutdown. One of the things it does is send email to root (forwarded to a distribution list) saying the system was shut down.


and in post #7


Terry_Kennedy said:


> And I don't want to extend the shutdown interval to collect the data,


When shuting down, when all processes are terminate, I don't see how to collect data about the shutdown process and send an email without extending the shutdown interval, or have I misunderstood you?



Terry_Kennedy said:


> I was just hoping there was a more "official" way of obtaining the information than rooting around in the dmesg(8) buffer.


How about the System logging (enabled by default anyway).

/var/log/auth.log (freebsd hostname) ( su, sudo) (third message block - shutdown delayed 5 s)

```
Dec  1 18:14:08 freebsd login: login on ttyv3 as john
Dec  1 18:14:23 freebsd su: john to root on /dev/ttyv3
Dec  1 20:07:37 freebsd su: john to root on /dev/pts/1
Dec  1 20:36:18 freebsd shutdown: power-down by john:  
...
Dec 16 10:40:28 freebsd login[1165]: login on ttyv7 as john
Dec 16 10:46:30 freebsd sudo[1196]:     john : TTY=ttyv7 ; PWD=/usr/home/john ; USER=root ; COMMAND=/sbin/shutdown -p now
Dec 16 10:46:30 freebsd shutdown[1198]: power-down by john:
...
Dec 16 11:26:30 freebsd login[1001]: login on ttyv4 as john
Dec 16 11:28:08 freebsd sudo[1070]:     john : TTY=ttyv4 ; PWD=/usr/home/john ; USER=root ; COMMAND=/sbin/shutdown -p +5s
Dec 16 11:28:13 freebsd shutdown[1072]: power-down by john:
```


----------



## Terry_Kennedy (Dec 18, 2018)

T-Daemon said:


> When shuting down, when all processes are terminate, I don't see how to collect data about the shutdown process and send an email without extending the shutdown interval, or have I misunderstood you?


At the point where rc(8) runs my script, the system can still send email. A "sleep 10" in the script is sufficient to get the email off the system before the shutdown proceeds.


> How about the System logging (enabled by default anyway).


That still involves parsing the same format as I get from dmesg(8), with the overhead of waiting for syslogd(8) to write it to disk, hopefully do a sync(2), and then read the file. dmesg(8) has essentially zero overhead:

```
(0:110) host:~terry# time dmesg -a >/dev/null
0.000u 0.006s 0:00.00 0.0%      0+0k 0+0io 0pf+0w
```

Here's what I ended up with:

```
whodunnit()
{
    # Rummage around in the message buffer entrails to see who
    # requested the shutdown and the type of shutdown.
    who=`dmesg -a | grep "shutdown\[" | awk '{gsub(":",""); printf $8}'`
    # Yes, I know I could do this as an utterly unreadble 1-line awk
    what=`dmesg -a | grep "shutdown\[" | awk '{printf $6}'`
    # "Make it all shiny" (Kaylee)
    what=$(echo "$what" | sed 's/halt/halted/;s/power-down/powered down/;s/power-cycle/power cycled/;s/shutdown/shut down/;s/reboot/rebooted/')
    # Check to make sure we actually found something.
    if [ "x$who" = "x" ]; then
        who="Unknown"
    fi
    if [ "x$what" = "x" ]; then
        what="Unknown"
    fi
}
```


----------



## T-Daemon (Dec 18, 2018)

T-Daemon said:


> How about the System logging (enabled by default anyway).





Terry_Kennedy said:


> That still involves parsing the same format as I get from dmesg(8), with the overhead of waiting for syslogd(8) to write it to disk, hopefully do a sync(2), and then read the file. dmesg(8) has essentially zero overhead:


I see. Thanks for explaining.


----------

