#!/usr/bin/perl
#$Id: sudoshell,v 1.14 2002/04/03 23:10:20 howen Exp $

$ENV{PATH}="/bin:/usr/bin:/usr/sbin:/usr/local/bin"; # Give ourselves a safe one.
use strict;
my $GREP = "grep";
my $SUDO="sudo";
my $PS;
my $initscr;
# OS dependencies
if ($^O eq 'solaris' || $^O eq 'linux'){
  $PS="ps -elf";
  $initscr="/etc/init.d/sudoscriptd";
} elsif($^O eq 'freebsd' || $^O eq 'openbsd') {
  $PS="ps -aux";
  $initscr="/usr/local/etc/rc.d/sudoscriptd.sh";
} else {
  print <<'EOM';
Sorry, but your OS is not among the ones I support Currently, that's
linux, solaris, freebsd and openbsd That's because those are the ones
I have access to. If you'd like support for your OS, either give me a
root shell (!) on a representative system running your OS, or port it
yourself and send me (hbo@egbok.com) the diffs. If system directory locations
(i.e. /var/log and /var/run) don't have to change, and your system has Perl
5 and POSIX,  That shouldn't be too hard. See the PORTING document in the
distribution for details.
EOM

  exit;
}
#'
# No more failing open. Good rhetoric. Bad practicum.
check_sudoscriptd();

my $rundir="/var/run/sudoscript";
my $fifo="$rundir/typescript";
if ($>){ # not run as root. Try sudo
  exec "$SUDO $0";
}
if ($>){
  print "Not root! Exiting.\n";
  exit;
}
if (!defined $ENV{SHELL}){
  print "Can't determine your shell. Set the SHELL env variable! Exiting.\n";
  exit;
}

# Don't take their word for it.
open SHELLS, "/etc/shells" || die $!;
my $shellsre=join "|", map {chomp;s~/~\\\/~g;$_} (<SHELLS>);
close SHELLS;

if ($ENV{SHELL}!~/$shellsre/){
  print "Your shell (".$ENV{SHELL}.") isn't in /etc/shells! Exiting.\n";
  exit;
}

my $name;
if ($ENV{SUDO_UID}){
  ($name) =getpwuid $ENV{SUDO_UID};
} else {
  $name = "root";
}

