#!/usr/bin/perl -w
# $Id: user_mgmt.pl 406 2009-04-24 22:52:11Z jonas $
# Copyright (C) 2009 Jonas Genannt <jonas.genannt@brachium-system.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 3 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, see <http://www.gnu.org/licenses/>.

use strict;
use Config::General;
use Getopt::Long;
use Passwd::Unix;
# libcrypt-passwdmd5-perl libconfig-general-perl libstruct-compare-perl libio-compress-bzip2-perl
my $VERSION='$Id: user_mgmt.pl 406 2009-04-24 22:52:11Z jonas $';
sub print_help ;
sub check_uid ;
sub check_gid ;

# set umask to 022 because of bug in Passwd:Unix
umask 022;

my ($config_filename,$verbose);
my $result = GetOptions (
	"verbose"	=> \$verbose,
	"config=s"	=> \$config_filename
	);

if (!defined($config_filename) || ! -f $config_filename) {
	print_help;
	exit 1;
}
if ($< != 0 ) {
	print_help;
	print STDERR "Error: $0 has to be run as user root!\n";
	exit 1;
}

# Initialize configuration file
my $conf   = new Config::General($config_filename);
my %config = $conf->getall();

# Check Conf
$config{'users'}{'default'}{'home'}=~s/\/$//g if ($config{'users'}{'default'}{'home'}=~m/\/$/);

my $pu = Passwd::Unix->new('warnings' => 1);
print "============================== READ GROUP CONF FROM FILE ===================\n" if ($verbose);

# Read the Group from configuration file and create/delete it
foreach my $conf_group (keys(%{$config{'groups'}{'group'}})) {
	if (!defined ( $config{'groups'}{'group'}{$conf_group}{'gid'}) || 
		$config{'groups'}{'group'}{$conf_group}{'gid'}!~m/^[0-9]+$/) {

		print "[E] $0: The group $conf_group has got no gid set!\n";
		next;
	}
	my $active = 1;
	if (defined($config{'groups'}{'group'}{$conf_group}{'active'}) &&
		$config{'groups'}{'group'}{$conf_group}{'active'}=~m/^0$/ ) {
		
		print "[I] We want to delete group $conf_group\n" if ($verbose);
		$active = 0;
	}
	print "[I] active mode of $conf_group: $active \n" if ($verbose);
	if ( $pu->exists_group($conf_group) && $active == 0 ) {
		# Group exits, and we have to delete it
		print "[I] We want to delete group $conf_group\n" if ($verbose);
		$pu->del_group($conf_group);
		next;
	}

	my $gid = $config{'groups'}{'group'}{$conf_group}{'gid'};
	print "[I] gid of $conf_group: $gid\n" if ($verbose);
	if (! $pu->exists_group($conf_group) &&  $active ) {
		# Group does not exists - we create it
		if (check_gid($gid) == 1 ) {
			print STDERR "[E] The gid ($gid) already exists at group file!\n";
			exit 1;
		}
		else {
			print "[I] We want to create group $conf_group\n" if ($verbose);
		 	$pu->group($conf_group, $gid, undef );
		}
	}
	print "\n" if ($verbose);
}


print "============================== READ USER CONF FROM FILE ===================\n" if ($verbose);

