#!/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 (){ $_=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 is a daemon for logging output from L. Used with that script, it provides an audit trail for shells run under sudo. =head1 README I 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 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, 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 =head1 LICENSE L may be distributed under the same terms as Perl itself. =head1 PREREQUISITES sudoshell - L or on CPAN sudo - L =head1 OSNAMES C C C C =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)