#!/usr/bin/perl
#$Id: sudoshell-in,v 1.9 2004/11/22 16:09:05 hbo Exp $

use Fcntl qw(O_RDWR O_WRONLY);
use POSIX qw(pause);
use strict;
use warnings;
use Getopt::Long;
use lib "/usr/lib/sudoscript";
use Sudoscript;

my $ss=Sudoscript->new();
exit if (! defined $ss);

my ($user,$newenv);
GetOptions(
	   "user:s" => \$user,
	   ""       => \$newenv,
	  );

my $GREP = $ss->GREP();
my $SUDO=$ss->SUDO();
my $PS=$ss->PS();
my $SCRIPT=$ss->SCRIPT();
my $INITSCR=$ss->INITSCR();


# Check to see if sudoscriptd is running. Offer to start it if not
$ss->check_ssd();


# Get our current ID's pwent
my ($name,$passwd,$uid,$gid,$quota,$comment,$gcos,$dir,$shell,$expire) =getpwuid($>)
	or die "no passwd entry for current user: $>\n";

# Check our -u parameter.
if ($user){

  # Get the UID of the -u user and compare it to ours.
  ($name,$passwd,$uid,$gid,$quota,$comment,$gcos,$dir,$shell,$expire) =getpwnam($user)
	or die "no passwd entry for user: $user\n";

  if ($uid != $>){ # We have not become the requested user yet
    # Call sudo to give us the right identity.
    my $reexec="$SUDO -u $user $0 -u $user";
    $reexec .= " -" if ($newenv);
    exec $reexec;
  }
  # Otherwise, check if we are root.
} elsif ($>){ # No -u and not run as root. Try sudo
  my $reexec= "$SUDO $0";
  $reexec .= " -" if ($newenv);
  exec $reexec;
}

# We are either root or the -u user here forward

# Check the user's shell

$shell=$ENV{SHELL} if ($ENV{SHELL}); # Environment overrides pwent (obtained above)

# Don't take their word for it.
# Get the list of shells in /etc/shells and turn them into a single regular expression
open SHELLS, "/etc/shells" or die "Can't open /etc/shells $!\n";
my $shellsre=join "|", map {chomp;s~/~\\\/~g;$_} (<SHELLS>);
close SHELLS;

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

# Get the name we came from, if available.
# (SUDO_UID  could be blank, if root ran us to start with. In that
# case we get the name from the first getpwuid above.)
($name) = getpwuid $ENV{SUDO_UID} if ($ENV{SUDO_UID});

# Implement the "-" parameter.
if ($newenv){
  my ($n,$p,$u,$g,$q,$c,$gc,$home,$sh) = getpwuid $>;
  $ENV{HOME} = $home if ($home);
  $ENV{SHELL}= $sh   if ($sh and $sh =~ /$shellsre/);
}

# Open the master daemon's front-end FIFO if it exists
# (This is why we need rwx perms for group to $fifodir.)
my $rundir="/var/run/sudoscript";
my $fifo="$rundir/rendezvous";

if (-p $fifo){
  sysopen (FIFO,$fifo,O_WRONLY) or die $!;
  # Unbuffer it
  my $stdout=select FIFO;
  $|=1;
  select $stdout;

  $SIG{WINCH}=sub{return}; # Logger will signal us

  # Our handshake with sudoscriptd will differ based on whether we are running as
  # root or an unprivileged user.
  my $handshake;
  if ($user){
    $handshake="$name $$ $user\n"; # $name is SUDO_USER or root. $user is non-root user we have become
  } else {
    $handshake="$name $$\n";
  }
  # Say HELO to sudoscriptd
  print FIFO "HELO $handshake\n";

  # Wait for the logger to be ready
  pause;

  # We have been awakened, prepare to script(1) to the new FIFO
  # close te old FIFO first
  close FIFO;

  # Session FIFO name differs based on whether we are running as
  # root or an unprivileged user.
  my $fifo2;
  if ($user){
    $fifo2=$rundir."/ssd.${name}_${user}_$$/$name$$.fifo";
  } else {
    $fifo2=$rundir."/ssd.${name}_root_$$/$name$$.fifo";
  }

  # Here we go
  my $script=$ss->SCRIPT();

  system "$script $fifo2";

  # script(1) session has finished
  # Reopen the old fifo to let the master know we are done.
  sysopen (FIFO,$fifo,O_WRONLY) or die $!;

  # Say GDBY, dick
  print FIFO "GDBY $handshake\n";
  close FIFO;

  # all done.
  exit;
} else { # Problem with the FIFO. (no -p $fifo)
  print "The logging FIFO doesn't exist. Can't run shell!\n";
}


=pod

=head1 NAME

  sudoshell, ss - Run a shell with logging

=head1 SYNOPSIS

sudoshell|ss [-] [-u|--user I<username>]

=head1 VERSION

This manpage documents version 2.1.2 of sudoshell.

=head1 DESCRIPTION

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

