# $Id: Sudoscript.pm,v 1.5 2004/05/30 20:44:47 hbo Exp $
package Sudoscript;
our $VERSION='0.1';

=pod

=head1 NAME

Sudoscript.pm

=head1 SYNOPSIS

 use Sudoscript;
 my $ss=Sudoscript->new();
 .. do stuff with $ss

=head1 DESCRIPTION

A module to implement some common attributes and methods
shared between sudoshell and sudoscriptd. See L<sudoscript(8)>,
L<sudoscriptd(8)> and L<sudoshell(1)> for descriptions of the sudoscript system.

This module implements routines that

=over 4

=item *

Set up the execution environment based on the running OS

=item *

Return command names with switches tuned for the running OS

=item *

Check to see if sudoscriptd is running

=item *

Return date stamp strings in one of several formats

=item *

Close standard I/O channels for daemons, redirecting STDERR to log files.

=back

The following sections docment these routimnes more fully.

=cut

#
use strict;
use warnings;
use POSIX qw(tzname);

=head1 Constructor

The constructor is called new(). It does the usual OO style initialization, then returns the
result of calling _init();

=cut

# The constructor
sub new {
  my $class = shift;
  my $self= bless {}, ref($class) || $class;
  return $self->_init(@_);
}

=pod

The _init() routine:

=over 4

=item *

Initializes the local time zone names

=item *

Sets a safe execution path

=item *

Checks the OS type of the running system

=item *

Sets up some shell commands based on the OS type

=item *

Returns its $self if it recognized the OS, or undef otherwise

=back

=cut

#

sub _init {
  my $self=shift;

  # Get time zone names
  POSIX::localtime(time);
  my @zones=tzname();
  $self->{TZNAMES}=\@zones;

  # Set a safe PATH and define some external programs
  $ENV{PATH}="/bin:/usr/bin:/usr/sbin:/usr/local/bin";

  $self->{GREP} = "grep";
  $self->{SUDO}="sudo";

  # These are OS dependent
  # OS dependencies
  my ($PS,$initscr,$script);
  if ($^O eq 'solaris'){# or $^O eq 'irix'){
    $PS="ps -ef";
    $initscr="/etc/init.d/sudoscriptd";
    $script="script";
    $ENV{PATH} .= ':/usr/freeware/bin:/usr/bsd' if $^O eq 'irix';
  } elsif ($^O eq 'linux'){
    $script="script -f"; # flush channels on linux (gnu script)
    $PS="ps auxww";
    $initscr="/etc/init.d/sudoscriptd";
  } elsif($^O eq 'freebsd' || $^O eq 'openbsd') {
    $PS="ps aux";
    $initscr="/usr/local/etc/rc.d/sudoscriptd.sh";
    $script="script";
  } elsif($^O eq 'netbsd') {
    $ENV{PATH} .= ':/usr/pkg/bin:/usr/pkg/sbin';
    $PS="ps aux";
    $initscr="/usr/pkg/etc/rc.d/sudoscriptd";
    $script="script";
  } elsif($^O eq 'hpux') {
    $PS="ps -ef";
    $initscr="/sbin/init.d/sudoscriptd";
    $script="script";
  } else {
    print <<'EOM';
Sorry, but your OS is not among the ones I support Currently, that's
linux, solaris, hp-ux, freebsd, openbsd and netbsd. That's because those are the
ones I or other contributors 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
#'

    return undef;
  }
  $self->{PS}=$PS;
  $self->{INITSCR}=$initscr;
  $self->{SCRIPT}=$script;
  return $self;
}

=head1 Command Properties

These properties return unqualified command names with switches, when they appear,
appropriate for the running OS. (Since _init() sets the execution path, we do not
fully qualify the paths to these commands.)

=head2 SUDO()

The  sudo(8) command without any switches

=cut

sub SUDO {
  my $self=shift;
  return $self->{SUDO};
}

=head2 GREP()

The grep(1) command, without any switches

=cut

sub GREP {
  my $self=shift;
  return $self->{GREP};
}

=head2 PS()

The ps(1) command with switches that produce a listing parseable by checkpid()

=cut

sub PS {
  my $self=shift;
  return $self->{PS};
}

=head2 INITSCR()

  The complete path to the sudoscriptd init script

=cut

sub INITSCR {
  my $self=shift;
  return $self->{INITSCR};
}

=head2 SCRIPT()

The script(1) command. On Linux, this will have the -q switch added.

=cut

sub SCRIPT {
  my $self=shift;
  return $self->{SCRIPT};
}

=head2 TZNAMES()

The local time zone names set up by _init()

=cut

sub TZNAMES {
  my $self=shift;
  return @{$self->{TZNAMES}};
}