if (-p $fifo){ # script to the fifo if it exists
  open FIFO, ">>$fifo" || die $!;
  print FIFO "sudoshell started by $name\n";
  close FIFO;
  exec "script $fifo";
} else {
  print "The logging FIFO doesn't exist. Can't run shell!\n";
}
sub check_sudoscriptd {
  if (!checkpid()){
    my $ans;
    print "The sudoscriptd doesn't appear to be running!\n";
    print "Would you like me to start it for you? (requires root privilege)? ";
    $ans=<>;
    chomp $ans;
    die "Can't run sudoshell without sudoscriptd" if ($ans!~/^[Y|y]/);
    print <<'EOM';
This will be a one-off startup of the daemon. You may have
to arrange for it to be started when the system starts, if that's
what you want. See the INSTALL file in the distribution for details.
EOM
# bloody emacs sytax highlighting doesn't grok here docs
    if (! -x $initscr){
      print "Hmm.. I can't seem to find the sudoscriptd startup file.\n";
      print "Please tell me where it is. or just return for exit ";
      $ans=<>;
      chomp $ans;
      if (-x $ans) {
	$initscr=$ans;
      } else {
	die "Can't run sudoshell without sudoscriptd";
      }
    }
    system($SUDO, $initscr, "start",'&');
    print "waiting for the daemon ..";
    sleep 3;
    print "done\n";
    if (!checkpid()){
      print "Sorry, but I can't seem to start sudoscriptd for you!\n";
      print "exiting\n";
      exit;
    }
  }
}
sub checkpid {
  my $dpidf="/var/run/sudoscriptd.pid";
  my $dpid;
  my @ret;
  my $gotone=0;
  if (-e $dpidf){
    open DPID, $dpidf ||  die $!;
    $dpid=<DPID>;
    chomp $dpid;
    @ret=`$PS |$GREP $dpid | $GREP -v $GREP`;
    $gotone= ($#ret >-1 && $ret[0]=~/sudoscriptd/);
  }
  return $gotone;
}


=pod

=head1 NAME

  sudoshell, ss - Run a root shell with logging

=head1 SYNOPSIS

  sudoshell or sudo sudoshell

=head1 DESCRIPTION

I<sudoshell> runs the I<script> command with a fifo as the
typescript. Used in conjunction with I<sudoscriptd> and I<sudo>, it
provides a way to maintain the sudo audit trail while running a root
shell.

=head1 README

I<Sudo> is a tool used for running programs with root privilege. Its
major benefit is the audit trail it provides, as it logs each
invocation with the command name, its arguments and the user who ran
it. Because this audit trail is lost if a user runs a shell (e.g. bash
or csh) with sudo, many sites restrict sudo to not allow such
usage. Since this can cause problems, (see L<SUDO AND SHELLS>) many
users prefer to retain the root password, even if it means forgoing
support. This outcome also results in a loss of the audit trail, while
increasing the chances that an unmanaged system will become a support
problem later.

Sudoshell is a small Perl script that works in conjunction with a
logging daemon (see L<sudoscriptd>) to log all activity within a root
shell. It uses the unix I<script> command to create the log. Once
invoked, all console commands and output are logged to a fifo. The
logging daemon reads from this fifo and manages log files to store the
data produced. The logs are rotated to ensure that they do not
overflow the disk space on the logging partition. Sudoshell checks to
see if the daemon is running and offers to start it if it is not.
(It does this with sudo, so you need to have sudo access to perform this
step._) Sudoshell then checks to see if it has been run with root privilege, 
via 'sudo sudoshell' or otherwise. If not, it reinvokes itself using sudo. 
The script then checks the user's SHELL environment variable. If the value of 
this variable doesn't match one of the shells listed in /etc/shells,
sudoshell refuses to run. Next the logging fifo is checked.  If it
exists, sudoshell runs the script command using the fifo as the
typescript. If it doesn't exist, sudoshell exits. (This shouldn't happen
because the daemon's status has already been checked.)

=head1 SUDO AND SHELLS

Most root tasks can be accomplished with sudo without running a shell. 
However certain tasks, such as running privileged commands in a pipeline, 
can be more difficult using sudo. Since sudo sometimes prompts for a 
password (depending on how long ago the user last authenticated) you can 
run into quirky situations like this:

  howen@ivan03|509> sudo ls | sudo more
  Password:Password:(enter password)
  (enter password)
  #sudoshell#
  CVS
  sudoscriptd
  sudoscriptd~
  sudoshell
  sudoshell~
  howen@ivan03|510>

In this case we get two password prompts, right on top of one another. We enter
the password for the first prompt, and sudo waits for the next one. Since
the prompt is on the preceding line, this can be very confusing.

Another place sudo has difficulty is with I/O redirection:

  howen@ivan03|504> sudo chown root /tmp/foo
  howen@ivan03|505> ls -l /tmp/foo
  -r--r--r--   1 root     other       1464 Mar 25 13:10 /tmp/foo
  howen@ivan03|506> sudo ls >>/tmp/foo
  bash: /tmp/foo: Permission denied
  howen@ivan03|507> sudo ls | sudo cat >>/tmp/foo
  bash: /tmp/foo: Permission denied

But this works:

  howen@ivan03|508> sudo ls | sudo perl -e 'open OUT, ">>/tmp/foo";while(<>){print OUT;}'

It's not something you can generally recommend, however.

=head1 BUGS

Sudoshell logs the name of the user that successfully invokes it
when it starts, but there is no way to uniquely stamp subsequent lines
(generated by script(1)) with the user's ID. A solution to this would
involve a quite-a-bit more complicated c program. This solution is
imperfect, but simple and portable.

=head1 SEE ALSO

L<sudoscriptd>
L<script>

=head1 LICENSE

L<sudoshell> may be distributed under the same terms as Perl itself.

=head1 PREREQUISITES

sudoscriptd - L<http://www.egbok.com/sudoscript/sudoscriptd> or on CPAN

sudo - L<http://www.courtesan.com/sudo/index.html>

=head1 OSNAMES

C<Solaris>

C<Linux>

C<freebsd>

C<openbsd>

=head1 SCRIPT CATEGORIES

UNIX/System_administration

=head1 CONTRIBUTORS

The following people offered helpful advice:

   Dan Rich       (drich@emplNOoyeeSPAMs.org)
   Alex Griffiths (dag@unifiedNOcomputingSPAM.com)
   Bruce Gray     (bruce.gray@aNOcSPAMm.org)

=head1 AUTHOR

Howard Owen (hbo@egbok.com)