#!/usr/bin/perl #! $Id: sudoscriptd,v 1.11 2002/04/03 19:55:35 howen Exp $ use strict; use POSIX qw(mkfifo setsid tzname); use POSIX qw(:sys_wait_h); # Set up some date values; POSIX::localtime(time); my @zones=tzname(); # Become a daemon my $pid=fork; exit if $pid; die "Couldn't fork $!" unless defined($pid); die "Couldn't start new session $!" unless POSIX::setsid(); # Record our PID for those who would wish us ill 8) my $rundir="/var/run/sudoscript"; open PID,">/var/run/sudoscriptd.pid"; print PID "$$\n"; close PID; # Open the log my $MAXLOGSIZE=1024*1024*2; my $size=openlog(); # Create and open the FIFO that sudoshell will log to my $fifo="$rundir/typescript"; mktypescript(); while (1){ no strict qw(subs); sysopen(FIFO, $fifo,O_RONLY); use strict; while (<FIFO>){ $_=datestamp()." $_"; $size += length; # Base $size is set in openlog(); print LOG; waitpid -1,&WNOHANG; # Reap any compressors out there if ($size > $MAXLOGSIZE){ $size=0; openlog(); } } } sub mktypescript { # Create the fifo directory if it doesn't exist if (! -d $rundir){ mkdir $rundir, 0700 || die "Can't mkdir $rundir $!"; } # Nuke any old FIFOs left (NB: This daemon is _single_threaded_!) # (Unlike sudoshell) if (-e $fifo){ unlink $fifo; } mkfifo($fifo,0600) || die "Can't make fifo $fifo $!"; } sub openlog{ # (re)open the log file # Rotate the logs if larger than $MAXLOGSIZE my $log="/var/log/sudoscript"; my $pid; my($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size); if (-e $log){ ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size)=stat $log; $size=rotate_log() if ($size>$MAXLOGSIZE) } my $op = ">"; $op.=">" if (-e $log); chmod 0600,$log; open LOG, "$op$log"; my $foo=select LOG; $|=1; # Unbuffered select $foo; return $size; } sub rotate_log { # Rotate the log files my $logdir="/var/log"; my $logbase="sudoscript"; my $log="$logdir/$logbase"; my $tlog="$log.$$"; # Move the old log out of the way link $log, $tlog; unlink $log; # Fork a child to do the rotation/compression my ($pid)=fork; return $pid if $pid; # CHILD my $MAXLOGS=10; my ($currlog,$lastlog); for (my $i=$MAXLOGS-1;$i;$i--){ $currlog="$logdir/$logbase.$i.gz"; $lastlog="$logdir/$logbase.".($i+1).".gz"; if (-e $currlog){ unlink $lastlog; link $currlog,$lastlog; } } unlink "$logdir/$logbase.1.gz"; my $cmd="gzip -c $tlog >$logdir/$logbase.1.gz"; `$cmd`; chmod 0600,"$logdir/$logbase.1.gz"; unlink $tlog; exit 0; } sub datestamp { # Sorta kinda syslog format 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(); return sprintf "%3s %3s %02d %02d:%02d:%02d %3s %04d", $wdnames[$wday],$monames[$mo],$d,$h,$mi,$s,$zones[$isdst],$y+1900; } =pod =head1 NAME sudoscriptd =head1 SYNOPSIS sudoscriptd =head1 DESCRIPTION I<sudoscriptd> is a daemon for logging output from L<sudoshell(8)>. Used with that script, it provides an audit trail for shells run under sudo. =head1 README I<sudoscriptd> creates a named pipe (FIFO) in a spool area, and hangs around waiting for someone to write to it. When output is received, it is timestamped and placed in a log file in /var/log. The size of the data received is monitored. When the size of the log plus the size of the data exceed 2MB, the log is rotated and gzipped in a subprocess. At most 10 logs are kept hanging around. Multiple processes can write to the fifo at the same time. The output will get a little jumbled, but timestamps may help you sort things out. =head1 FILES The fifo is /var/run/sudocript/typescript. It is owned by root and mode 600. The log files are named /var/log/sudoscript(.extension if any). Sudoscriptd stores its PID in /var/run/sudoscriptd.pid. =head1 BUGS If the log file is greater than 2MB in size when sudoscript runs, it will fork a process to compress the log, but will not I<waitpid> until output is first detected on the fifo. This will result in a zombie process in the system until the fifo is used by L<sudoshell(8)>, at which time the child process will be reaped. The datestamp() routine is not locale aware and returns American English values. =head1 SEE ALSO L<sudoshell> =head1 LICENSE L<sudoscriptd> may be distributed under the same terms as Perl itself. =head1 PREREQUISITES sudoshell - L<http://www.egbok.com/sudoscript/sudoshell> 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)