safe remote poweroff

If you run a root command shell on your linux machine and remotely administer another one, also via a root command shell, it's a matter of time before you power off the local one only to discover that, oops, no, you just shut down the remote     instead by accident. At that point a remote physical presence is needed, to press the power button. Get in the car, or buy your plane ticket, or embarrass yourself with another phone call to somebody onsite asking them to again fix your mistake for you. Or, you could write some substitute prophylactic code under the same name(s) as the command(s) you habitually use for turning the machine off (probably shutdown, poweroff, or halt). Write it so it deflects (does not actually run) the poweroff attempt, instead printing a helpfully alarmist message reminding the adminstrator that OMG he appears to be trying to turn off the remote machine, telling him how to do so if it's what he actually intended, and exiting. Never again will he do it unawares.

Let's assume the command you always use to put your machines down is poweroff. There is probably more than one solution. But any of them will involve emplacing some code that goes by the name "poweroff" and gets called ahead of the usual code that actually turns out the lights, eclipsing it. The new code will not turn the machine off, but rather print a message saying so and giving the user an alternative direct command that will run the regular poweroff. Except, that's a bit of overkill if done unconditionally. You really want to take that step only in case of a remote connection. If "poweroff" is issued on a local one, you want to let it go through. It's OK, the protection is unneeded, because turning off the machine by accident is recoverable by just turning it right back on again. By contrast a remote user doesn't have that luxury; so the new code should be conditional on whether the user's connection is local or remote.

The assignment to perform:

Operate as root. Write a script to be run in whenever the root user types "poweroff" in place of the regular poweroff. The script logic is to be:

determine if the connection is on a regular tty   # (how? tty, ps, test, $-, $0 might help; see Advanced Bash Scripting Guide sec 36.1)
if "tty"
  print a message on screen saying in effect "You're local, I'm gonna turn you off now"
  pull the trigger by running the regular poweroff 
(machine turns off)
else
  print a warning message on screen declaring which machine this is by hostname and
    printing an alternative command formulation the user could use
    that would pull trigger by running the regular poweroff
  exit with error (exit status value 1) 
(machine does not turn off)
endif

Put your code in a file named safe-remote-poweroff. Make it root-executable. Determine the directory where the poweroff executable file sits. Then determine a directory that will be placed in root's path, whether root connects locally or remotely, and that occurs within PATH earlier than poweroff's directory. You will have to determine this either analytically or empirically. If analytically, look into the bash startup files that run in the 2 possible cases (remote root, local root) and see what the PATH variable gets set to. If empirically (easier), just log in both ways and examine the results (i.e., the content of the PATH variables you get). Find a directory that shows up in both. Put your script file into it. Note that there is no need to use your script for non-root users. They need no protection from running the real poweroff, because it does its own checking and powers off only if being run as root.

Manually create an alias for your your script, under the name "poweroff". This gives it prior precedence over running the regular executable file by that name. That code turns off the machine; yours doesn't. (Another possibility would be to make a function out of your code, since functions too trump executable files.) Automate this step to make it persistent, by editing the appropriate bash startup script(s) that fires off whenever root logs in. You need to identify a startup script that runs both when connecting locally and remotely.

Connect all 4 ways-- local and remote root, local and remote non-root-- and make sure typing "poweroff" does what it's supposed to in each. Do this all on a local linux machine. Where you need a remote connection, simulate/achieve it by ssh'ing or telneting from your machine to itself. Make sure that in both root cases, your new code gets called. But also make sure that in those two cases the alternative code prescribed to the user to bring it down actually does do so. Make sure that in both local cases, "poweroff" does run the regular poweroff executable, and it prints its refusal, "poweroff: Need to be root". Any time the machine goes down but you need to continue this exercise, reboot it.

Here's what should happen when you are connected remotely and try to power off, as non-root and as root:


Perform this exercise at home. When you have it working please:
 1 - drop a copy of it into your assignments subdirectory, and
 2 - bring your file to class and set your classroom machine up the same way, so that it works and can be examined.


For reference:

This assignment calls on you to know

 - the basics of execution precedence among different types of code that have the same name
 - which shell startup scripts run under what scenarios
 - how to construct an "if" conditional
 - how to detect if you are local or remote