# reread the users/group configuration
undef $pu;
$pu = Passwd::Unix->new('warnings' => 1);
# Read the user from configuration file and create/delete it
foreach my $conf_user (keys(%{$config{'users'}{'user'}})) {
	if (!defined( $config{'users'}{'user'}{$conf_user}{'uid'}) &&
		$config{'users'}{'user'}{$conf_user}{'uid'}!~m/^[0-9]+$/) {

		print "[E] $0: The user $conf_user has got no uid set!\n";
		next;
	}
	my $active = 1;
	if (defined($config{'users'}{'user'}{$conf_user}{'active'}) &&
		$config{'users'}{'user'}{$conf_user}{'active'}=~m/^0$/ ) {
		
		print "[I] We want to delete user $conf_user\n" if ($verbose);
		$active = 0;
	}
	my $home  = defined($config{'users'}{'user'}{$conf_user}{'home'})
			? $config{'users'}{'user'}{$conf_user}{'home'} 
			: $config{'users'}{'default'}{'home'} . "/" . $conf_user;
	# remove tailing slash
	$home=~s/\/$// if ($home=~m/\/$/);
	my $shell = defined($config{'users'}{'user'}{$conf_user}{'shell'}) 
			? $config{'users'}{'user'}{$conf_user}{'shell'} : $config{'users'}{'default'}{'shell'};
	my $dfl_group = defined($config{'users'}{'user'}{$conf_user}{'group'})
			? $config{'users'}{'user'}{$conf_user}{'group'} : $config{'users'}{'default'}{'group'};
	my ($dfl_group_gid,undef, undef) = $pu->group($dfl_group);
	# When not defined (Gorup does not exists, set it to 1000!
	# FIXME: perhaps, we should exist here / drop an error!
	$dfl_group_gid    = defined($dfl_group_gid) ? $dfl_group_gid : "1000";

	my $gecos = defined($config{'users'}{'user'}{$conf_user}{'comment'}) 
		&& $config{'users'}{'user'}{$conf_user}{'comment'}=~m/../
			? $config{'users'}{'user'}{$conf_user}{'comment'} : "";
	print "[I] active mode of $conf_user: $active \n" if ($verbose);
	my $uid = $config{'users'}{'user'}{$conf_user}{'uid'};
	print "[I] uid of $conf_user: $uid\n" if ($verbose);
	print "[I] shell of $conf_user: $shell\n" if ($verbose);
	print "[I] home of $conf_user: $home\n" if ($verbose);
	print "[I] group of $conf_user: $dfl_group (GID: $dfl_group_gid)\n" if ($verbose);
	print "[I] comment of $conf_user: $gecos\n" if ($verbose);
	if ( $pu->exists_user($conf_user) && $active == 0 ) {
		# User exits, and we have to delete it
		print "[I] We want to delete user $conf_user\n" if ($verbose);
		$pu->del_user($conf_user);
		next;
	}
	if ( ! $pu->exists_user($conf_user) && $active == 1 ) {
		# User does not exists - we create it
		print "[I] We want to create user $conf_user\n" if ($verbose);
		# TODO: perhaps we should allow to store md5 hashes at config file
		if (check_uid($uid,0) == 1 ) {
			print STDERR "$0: Error uid $uid already exists at passwd!\n";
			exit 1;
		}
		else {
			$pu->user($conf_user,"!",$uid,$dfl_group_gid,$gecos,$home,$shell);
		}
		
	}
	else {
		if (check_uid($uid,$conf_user)== 1 ) {
			print STDERR "$0: Error uid $uid already exists at passwd!\n";
			exit 1;
		}
		else {
			$pu->uid($conf_user, $uid);
			$pu->gecos($conf_user, $gecos);
			$pu->home($conf_user,$home);
			$pu->shell($conf_user,$shell);
			$pu->gid($conf_user,$dfl_group_gid);
		}
	}
	if (defined($config{'users'}{'user'}{$conf_user}{'create_home'}) &&
		$config{'users'}{'user'}{$conf_user}{'create_home'}=~m/^1$/ ) {

		my $homedir = "";
		if (defined($config{'users'}{'user'}{$conf_user}{'home_prefix'}) &&
			$config{'users'}{'user'}{$conf_user}{'home_prefix'}=~m/../ ) {
			
				$homedir = $config{'users'}{'user'}{$conf_user}{'home_prefix'} . "/" . $home;
		}
		else {
				$homedir = $home;
		}
		if ($homedir) {
			if ( ! -d $homedir ) {
				print "[I] We want to create home dir ($homedir) of $conf_user\n" if ($verbose);
				`mkdir -p $homedir`;
				`chown $conf_user:$dfl_group  $homedir`;
				`chmod 750  $homedir `;
			}
		}
	}



	print "\n" if ($verbose);
}


print "======== READ MAPPING GROUP->USERS CONF FROM FILE ===========\n" if ($verbose);

# reread the users/group configuration
undef $pu;
$pu = Passwd::Unix->new('warnings' => 1);
# Read the user from configuration file and create/delete it
foreach my $conf_group (keys(%{$config{'mapping'}{'groups-users'}{'group'}})) {
	print "[I] Now checking group: $conf_group\n" if ($verbose);
	if ($pu->exists_group($conf_group)) {
		if (defined($config{'mapping'}{'groups-users'}{'group'}{$conf_group}{'members'}) &&
			$config{'mapping'}{'groups-users'}{'group'}{$conf_group}{'members'}=~m/../) {
			my @group_members;
			my ($group_gid,undef, undef) = $pu->group($conf_group);
			print  "[I] Group $conf_group GID: $group_gid\n" if ($verbose);
			foreach my $member (split(/,/ , $config{'mapping'}{'groups-users'}{'group'}{$conf_group}{'members'})) {
				print "[I] $conf_group, Member: $member\n" if ($verbose);
				if ($pu->exists_user($member)) {
					push(@group_members,$member);
				}
				else {
					print STDERR "[E] $0: The group $conf_group has got one member ($member) that does not exists at passwd!\n";
				}
			} 
			if (@group_members > 0 ) {
				print "[I] Modify group $conf_group\n" if ($verbose);
				$pu->group($conf_group,$group_gid,\@group_members);
			}
		}
		else {
			print STDERR "[E] $0: The group $conf_group has got no members!\n";
		}
	}
	else {
		print STDERR "[E] $0: The group $conf_group does not exists - can't do user mappings!\n";
	}
}



###########################################################
################### CODE END ##############################
###########################################################

###########################################################
###################### SUBS ###############################
###########################################################

sub print_help {
	print "$0: The user mgmt software\n";
	print "Version: $VERSION\n";
	print "options:\n";
	print "  --config=foo.conf\n";
	print "  --verbose\n";
}
sub check_uid {
	my $uid  = $_[0];
	my $mode = $_[1];
	# if mode defined, than user exists already, and mode = username

	$pu = Passwd::Unix->new('warnings' => 1);
	if (defined($mode) && $mode=~/^0$/ ) {
		foreach my $get_user ($pu->users()) {
			my $get_uid = $pu->uid($get_user);
			return 1 if ($get_uid == $uid )
		}
	}
	else {
		foreach my $get_user ($pu->users()) {
			my $get_uid = $pu->uid($get_user);
			return 1 if ($get_user ne $mode && $get_uid == $uid );
		}
	}
	return 0;
}

sub check_gid {
	my $gid = $_[0];

	$pu = Passwd::Unix->new('warnings' => 1);
	foreach my $get_group ($pu->groups()) {
		my ($get_gid,undef, undef) = $pu->group($get_group);
		return 1 if ($get_gid == $gid);
	}
	
	return 0;
}


