Skip site navigation (1)Skip section navigation (2)
Date:      Fri, 12 Mar 1999 08:20:43 +0200 (SAST)
From:      Geoff Rehmet <geoff@hangdog.is.co.za>
To:        hackers@freebsd.org
Subject:   The infamours "temp cleaner" debate
Message-ID:  <199903120620.IAA09997@hangdog.is.co.za>

next in thread | raw e-mail | index | archive | help
Hmm, yes, after everyone though that one had been put to bed.
I've been trying to write a script that is not susceptible to
security problems, to handle my temp cleaning - delete old files
from /tmp and /var/tmp.

The idea was to write it in Perl, without running any external
commands, and sitting in a chroot jail.  That way, it should (I
hope) not be possible for anyone to exploit any race conditions,
and subvert my script to delete any file on the system.  The
script is somewhat paranoid about re-stat(2)ing things that have
alrrady been stat(2)ed, in case there is any change in the 
unerlying directory strcuture that it is traversing.  (If it gets
unconfortable, it bails.)
Now I'm ready to throw it open for comments.  If it lives up to
scrutiny here, and there is general agreement, I'd like to
commit it, to have a better replacement for the (currently disabled)
mechanism in /etc/periodic/daily.
Another use for the script is to periodically clean out
/usr/share/man/cat?. 

I've also included my version of /etc/periodic/daily/110.clean-tmps.
(Watch out for the overlong lines in this file.)  110.clean-tmps
should be a bit more elegant, allowing people to specifify how many
days to keep files for etc.

Especially useful, would be any suggestions to make the script
more paranoid.
Also, the name "purgeold" sucks - can anyone think of a better
one?

Geoff.


--- snip, snip -- /usr/local/bin/purgeold -----------------------
#!/usr/bin/perl
#
# Copyright (c) 1999 Geoffrey M. Rehmet
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 3. All advertising materials mentioning features or use of this software
#    must display the following acknowledgement:
#	   This product includes software developed by Geoffrey M. Rehmet
# 4. The name of Geoffrey M Rehmet may not be used to endorse or promote 
#    products derived from this software without specific prior written 
#    permission.
#
# THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL GEOFFREY M. REHMET BE LIABLE FOR ANY DIRECT, 
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
# $Id$
#


#
# Purge old files from a temp dir.  Runs in a chroot jail
# and does not call any external executable programs.
#

use Cwd;


# Defaults - some are args, some should be args
$REMOVETIME = 7;	# Delete older than 1 week
$VERBOSE = 0;		# By default, be terse
$DOIT = 1;			# Actually delete files

$ENV{'PATH'}='';
$SIG{'INT'}='IGNORE';
$SIG{'QUIT'}='IGNORE';

if ( @ARGV < 1 ) {
	usage();
}

while ( $arg = shift ) {
	if		($arg eq '-v')		{ $VERBOSE = 1; }
	elsif	($arg eq '-d')		{ $REMOVETIME = shift; }
	elsif	($arg eq '-n')		{ $DOIT = 0 ; $VERBOSE = 1; }
	elsif	($arg eq '-xf')		{ push @EXCLUDE_FILE, shift ; }
	elsif	($arg eq '-xd')		{ push @EXCLUDE_DIR, shift ; }
	elsif	($arg =~ "^-.*")	{ die "invalid argument $arg"; }
	else						{ $TMPDIR = $arg ; }
}

$TMPDIR eq '' &&  die "No temp directory specified";
$TMPDIR =~ '\/$' || ( $TMPDIR = "$TMPDIR/" );	# make sure path ends with '/'
$TMPDIR =~ "^\/" || die "$TMPDIR is not an absolute path";

verbose ("Clearing files older than $REMOVETIME days from $TMPDIR\n");

$basedir = "";	# We start out at the root ($TMPDIR already starts with "/")

# Check that we have a directory
# Use stat (rather than lstat) - we are happy to follow a symlink on this
# one occasion
stat $TMPDIR || die "Cannot stat $TMPDIR";
if (-d _ ) {
	if ($> == 0){
		(chroot $TMPDIR) || die "Could not chroot to $TMPDIR" ;
		# Dir to clean is now our root dir!
		$basedir = $TMPDIR;		# Remember our old starting point
		$basedir =~ s/\/$//;	# Remove trailing '/' from base
		$TMPDIR="/"; 
	}
	chdir($TMPDIR) || die "Cannot chdir to $TMPDIR";
	do_dir($TMPDIR);
	exit 0;
} else {
	die "$TMPDIR is not a directory"
}

#
# Recursively handle each directory
#
sub do_dir ($) {
	my $item = shift;
	my $direntry; 
	my @dirs;
	my $wdir;
	my $subdir;


	# Keep a record of where we are
	# This is used later to see if anyone has changed things while we
	# weren't looking
	$wdir = cwd;

	# Open dir and get rid of "." and ".."
	(opendir DIRHND, ".") || die "Cannot open directory $item";
	(readdir DIRHND) || die "Error reading $item"; 	# Chomp "."
	(readdir DIRHND) || die "Error reading $item"; 	# Chomp ".."

	# Go through directory entries, keeping subdirectories for later
	# attention.
	while ($direntry = readdir DIRHND) {
		lstat $direntry || die "Cannot stat $direntry";
		if (-d _ ) {
			# Keep subdirectories for later use
			-l $direntry  &&  die "$direntry is a symlink";
			$skip = 0;
			for ($j = 0 ; $j < @EXCLUDE_DIR; $j++) {
				$skip = $skip || ($direntry =~ /\A$EXCLUDE_DIR[$j]\Z/);
			}
			$skip || push @dirs, $direntry;
		} else {
			# We've got a regular file, device, symlink, named pipe or socket
			$skip = 0;
			for ($j = 0 ; $j < @EXCLUDE_FILE; $j++) {
				$skip = $skip || ($direntry =~ /\A$EXCLUDE_FILE[$j]\Z/);
			}
			if (!$skip) {
				lstat $direntry || die "cannot stat $direntry";
				if ( -A _ > $REMOVETIME)  {
					verbose ("Deleting ${basedir}${item}${direntry}\n");
					$DOIT && ( unlink $direntry  ||
						die "Could not delete $direntry");
				}
			} 
		}
	}
	closedir DIRHND;

	# Now traverse subdirectories
	while ( $subdir = pop @dirs ) {
		lstat($subdir) || die "Cannot stat $subdir";
		-d _ || die "$subdir is no longer a directory!!";
		chdir ($subdir) || die "Could not chdir from $item to $subdir";
		do_dir("${item}${subdir}/");

		# We intentionally "chdir .." 
		chdir ("..") || die "Could not chdir to parent";

		# Check that we are where we think we are
		if (cwd ne $wdir ) {
			$newwd = cwd;
			die "$newwd != $wdir: Directory has been moved or deleted!";
		}

		# Now check if we need to delete the subdirectory (stat again)
		lstat $subdir || die "cannot stat $subdir";
		if ( -M _ > $REMOVETIME ) {
			verbose ("Deleting directory ${basedir}${item}${subdir}\n");
			$DOIT && rmdir $subdir;
		}
	}
}

#
# print verbose messages
#
sub verbose ($) {
	my $message = shift;
	
	$VERBOSE && print STDERR $message;
}

#
# Print usage message
#
sub usage ($) {
	print STDERR 
		"Usage: $0 <tmpdir> [-v] [-d <days>] [-xf <file>] [-xd <dir>] [-n]\n";
	print STDERR " options:\n";
	print STDERR "    -v:        print verbose messages\n";
	print STDERR "    -d <n>:    delete files older than n days\n";
	print STDERR "    -n :       Don't delete any files (turns on verbose)\n";
	print STDERR "    -xf <f>:   Do not delete files matching regex f\n";
	print STDERR "    -xd <d>:   Do not delete directories matching regex d\n";
	print STDERR "\n";
	print STDERR "-xf and -xd may be repeated\n";
	exit (1);
}
--- snip, snip -- /etc/periodic/daily/110.clean-tmps-------------
#!/bin/sh
#
# $Id: 110.clean-tmps,v 1.3 1997/09/11 15:21:30 ache Exp $
#
#


if [ -d /tmp ]; then
	/usr/local/bin/purgeold /tmp -d 4 -xf '\.X.*-lock' -xf 'quotas' -xd 'lost+found' -xd '\.X11-unix' -v
fi


if [ -d /var/tmp ]; then
	/usr/local/bin/purgeold /var/tmp -d 4 -xd 'vi.recover' -xf 'quotas' -xd 'lost+found' -v
fi
-----------------------------------------------------------------
-- 
Geoff Rehmet, The Internet Solution - Infrastructure 
tel: +27-11-283-5462, fax: +27-11-283-5401 mobile: +27-83-292-5800
email: geoffr@is.co.za 
URL: http://www.is.co.za 


To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-hackers" in the body of the message




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?199903120620.IAA09997>