#!/usr/bin/perl
#This make su -c for all users (uid>500 uid<1500)
#$Revision: 1.8 $ <steffen@dett.de>

#I would glad if I would get a mail if somebody modified this
#  little piece!

#Perl's idea about constants: globbing to scalars :))
*MINUID=\500;
*MAXUID=\1500;
#*MINUID=\557;
#*MAXUID=\559;

my %users = ();
my $simu = undef;

use Getopt::Long;

sub build_user_list($)
{
	my $wanted_users_ref = shift; #wanted by commandline --users=..
	my @invalid_users = ();       #wanted but not in getpwent or so
	
	#check if wanted users present or if we need all
	if (  defined($wanted_users_ref) 
           && defined(@$wanted_users_ref)
           && defined($$wanted_users_ref[0])) {
		#enable skip of unlisted user
		$skip_mode = "true";
		#print "skipmode\n";
	} else {
		$skip_mode = undef;
	}

	#print @$wanted_users_ref;
UID:while (($name,$passwd,$uid,$gid,
		$quota,$comment,$gcos,$dir,$shell,$expire) = getpwent()) {
		if ( ($uid >= $MINUID) and ($uid<=$MAXUID)) {
			if ($skip_mode) {
				my $skip = "true";
				foreach $wanted (@$wanted_users_ref) {
					if ($wanted eq $name) {
						$skip = undef;
						$wanted=undef;
					}
				}
				next UID if ($skip);
			}
			#print "User $name (uid:$uid)\n";
			$users{$uid}->{'name'}=$name;		
			$users{$uid}->{'dir'}=$dir;		
		}
	}
    endpwent();
	foreach $invalid (@$wanted_users_ref) {
		if (defined($invalid)) {
			#still here: store as invalid!
			push @invalid_users, $invalid;
		}
	}
	if ($#invalid_users >= 0) {
		print "FATAL: Not all required users verified!\n";
		print "       (they may not exist or have a bad [to small?] uid)\n";
		print "INVALID USERS: ", join(",", @invalid_users), "\n";
		print "*** STOP.\n";
		exit;
	}
}

sub unique()
{
	$unique  = ".su-all.tmp.pid";
	$unique .= $$;
	$unique .= ".time";
	$unique .= time();
	$unique .= ".";
	$unique .= int(100000*rand());
	$unique .= int(100000*rand());
	return $unique;
}

sub check_home($)
{
	$uid = shift;
	#do some checks (i.e. exec & write access to home)
	$file = $users{$uid}->{'dir'} . "/" . unique();
	print " (Testing home dir with file: ~/$unique)\n";
	su_cmd_exec("noout", $uid, "cd ~ && touch $file");
	if (-r $file) {
		unlink $file;
	} else {
		return 0;
	}
	return "true";
}

sub su_cmd_exec($$@)
{
	$out = shift;
	$uid = shift;
	@cmd = @_;
	if ($out eq "noout") {
		$out = "0";
	} elsif ($out eq "out") {
		$out = "true";
	} else {
		print "Outputmode [$out] invalid!!\n";
		exit 0;
	}
	$command = "/bin/su --shell=/bin/bash - " . $users{$uid}->{'name'} . " -c '"
		.  join(" ", @cmd) . "'";
    if ($simu) {
        $command = "/bin/echo $command\n";
    }
	if ($out) {
		print "Command: ", $command, "\n";
		print "-" x 20, "[ Output ]", "-" x 20, "\n";
	}
	print `cd /tmp ; $command`;
	if ($out) {
		print "-" x 18, "[ Output end ]", "-" x 18, "\n";
	}
}


sub do_su()
{
	foreach $uid (sort keys %users) {
		push @all_users, $users{$uid}->{'name'};
		print "\n", "#" x 60, "\n";
		print "# User: ", $users{$uid}->{'name'}, " UID=", $uid, 
		      " Home: ", $users{$uid}->{'dir'}, "\n";
		print "#" x 60, "\n";
		if (check_home($uid)) {
			su_cmd_exec("out", $uid, @ARGV);
		} else {
            if (!$simu) {
			    print "*** FATAL: Home check failed!\n";
			    print "    (press key to continue, q to quit)\n";
    			$input = <STDIN>;
	    		if ($input =~ m/^q/i ) {
		    		print "Aborting.\n";
			    	exit 0;
    			}
	    		push @err_users, $users{$uid}->{'name'};
            } else {
                print "[Homecheck in simu: Assuming success and continueing]\n";
			    su_cmd_exec("out", $uid, @ARGV);
            }
		}
	}
}
sub print_user_list()
{
	my @all_users = ();
	foreach $uid (sort keys %users) {
		push @all_users, $users{$uid}->{'name'};
	}
	print "Userlist (parsed): \"", join('", "', @all_users), "\"\n";
}

sub usage()
{
	print 'This is su for all users $Revision: 1.8 $ <steffen@dett.de>', "\n";
    print "Feel free to use, distribute, re-engineer or modify it\n";
	print "\tusage: $0 [options] <command>\n";
	print "\tFor all users ($MINUID<uid<$MAXUID) <command> is executed\n";
	print "\tafter a simple check useing \"/bin/su - <user> -c '<command>\"'\n";
    print "\tbefore execution a temp-writetest is performed to the home\n";
    print "\tthis avoids accidently useing \"www-run\", \"bin\" or similar\n";
	print "options:  --help                           this help\n";
    print "          --simu                           just echo commands\n";
	print "          --users=<user1>[,<user2>...]     use that users\n";
	exit 1;
}

sub opts() {
	my $help = undef;
	my @users = ();
	GetOptions("users=s" => \@users,
		   "help"   => \$help,
           "simu"   => \$simu);

	if ($help) {
		usage();
	}
 	#else {
	#	print "ok\n";
	#	print join("|", @users), "\n";
	#	print @ARGV;
	#}
	return \@users;
}

if ($#ARGV == -1) {
	usage();
}

my $user_ref = opts();
my @users = ();
foreach $user (@$user_ref) {
	push @users, split(",", $user);
}
#print "\nusers: ", join("|", @users), "\n";

build_user_list(\@users);
print_user_list();
do_su();


print "*** JOB DONE.\n";
print "    Total Users: ", $#all_users + 1, " with ", $#err_users +1 , 
	" errors.\n";
print "    Error Users: ", join(",", @err_users), "\n";

