#!/usr/bin/perl -w
##############################################################################
# This script searches LDAP for the members of a specified group and makes
# sure that they have local accounts using their information from LDAP.
# The group is made locally as well.
#
# This is used on our servers to provide accounts for our sysadmins without
# making the servers full-scale LDAP clients, which makes them vulnerable to
# LDAP server failures.
##############################################################################

use IPC::Open3;
use File::Copy;

#
# Constants
#

# The members of this group are the ones we operate on
my $GROUP = 'sysadm';
# This string is prepended to the user's GECOS field in the password
# file to provide a way for us to identify which accounts we added
my $GECOSFLAG = 'SYSADM ';

#
# Get the members of the group from LDAP
#

# Use open3 so that stderr goes away
my $pid = open3(\*CMDIN, \*CMDOUT, \*CMDERR, 'ldapsearch', '-x', "cn=$GROUP");
die "Fork failed: $!" if (! $pid);
close(CMDIN);
close(CMDERR);

my @admins;
my $group_gid;
while(<CMDOUT>)
{
	if (/^memberUid: (.+)/)
	{
		my $admin = $1;
		push(@admins, $admin);
	}
	elsif (/^gidNumber: (\d+)/)
	{
		$group_gid = $1;
	}
}
close(CMDOUT);

waitpid($pid, 0);
if ($? != 0)
{
	# If ldapsearch exited with an error, we probably have bad
	# information and don't want to continue.
	die;
}

#print "admins are " . join(', ', @admins) . "\n";

#
# Lookup the user's account information in LDAP for each of the members
# of the specified group
#

my @accts;
foreach my $admin (@admins)
{
	my $pid = open3(\*CMDIN, \*CMDOUT, \*CMDERR,
		'ldapsearch', '-x', "uid=$admin");
	die "Fork failed: $!" if (! $pid);
	close(CMDIN);
	close(CMDERR);

	my ($uid, $gid, $name, $homedir, $shell);
	while(<CMDOUT>)
	{
		if (/^uidNumber: (\d+)/)
		{
			$uid = $1;
		}
		elsif (/^gidNumber: (\d+)/)
		{
			$gid = $1;
		}
		elsif (/^cn: (.+)/)
		{
			$name = $1;
		}
		elsif (/^homeDirectory: (.+)/)
		{
			$homedir = $1;
		}
		elsif (/^loginShell: (.+)/)
		{
			$shell = $1;
		}
	}
	close(CMDOUT);

	waitpid($pid, 0);
	if ($? != 0)
	{
		# If ldapsearch exited with an error, we probably have bad
		# information and don't want to continue.
		die;
	}

	if ($uid && $gid && $name && $homedir && $shell)
	{
		push(@accts,
			"$admin:x:$uid:$gid:$GECOSFLAG$name:$homedir:$shell");
	}
}

#
# Starting with the current /etc/passwd, remove any old entries that
# we added, then add the new entries based on the information we retrieved.
# This is used to build a passwd.sysadm working file.
#

open(PASSWD, '< /etc/passwd') || die;
open(NEWPASSWD, '> /etc/passwd.sysadm') || die;
while(<PASSWD>)
{
	my (undef, undef, undef, undef, $gecos, undef, undef) = split(/:/);
	if ($gecos !~ /^$GECOSFLAG/)
	{
		print NEWPASSWD $_;
	}
}
close(PASSWD);

foreach my $acct (@accts)
{
	print NEWPASSWD "$acct\n";
}

close(NEWPASSWD);

# Now some sanity checks to make sure we've written out a valid file
if (! -s '/etc/passwd.sysadm')
{
	die "/etc/passwd.sysadm is empty";
}

#
# Now make a backup copy of the current passwd file and move our working
# copy into place.
#

copy('/etc/passwd', '/etc/passwd.bak');
move('/etc/passwd.sysadm', '/etc/passwd');

#
# Starting with the current /etc/group, remove any old entries that
# we added, then add the new entry based on the information we retrieved.
# This is used to build a group.sysadm working file.
#

open(GROUP, '< /etc/group') || die;
open(NEWGROUP, '> /etc/group.sysadm') || die;
while(<GROUP>)
{
	my ($group, undef, undef, undef) = split(/:/);
	if ($group ne $GROUP)
	{
		print NEWGROUP $_;
	}
}
close(GROUP);

if ($group_gid)
{
	my $mems = join(',', @admins);
	print NEWGROUP "$GROUP:x:$group_gid:$mems\n";
}

close(NEWGROUP);

# Now some sanity checks to make sure we've written out a valid file
if (! -s '/etc/group.sysadm')
{
	die "/etc/group.sysadm is empty";
}

#
# Now make a backup copy of the current group file and move our working
# copy into place.
#

copy('/etc/group', '/etc/group.bak');
move('/etc/group.sysadm', '/etc/group');