Below are a couple of summary slides and the man page section about bash invocation, for your reference.

 

 
INVOCATION
       A login shell is one whose first character of argument zero is a	 -,
       or one started with the --login option.

       An interactive shell is one started without non-option arguments and
       without the -c option whose standard input and error are	 both  con-
       nected  to  terminals  (as  determined by isatty(3)), or one started
       with the -i option.  PS1 is set and $- includes i if bash is  inter-
       active,	allowing  a  shell  script  or	a startup file to test this
       state.

       The following paragraphs describe  how  bash  executes  its  startup
       files.	If  any of the files exist but cannot be read, bash reports
       an error.  Tildes are expanded in  file	names  as  described  below
       under Tilde Expansion in the EXPANSION section.

       When  bash  is  invoked	as an interactive login shell, or as a non-
       interactive shell with the --login option, it first reads  and  exe-
       cutes  commands	from  the  file	 /etc/profile, if that file exists.
       After   reading	 that	file,	it   looks   for   ~/.bash_profile,
       ~/.bash_login, and ~/.profile, in that order, and reads and executes
       commands from the first	one  that  exists  and	is  readable.	The
       --noprofile  option may be used when the shell is started to inhibit
       this behavior.

       When a login shell exits, bash reads and executes commands from	the
       files ~/.bash_logout and /etc/bash.bash_logout, if the files exists.

       When an interactive shell that is not a login shell is started, bash
       reads and executes commands from ~/.bashrc,  if	that  file  exists.
       This may be inhibited by using the --norc option.  The --rcfile file
       option will force bash  to  read	 and  execute  commands	 from  file
       instead of ~/.bashrc.

       When  bash  is started non-interactively, to run a shell script, for
       example, it looks for the  variable  BASH_ENV  in  the  environment,
       expands	its  value if it appears there, and uses the expanded value
       as the name of a file to read and execute.  Bash behaves as  if	the
       following command were executed:
	      if [ -n "$BASH_ENV" ]; then . "$BASH_ENV"; fi
       but  the	 value	of  the PATH variable is not used to search for the
       file name.

       If bash is invoked with the name sh, it tries to mimic  the  startup
       behavior	 of historical versions of sh as closely as possible, while
       conforming to the POSIX standard as well.  When invoked as an inter-
       active  login  shell,  or  a  non-interactive shell with the --login
       option,	it  first  attempts  to	 read  and  execute  commands  from
       /etc/profile  and ~/.profile, in that order.  The --noprofile option
       may be used to inhibit this behavior.  When invoked as  an  interac-
       tive  shell  with  the  name  sh,  bash	looks for the variable ENV,
       expands its value if it is defined, and uses the expanded  value	 as
       the name of a file to read and execute.	Since a shell invoked as sh
       does not attempt to read and execute commands from any other startup
       files,  the  --rcfile option has no effect.  A non-interactive shell
       invoked with the name sh does not attempt to read any other  startup
       files.  When invoked as sh, bash enters posix mode after the startup
       files are read.

       When bash is started in posix mode, as with the --posix command line
       option,	it  follows  the POSIX standard for startup files.  In this
       mode, interactive shells expand the ENV variable	 and  commands	are
       read  and  executed  from the file whose name is the expanded value.
       No other startup files are read.

       Bash attempts to determine when it is being run	with  its  standard
       input  connected	 to  a	network connection, as when executed by the
       remote shell daemon, usually rshd, or the secure shell daemon  sshd.
       If  bash	 determines  it	 is being run in this fashion, it reads and
       executes commands from ~/.bashrc, if that file exists and  is  read-
       able.   It will not do this if invoked as sh.  The --norc option may
       be used to inhibit this behavior, and the  --rcfile  option  may	 be
       used  to	 force another file to be read, but rshd does not generally
       invoke the shell with those options or allow them to be specified.

       If the shell is started with the effective user (group) id not equal
       to  the	real user (group) id, and the -p option is not supplied, no
       startup files are read, shell functions are not inherited  from	the
       environment,  the  SHELLOPTS, BASHOPTS, CDPATH, and GLOBIGNORE vari-
       ables, if they appear in	 the  environment,  are	 ignored,  and	the
       effective  user	id is set to the real user id.	If the -p option is
       supplied at invocation, the startup behavior is the  same,  but	the
       effective user id is not reset.
           -- bash man page