=head1 Methods

=head2 check_ssd()

This method checks to see if sudoscriptd is running (via checkpid())
If not, it offers to start it, and gives some helpful advice regarding starting sudoscriptd
at boot time. It also tells the user they need root sudo privilege to successfully start
sudoscriptd. The method then looks for the sudoscriptd init script set up
by _init(). If that script is not found, or is not executable, the method prompts for
an init script path to use. It then attempts to start the daemon. The method sleeps for
three seconds and then checks again for sudoscriptd with checkpid(). If the daemon
still isn't running, the method B<die>s, taking the caller with it.

=cut

sub check_ssd {
  my $self=shift;
  if (!$self->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 sudo 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 syntax highlighting doesn't grok here docs
    my $initscr=$self->INITSCR();
    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($self->SUDO(), $initscr, "start",'&');
    print "waiting for the daemon ..";
    sleep 3;
    print "done\n";
    if (!$self->checkpid()){
      print "Sorry, but I can't seem to start sudoscriptd for you!\n";
      print "exiting\n";
      exit;
    }
  }
}

=head2 checkpid()

This method gets the PID of the current sudoscriptd out of /var/run/sudoscriptd.pid,
if there is such a file. It  looks for the PID in the process list
If there is such a process, and its name contains 'sudoscriptd', the
method returns the PID. Otherwise, it returns 0.

=cut

sub checkpid {
  my $self=shift;
  my $dpidf="/var/run/sudoscriptd.pid";
  my $dpid;
  my @ret;
  my $gotone=0;
  if (-e $dpidf){
    open DPID, $dpidf or  die $!;
    $dpid=<DPID>;
    chomp $dpid;
    my $GREP=$self->GREP();
    my $PS=$self->PS();
    @ret=`$PS |$GREP $dpid | $GREP -v $GREP`;
    $gotone= ($#ret >-1 && (grep /sudoscriptd/,@ret)>0);
  }
  return $dpid if ($gotone);
  return 0;
}

=head2  datestamp()

This method returns a date stamp string in one of three formats,
depending on the passed parameter.

These are:

=over 4

=item long

wdy mon dd hh:mm:ss TZ yyyy

=item sortable

yyyymoddhhmmss

=item anything else

wdy mon dd hh:mm:ss

=back

 Where:

   wdy     = week day name
   mon     = three letter month name
   TZ      = three letter time zone name (e.g. 'PST')
   yyyy    = four digit year
   mo      = two digit month number
   dd      = two digit day of month
   hh      = two digit hour
   mm      = two digit minute
   ss      = two digit second

=cut

sub datestamp {
  my $self=shift;
  # Sorta kinda syslog format
  my $DATEFMT = shift;
  my @zones=$self->TZNAMES();
  my @monames=('Jan','Feb','Mar','Apr','May','Jun',
	       'Jul','Aug','Sep','Oct','Nov','Dec');
  my @wdnames=('Sun','Mon','Tue','Wed','Thu','Fri','Sat');
  my ($s,$mi,$h,$d,$mo,$y,$wday,$yday,$isdst)=localtime();
  if ($DATEFMT eq 'long'){
    return sprintf "%3s %3s %02d %02d:%02d:%02d %3s %04d",
      $wdnames[$wday],$monames[$mo],$d,$h,$mi,$s,$zones[$isdst],$y+1900;
  } elsif ($DATEFMT eq 'sortable'){
    return sprintf "%4d%02d%02d%02d%02d%02d",
      $y+1900,$mo+1,$d,$h,$mi,$s;
  } else {
    return sprintf "%3s %3s %02d %02d:%02d:%02d",
      $wdnames[$wday],$monames[$mo],$d,$h,$mi,$s;
  }
}

=head2 daemon_io()

This method closes STDIN and STDOUT, and redirects STDERR to a file named:

  /var/run/sudoscriptd/stderr$tag

Where $tag is a string passed to the method. There are commonly three, but sometimes
two or four types of daemons running at all times in the sudoscript system. Each of them
calls daemon_io() and each gets a seperate (per daemon type) stderr log. These are
are overwritten on the next sudoscriptd startup.

=cut


sub daemon_io {
  my $self=shift;
  my $tag=shift;
  my $file="/var/run/sudoscript/stderr";
  $file .=$tag if (defined $tag);
  close STDERR;
  open STDERR,">>$file";
  close STDIN;
  close STDOUT;
}

=head1 SEE ALSO

sudoscript(8)

sudoscriptd(8)

sudoshell(1)

sudo(8)

sudoers(5)

http://www.egbok.com/sudoscript

=head1 AUTHOR

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

=head1 COPYRIGHT AND LICENSE

Copyright 2003 by Howard Owen

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

=cut

1;