#!/usr/bin/perl # # Rotates all the logs associated with all the vhosts in an apache config # # $Id: vhost-log-rotator,v 1.3 2004/04/24 20:17:36 nemesis Exp $ # # Copyright (C) 2001-2004 Kees Cook # kees@outflux.net, http://outflux.net/ # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # http://www.gnu.org/copyleft/gpl.html # use Getopt::Std; use strict; umask(0022); our($opt_n,$opt_v,$opt_h,$opt_u,$opt_c); getopts('nvhu:c:'); if ($opt_h) { die "Usage: $0 [-nvh] [-c CYCLES] [-u USER] [HTTPD.CONF] ...\n -n No actions: dry run -- don't actually do anything -v Run verbose -h This help -u USER Have new files owned by USER (default 'root') -c CYCLES How many cycles of rotation to keep (default '3') "; } my $DRYRUN =$opt_n; my $VERBOSE=$opt_v; my $CYCLES =$opt_c || 3; my $USER =$opt_u || "root"; my @CONFS=("/etc/apache/httpd.conf"); if ($ARGV[0]) { @CONFS=@ARGV; } my $COMPRESS=findProggie("gzip"); my $CP=findProggie("cp"); my $CAT=findProggie("cat"); my $EGREP=findProggie("egrep"); my $MV=findProggie("mv"); my $APACHECTL=findProggie("apachectl"); my $CHOWN=findProggie("chown"); my $TOUCH=findProggie("touch"); my @LOGS=findLogs(@CONFS); checkLogs(@LOGS); shuffleLogs(@LOGS); restartDaemon(); compressLogs(@LOGS); sub findProggie { my($prog)=@_; my @LIST = ("/sbin", "/bin", "/usr/sbin", "/usr/sbin", "/usr/local/bin", "/usr/local/sbin"); my($path,$exe); foreach $path (@LIST) { $exe="$path/$prog"; return $exe if (-x $exe); } die "Could not find '$prog'\n"; } sub findLogs { my(@confs)=@_; my(@lines,%logs,$server_root); foreach my $file (@confs) { warn "Processing: $file\n" if ($VERBOSE); @lines=`$CAT $file`; foreach my $line (@lines) { chomp($line); $line=~s/^\s+//; $line=~s/\s+$//; # Locate server root for unqualified paths if ($line =~ /^ServerRoot\s+\"([^\"]+)/) { $server_root=$1; # trim trailing slashes $server_root=~s#/$##g; warn "ServerRoot: $server_root\n" if ($VERBOSE); } # Find any includes if ($line =~ /^Include\s+(\S+)/) { my $glob=$1; # prepend ServerRoot if not full path $glob=$server_root."/".$glob if ($glob!~m#^/#); warn "Include: $glob\n" if ($VERBOSE); my @moreconf=glob($glob); push(@confs,@moreconf); grep(warn("Conf: $_\n"),@moreconf) if ($VERBOSE); } if ($line !~ /^(SSL|)LogFormat\s+/ && $line !~ /^(SSL|)LogLevel\s+/ && $line =~ /^(Error|Custom)Log\s+/) { my @parts=split(/\s+/,$line); # prepend ServerRoot if not full path $parts[1]=$server_root."/".$parts[1] if ($parts[1]!~m#^/#); $logs{$parts[1]}=1; } } } return sort keys %logs; } sub checkLogs { my(@logs)=@_; my $file; foreach $file (@logs) { warn "Log: $file\n" if ($VERBOSE); if (! -f $file) { System("$TOUCH $file"); System("$CHOWN $USER $file"); } } } sub System { my($cmd)=@_; if ($VERBOSE) { warn "$cmd\n"; } if (!$DRYRUN) { system($cmd); } } sub shuffleLogs { my(@logs)=@_; my($file,$cycle,$nextlog); my($orig,$next); foreach $file (@logs) { for ($cycle=$CYCLES; $cycle>0; $cycle--) { $nextlog=$cycle+1; $orig="$file.$cycle"; $next="$file.$nextlog"; if (-f $orig) { System("$COMPRESS -f -9 $orig"); } else { $orig="$orig.gz"; $next="$next.gz"; } System("$MV -f $orig $next 2>/dev/null"); } System("$MV $file $file.1"); # compression comes later System("$CP /dev/null $file"); System("$CHOWN $USER $file"); } } sub compressLogs { my(@logs)=@_; my $file; foreach $file (@logs) { System("$COMPRESS -f -9 $file.1"); } } sub restartDaemon { System("${APACHECTL} graceful | ${EGREP} -v 'gracefully restarted|Processing'"); }