#!/usr/bin/perl
#! @PERL@ 
# Jens Benecke
# (Heiko Schlittermann)

my $USAGE = '
Usage: $ME [options] command 

	-u --users	u1:u2:u3:...	...
	-g --groups	g1:g2:g3:...	...
	-c --command	command
	-d --debug		debugging on?
	-n --nothing	do nothing, just show (dry run)

Execute a command for each user. The users LOGIN, GROUP, REALNAME, HOME,
UID, GID can be specified like this on the command line and will be
replaced. e.g.: 

  $ME <-g ...|-u ...> script.pl LOGIN GROUP REALNAME UID GID HOME

will execute script.pl for each user , e.g. will execute

  script.pl hans users \"Hans Wurst\" 602 100 /home/hans

You can specify as many users/groups as you want, seperating them with
colons or spaces. This thing does NOT YET support NIS or something, it
has to run on the SERVER!

';

#use strict;
use File::Basename;
use User::pwent;
use Getopt::Long;

###########################################################################
#
# KONFIGURATION
#
# Users / groups under this ID are NEVER touched (e.g. root/wheel)
# my $NEVER_ID = 100;
my $NEVER_ID = 20;	# we need to treat Debians "dialout" group 

###########################################################################
#
# INTERNA
#
my $ME = basename $0;
#my $opt_groups;
#my $opt_users;
#my $opt_command;
my $opt_dry = 0;
my $opt_debug = 0;
my %gname_list; my %gid_list;
my %groups; my %users;

sub debug(@);
sub user_ok($$);	# users/groups = global erspart uns blöde "->"-Referenzen

MAIN: {

	# users/groups _muss_ einen String bekommen (=s)
	GetOptions(
		"nothing" => \$opt_dry,
		"users=s" => \$opt_users,
		"groups=s" => \$opt_groups,
		"command=s" => \$opt_command,
		"debug!" => \$opt_debug,
	) or die eval "\"$USAGE\"";

	# ich will "foreachuser --users "`ls -1 /home/`" benutzen können, daher
	# Space als Seperator erlaubt
	# Hashes sind für "Ist User A in Gruppe B" Fragen DEUTLICH einfacher!
	defined($opt_groups) and my @groups = split /[ ,:]/, $opt_groups;
		for(@groups) { $doitforgroup{$_} = 1; }
	defined($opt_users) and my @users = split /[ ,:]/, $opt_users;
		for(@users) { $doitforuser{$_} = 1; }

	(!defined($opt_groups) && !defined($opt_users)) and die eval "\"$USAGE\"";

#	if($opt_debug) {
#		print "Groups: "; for(@groups) { print $_." "} ; print "\n";
#		print "Users : "; for(@users) { print $_." "} ; print "\n";
#		print "Groups: "; while(@a=each(%doitforgroup)){print " ".$a[0] if($a[1]);}
#		print "\nUsers : "; while(@a=each(%doitforuser)) {print " ".$a[0] if($a[1]);}
#		print "\n";
#	}


	# Syntax: $gmember{username} = "$group1 $group2 $group3 ...";
	open(GROUP, "/etc/group");
	while(<GROUP>) {
		chomp;
		my ($name, $x, $gid, $members) = split /:/;
		next if($gid<$NEVER_ID);
		@members = split /,/, $members;
		for(@members) { $gname_list{$_} .= "$name "; }
		for(@members) {   $gid_list{$_} .= "$gid "; }
	}

	setpwent();
	while(my $pw = getpwent()) {
		# debug $pw->name(), " ", $pw->uid(), "\n";
		next if ($pw->uid() < $NEVER_ID) or ($pw->gid() < $NEVER_ID);
		my ($realname) = $pw->gecos() =~ /^([^,]+)/;
		my $group = getgrgid($pw->gid());
		my $home = $pw->dir();
		my $uid = $pw->uid();
		my $gid = $pw->gid();
		my $name = $pw->name();

		# Gruppenmitgliedschaft mit der main group vorfüllen.
		# leading space ist wichtig wg. regexp in user_ok(), da ohne
		# "Randzeichen" priv auch auf private passen würde -> wollen wir nicht
		$gname_list{$pw->name()} .= $group." ";
		$gid_list{$pw->name()} .= $pw->gid." ";
		
		next unless &user_ok($pw->name(), $pw->uid());

		my $cmd = $opt_command;
		$cmd =~ s/LOGIN/$name/g;			$cmd =~ s/GROUP/$group/g;
		$cmd =~ s/HOME/$home/g;				$cmd =~ s/REALNAME/\"$realname\"/g;
		$cmd =~ s/UID/$uid/g;				$cmd =~ s/GID/$gid/g;

		debug("EXEC: $cmd");
		$opt_dry or system($cmd);

	}


	# debug-ausgabe: Gruppenzugehörigkeit
	if($opt_debug) {
		while(@a=each(%gname_list)) {  print $a[0]."\t".$a[1]."\n"; }
		while(@a=each(%gid_list)) {  print $a[0]."\t".$a[1]."\n"; }
	}
	

	#next unless user_ok($name, $gid, $users, $groups);
	#debug("$name\t$x\t$uid\t$gid=$group\t$gcos\t$home\t$shell");

}