=head1 README

I<sudo> is a tool used for running programs with privileges other than those
normally possessed by the user. Often, the privileges are those of the I<root> account,
but they could as well be those of any other user. One of I<sudo>'s 
major benefits 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(8)>) to log all activity within a
shell. It uses the unix L<script(1)> 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.

When started, I<sudoshell> checks to see if L<sudoscriptd(8)> is
running and offers to start it if it is not.  (It does this with sudo,
so you need to have sudo root access to perform this step. See
CONFIGURATION below) Sudoshell then checks to see if it has been run
as the correct user (either root or some other user with the -u
switch. See below.)  via 'sudo sudoshell' or otherwise. If not, it
reinvokes itself using sudo. The script then checks the user's
shell. If the SHELL environment variable is set, I<sudoshell> uses
that. If not, the shell entry from the passwd file is used.  If the
value thus obtained doesn't match one of the shells listed in
/etc/shells, sudoshell refuses to run. Finally, I<sudoshell> execs
script(1), pointing the output to a logging FIFO maintained by
L<sudoscriptd(8)>, which gives the user a shell as the desired user.

=head1 CONFIGURATION

I<sudoshell> uses L<sudo(8)> to perform all its authentication and
privilege escalation.  The I<sudoshell> user must therefore be in the
I<sudoers> file (See L<sudoers(5)>.)  with an entry that allows
running I<sudoshell> as the desired user. See the SUDOCONFIG file in
the distribution for details. (On Linux, this will be in
/usr/share/doc/sudoscript-VERSION. Everywhere else, it's in
/usr/local/doc/sudoscript-VERSION.)

=head1 SWITCHES

=over 4

=item -

Like 'su -'. This switch will load the environment of the user you become,
rather than retaining yours.


=item -u <user> 

If this switch is given
along with a username, I<sudoshell> will ask I<sudo> to give the ss
user the identity of the user named with the -u switch. If no -u
switch is given, I<sudoshell> will ask for a root shell.

Example:

sudoshell -u oracle

The idea of running shells as users other than root came from Donny Jekels.

=back

=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:

  hbo@egbok|509> sudo ls | sudo more
  Password:Password:(enter password)
  (enter password)
  #sudoshell#
  CVS
  sudoscriptd
  sudoscriptd~
  sudoshell
  sudoshell~
  hbo@egbok|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:

  hbo@egbok|511 > ls -l /tmp/foo
  -r--r--r--   1 root     other       1464 Mar 25 13:10 /tmp/foo
  hbo@egbok|512 > sudo ls >>/tmp/foo
  bash: /tmp/foo: Permission denied
  hbo@egbok|513 > sudo ls | sudo cat >>/tmp/foo
  bash: /tmp/foo: Permission denied

But this works:

  hbo@egbok|514 > sudo ls | sudo tee -a /tmp/foo >/dev/null

It's not very intuitive, however.

The problem occurs because the shell implements I/O redirection
before it invokes the command, which is sudo, NOT ls.

Globbing has problems for the same reason. But in this case, there's 
no good workaround, short of letting the user run a shell:


  hbo@egbok|515 > mkdir fff
  hbo@egbok|516 > chmod 700 fff
  hbo@egbok|517 > touch fff/foo
  hbo@egbok|518 > sudo chown root fff
  Password:
  hbo@egbok|519 > cd fff
  bash: cd: fff: Permission denied
  hbo@egbok|520 > sudo cd fff 
  sudo: cd: command not found
  hbo@egbok|521 > sudo rm fff/*
  rm: cannot remove `fff/*': No such file or directory

The cd fails because cd is a bash builtin, and sudo doesn't know anything
about it. The "globbing" fails because the shell tries to expand the
wildcard before executing the command, which is sudo, not rm.

=head1 BUGS

Please let me know if you find any.

=head1 SEE ALSO

sudoscript(8)

sudoscriptd(8)

Sudoscript(3pm)

sudo(8)

sudoers(5)

=head1 PREREQUISITES

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

=head1 OSNAMES

C<Solaris>

C<Linux>

C<OpenBSD>

C<FreeBSD>

C<HP-UX>

=head1 SCRIPT CATEGORIES

UNIX/System_administration

=head1 CONTRIBUTORS

The following people offered helpful advice and/or code:

   Dan Rich       (drich@emplNOoyeeSPAMs.org)
   Alex Griffiths (dag@unifiedNOcomputingSPAM.com)
   Bruce Gray     (bruce.gray@aNOcSPAMm.org)
   Chan Wilson    (cwilson@coNrOp.sSgPi.cAoMm>
   Tommy Smith    (tsNmOith@eSaPtAeMl.net)
   Donny Jekels   (donny@jNOeSkPeAlMs.com

=head1 AUTHOR

Howard Owen, E<lt>hbo@egbok.comE<gt>

=head1 COPYRIGHT AND LICENSE

Copyright 2002,2003 by Howard Owen

sudoscript is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

=cut