File: //scripts.20110531.215904.25158/safetybits.pl
#!/usr/bin/perl
#map (removed) - not safe with perlcc
################################################################
# safe_chown - Operates exactly like perl's chown, only it does
# NOT chown hard or soft linked files.
# Params:
# UID - User's numeric ID or user's name.
# GID - User's numeric ID, or undef if user name is provided.
# Files- A list of files to chown.
sub safe_chown {
my ($uid, $gid, @files) = @_;
my $count = 0;
if ($uid !~ m/^\d+$/) {
$uid = (getpwnam($uid))[2];
}
if ($gid !~ m/^\d+$/) {
$gid = (getgrnam($gid))[2];
} elsif ($gid == -2) {
$gid = (getpwuid($uid))[3];
}
if ($uid eq "" || $gid eq "") { print "safe_chown: Error Fetching the uid/gid"; return(); }
foreach my $file (@files) {
if ( (-l $file) || (ishardlink($file)) ) {
#print "${file} is a link. Not changing ownership.\n";
next;
}
chown $uid, $gid, $file;
$count++;
}
return $count;
}
##################################################################
# safe_recchown
# See safe_chown.
# This function provides the same functionality as safe_chown
# only it provides a recursive interface for chowning entire
# directory trees.
sub safe_recchown {
my ($uid,$gid,@files) = @_;
my $count = 0;
if ($uid !~ m/^\d+$/) {
$uid = (getpwnam($uid))[2];
}
if ($gid !~ m/^\d+$/) {
$gid = (getgrnam($gid))[2];
} elsif ($gid == -2) {
$gid = (getpwuid($uid))[3];
}
if ($uid eq "" || $gid eq "") { print "safe_recchown: Error Fetching the uid/gid"; return(); }
foreach my $file (@files) {
next if (-l $file) || (ishardlink($file));
if (-d $file) {
opendir(DIR,$file);
my @nfiles = grep { !/^\.+$/ } readdir(DIR);
my @newfiles;
foreach my $nfile (@nfiles) {
push(@newfiles,"${file}/${nfile}");
}
closedir(DIR);
$count += safe_recchown($uid,$gid,@newfiles);
}
$file =~ /(.*)/; $file = $1;
chown $uid, $gid, $file;
$count++;
}
return $count;
}
##################################################################
# safe_lrecchown
# See safe_chown.
# This function provides the same functionality as safe_chown
# only it provides a recursive interface for chowning entire
# directory trees. However it only checks for symlinks and
# not hard links
sub safe_lrecchown {
my ($uid,$gid,@files) = @_;
my $count = 0;
if ($uid !~ m/^\d+$/) {
$uid = (getpwnam($uid))[2];
}
if ($gid !~ m/^\d+$/) {
$gid = (getgrnam($gid))[2];
} elsif ($gid == -2) {
$gid = (getpwuid($uid))[3];
}
if ($uid eq "" || $gid eq "") { print "safe_lrecchown: Error Fetching the uid/gid"; return(); }
foreach my $file (@files) {
next if (-l $file);
if (-d $file) {
opendir(DIR,$file);
my @nfiles = grep { !/^\.+$/ } readdir(DIR);
my @newfiles;
foreach my $nfile (@nfiles) {
push(@newfiles,"${file}/${nfile}");
}
closedir(DIR);
$count += safe_lrecchown($uid,$gid,@newfiles);
}
$file =~ /(.*)/; $file = $1;
chown $uid, $gid, $file;
$count++;
}
return $count;
}
#################################################################
sub ishardlink {
my ($file) = @_;
return if ( -d $file );
my $linkage = (stat($file))[3];
return ($linkage > 1)?1:0;
}
################################################################
# safe_chmod - Provides a perl-like interface to chmod.
# Prior to chmoding a file, safe_chmod setuid's to the
# given user.
# Params:
# Perms - Numeric permissions to chmod the file.
# User - User's UID or User name.
# Files - Array of files to chmod.
sub safe_chmod {
my ($perms, $user, @files) = @_;
if ($user !~ m/^-?\d+$/) { $user = (getpwnam($user))[2]; }
my @dirs = ();
my @files2 = ();
my $dircnt = 0;
foreach my $file (@files) {
if (-d "$file") {
push(@dirs,"$file");
} else {
push(@files2,"$file");
}
}
# Allow setgid directories that are not the user's group
if (@dirs) {
foreach my $dir (@dirs) {
if ($user == ((stat("$dir"))[4])) {
$dircnt = $dircnt + chmod $perms,$dir;
}
}
}
if (@files2) {
if ($pid = fork()) {
waitpid($pid,0);
} else {
if ($> != ${user}) {
setuids($user);
}
foreach my $file (@files2) {
chmod $perms,$file;
}
exit();
}
}
return (($#files2 + 1) + $dircnt);
}
################################################################
# safe_recchmod - A recursive version of safe_chmod
sub safe_recchmod {
my ($perms, $user, @files) = @_;
my $count=0;
if ($user !~ m/^\d+$/) { $user = (getpwnam($user))[2]; }
if ($pid = fork()) {
waitpid($pid,0);
} else {
if ($> != ${user}) {
setuids($user);
}
foreach my $file (@files) {
if ( -d $file ) {
opendir(DIR,$file);
my @nfiles = grep { !/^\./ } readdir(DIR);
my @newfiles;
foreach my $nfile (@nfiles) {
$nfile =~ s/(.*)/$1/;
push @newfiles, "${file}/${nfile}";
}
closedir(DIR);
$count += safe_recchmod($perms,$user,@newfiles);
}
$file =~ /(.*)/; $file = $1;
chmod $perms,$file;
$count++;
}
exit();
}
return $count;
}
################################################################
sub setuids {
my($user) = $_[0];
my($uid,$gid);
if ($< != 0) { warn "Attempting to setuid as user $>"; return(); }
if ($user !~ /^\d+$/) {
(undef,undef,$uid,$gid) = getpwnam($user);
}
else {
$uid = $user;
$gid = (getpwuid($uid))[3];
}
if ( ! ($( = int($gid)) ) {
print "error setting gid\n";
exit;
}
if ( ! ($) = "$gid $gid") ) {
print "error setting gid\n";
exit;
}
if (! (($< = $uid) && ($> = $uid)) ) {
die "error setting uid ($uid) [$user]\n";
}
return $uid;
}
sub runasuser {
my($user,@CMDS) = @_;
if ($pid = fork()) {
waitpid($pid,0);
} else {
setuids($user);
exec(@CMDS);
exit();
}
}
1;