###########################################################################

sub debug(@) { 
	return unless $opt_debug;
#	warn "debug: ", @_;
	print "debug: @_\n";
}


# Prüfen, ob Username u/o Gruppe auf die Kommandozeile paßt, also mitbehandelt
# werden soll. Liefert 1=TRUE wenn Name "paßt".
sub user_ok($$) {
	my($name, $gid) = @_;

	return 1 if($doitforuser{$name}==1);	# User wurde verbatim als Parameter übergeben

	# ODER: Es wurden Gruppennamen übergeben -> durch die Gruppenzugehörigkeit
	# des Users wandern
	if(defined($opt_groups)) {	# Gruppenkrempel einlesen

		# sofort raus, wenn User in Gruppe < 100 (=0)
		my @suppgid = split(/ /, $gid_list{$name});
		for(@suppgid) { return 0 if(($_=~/\d+/) && ($_ < $NEVER_ID)) };	# never touched groups

		# sofort raus, wenn "passende Gruppe" gefunden (=1)
		my @suppgr = split(/ /, $gname_list{$name});
		for(@suppgr) { return 1 if(($_=~/[^ ]*/) && ($doitforgroup{$_})) };
	}

	return 0;
}


# Dummuser-Hilfe. :)
sub usage() {
	print <<EOSHIT ;
Syntax: foreachuser \<-g group-list|-u user-list\> '\<command\> PARAMETERS'

Execute a command for each user. The users LOGIN, GROUP, REALNAME, HOME,
UID, GID can be specified like this on the command line and will be replaced.

user-list and group-list can contain regexps OR be seperated with '|'
(but NO SPACES), e.g.
    -g "users|admins|loonies"
    -u "year.."  (matches all two-digit years)
You can only specify EITHER group-list OR user-list. If you use UPPERCASE
letters (i.e. "-G", "-U") the program will run in "dry mode", i.e. do nothing
but show what would have been done.

PARAMETERS:
    are verbatim one or more of "LOGIN", "GROUP", "REALNAME", 
    "UID", "GID" and "HOME". Must be uppercase, in the order the
    application wants them.

EXAMPLES:
    \$ foreachuser -g dummies '/bin/cp /etc/dummyfile HOME'
    will copy /etc/dummyfile in the HOME of all "dummies" group members.

    \$ foreachuser -u ".*" '/bin/mail -s "you are stupid" LOGIN < stupidtext'
    will insult ALL your users, except for group root (it is NEVER touched).

    \$ foreachuser -g users 'kmcreate.pl USER GROUP REALNAME UID GID HOME'
    will run a default \$HOME setup script with pre-configured Netscape etc
    for all users in group "users". This was the original reason for this
    script. E.g. it will call (if such a user exists):
    	kmcreate.pl hans users "Hans Wurst" 602 100 /home/hans
    It is then up to the script to do something useful with this information.

EOSHIT
}


# vim: set tw=80 ts=4 sw=4 nowrap aw:
