#!/usr/local/bin/perl
#
#Script to purge "expired users"
#
#Gets list of all users for the cluster (from PDB)
#and checks for:
#1) users not attached to any projects 
#2) users without a Glue account (i.e. not in passwd file)
#3) users in passwd but with ng of 'Restricted'
#
#Depending on options, will list, email, and or delete/revoke HPC
#access.

use strict;
use warnings;

use lib "/cell_root/software/HPCC/lib/site_perl";

use Glue::HPCC::Cluster;

use Glue::HPCC::Project::Project;
#use Glue::HPCC::Project::ProjectType;
use Glue::HPCC::Project::Person;
use Glue::HPCC::Project::PersonProject;
#use Glue::HPCC::Project::LastEmail;

use Glue::HPCC::ClusterUtil;

use Glue::HPCC::Utils::MailUtils qw(send_template_email);
use Glue::HPCC::Utils::UserAccounts qw(user_exists);
use Glue::HPCC::Utils::NetGroup qw(user_in_netgroup);

use Getopt::Long;
use Time::ParseDate;
use POSIX qw(strftime);
use Carp qw(carp croak);
use Cwd;

my $DEBUG;
my $VERBOSE;
my $RESTRICTED_NETGROUP = 'Restricted';
my $EXPIRATION;

sub usage()
{	
	print STDERR <<EOF;
$0: Cron job search for user accounts that need purging

Usage:
$0 [ --debug ] [ --verbose ] [ --dryrun ] [ -[no]mail ] \
	[ --cluster CLUSTER ] \
	[ --noprojs-mode NOPROJMODE ] \
	[ --noglue-mode NOGLUEMODE ] \
	[ --restricted-mode RESTRICTMODE ] \
	[ --expiration EXPIRATION ] \
	[ --exclude USERS2EXCLUDE ]

This will examine all users with access to the specified cluster (according
to the PDB), and note users falling into the following categories:
1) NOPROJECTS: user is not attached to any projects in the cluster
2) NOGLUE: user is not present in password file
3) RESTRICTED: user is in the 'Restricted' netgroup

Users found in any of the above states will be processed accordingly, based
on the NOPROJMODE, NOGLUEMODE, and RESTRICTMODE flags, respectively.  These
modes can be set to one of:
	PRINT: prints status of user to stdout
	EMAIL: emails system staff (NOPROJECTS or NOGLUE), or the user and
		the PoCs of any projects the user belongs to (RESTRICTED)
	DELETE: revokes HPC access from the user (NOPROJECTS or NOGLUE mode only)
	IGNORE: does nothing (not deleted, no email)
The default for all four is 'PRINT'.

CLUSTER will default appropriately based on hostname of system being run on.

If EXPIRATION given, it will be used as the expiration for archived directories, if
any.  Anything Time::Parsedate accepts is valid, including relative.  Default is
to use default from delusers (6 months from now).

If the --exclude option is given, it should be given one or more usernames (comma
delimited).  It can be given multiple times as well, results are additive.  The
users in this list are excluded from processing.

In --dryrun mode, no emails or deletions are actually done, but
the commands/emails that would have been sent are sent to STDERR.
-nomail is an alias for --dryrun.

EOF
}

#-------------------------------------------------------------------
#		Assorted routines
#-------------------------------------------------------------------

sub get_subdir_owners($)
#Given a directory $rdir, returns a hash ref keyed on uid with
#value being subdirectories immediately below $rdir owned by
#that uid.
{	my $rdir = shift;
	my $ohash = {};
	my $me = "get_subdir_owners";
	my $RDIR;

	unless ( -e $rdir )
	{	warn "$me: Rootdir $rdir does not exist, returning empty hash\n";
		return $ohash;
	}
	unless ( -d $rdir )
	{	warn "$me: Rootdir $rdir not a directory, returning empty hash\n";
		return $ohash;
	}

	opendir $RDIR, $rdir || die "$me: Error opening dir $rdir: $! at ";
	my @sdirs = readdir($RDIR);
	closedir $RDIR;

	@sdirs = grep { $_ ne '.' && $_ ne '..' } @sdirs;
	@sdirs = map { "$rdir/$_" } @sdirs;

	foreach my $sdir (@sdirs)
	{	my @stat = stat $sdir;
		my $uid = $stat[4];
		unless ( defined $uid )
		{	die "$me: Error stat-ing file/dir $sdir, $!, at ";
		}

		my $dlist = $ohash->{$uid};
		unless ( $dlist && ref($dlist) eq 'ARRAY' )
		{	$dlist = $ohash->{$uid} = [];
		}
		push @$dlist, $sdir;
	}

	return $ohash;
}
	
my $homedir_owners;

sub get_homedir_owners($)
#Sets $homedir_owners to hash refs keyed on uid
#of users owning homedirectories.  Value is list ref of directories owned.
{	my $hpc = shift;
	my $me = "get_homedir_owners";

	my $hroot_user = $hpc->homeroot_user;
	my $hroot_admin = $hpc->homeroot_admin;
	$hroot_admin = undef if $hroot_user eq $hroot_admin;

	$homedir_owners = {};
	foreach my $rdir ($hroot_user, $hroot_admin)
	{	next unless $rdir;
		unless ( -d $rdir )
		{	die "$me: Homedir root $rdir doesnt exist/not directory at ";
		}
		my $tmphash = get_subdir_owners($rdir);

		foreach my $key (keys %$tmphash)
		{	my $val = $tmphash->{$key};
			my $old = $homedir_owners->{$key};
			if ( $old && ref($old) eq 'ARRAY' )
			{	push @$old, @$val;
			} else
			{	$homedir_owners->{$key} = $val;
			}
		}
	}
}

sub user_has_homedir($$)
{	my $hpc = shift;
	my $uid = shift;

	unless ( $homedir_owners && ref($homedir_owners) eq 'ARRAY' )
	{	get_homedir_owners($hpc);
	}

	my $val = $homedir_owners->{$uid};
	return 1 if $val;
	return 0;
}

my $datadir_owners;

sub get_datadir_owners($)
#Sets $datadir_owners to hash refs keyed on uid
#of users owning data directories (NFS/lustre/etc).  Value is list ref of directories owned.
{	my $hpc = shift;
	my $me = "get_datadir_owners";

	my @datadirs = ();
	my $tmp = $hpc->lustre_filesystems;
	push @datadirs, @$tmp if $tmp && ref($tmp) eq 'ARRAY';
	$tmp = $hpc->nfs_data_filesystems;
	push @datadirs, @$tmp if $tmp && ref($tmp) eq 'ARRAY';

	foreach my $rdir (@datadirs)
	{	next unless $rdir;
		unless ( -d $rdir )
		{	die "$me: Homedir root $rdir doesnt exist/not directory at ";
		}
		my $tmphash = get_subdir_owners($rdir);

		foreach my $key (keys %$tmphash)
		{	my $val = $tmphash->{$key};
			my $old = $datadir_owners->{$key};
			if ( $old && ref($old) eq 'ARRAY' )
			{	push @$old, @$val;
			} else
			{	$datadir_owners->{$key} = $val;
			}
		}
	}
}

sub user_has_datadir($$)
{	my $hpc = shift;
	my $uid = shift;

	unless ( $datadir_owners && ref($datadir_owners) eq 'ARRAY' )
	{	get_datadir_owners($hpc);
	}

	my $val = $datadir_owners->{$uid};
	return 1 if $val;
	return 0;
}

sub user_in_this_cluster($$)
#Boolean.  Returns true if specified user really seems to be part
#of this cluster, and false otherwise.
#
#Algorithm is:
#Return true if any of the following holds:
#	user belongs to any projects in the cluster
#	user belongs to cluster netgroup (requires user to still have glue account)
#	user has home directory on cluster
#	user has data (NFS/lustre/etc) directory on cluster
#Return false if none of the above is true.
{	my $hpc = shift;
	my $uname = shift;
	my $me = 'user_in_this_cluster';

	my $user = Glue::HPCC::Project::Person->person_by_username($hpc, $uname);
	unless ( $user )
	{	die "$me: No PDB record for user $uname, huh? at ";
	}
	unless ( $user && ref($user) )
	{	die "$me: Error looking up PDB record for user $uname: $user at ";
	}
	my $uid = $user->uid;

	
	my $projects = Glue::HPCC::Project::PersonProject->get_projects_user_is_attached_to(
                        $hpc, $uname) || [];
	return 1 if @$projects;

	my $netgroup = $hpc->netgroup;
	if ( user_exists($uname) )
	{	return 1 if user_in_netgroup($uname, $netgroup);
	}

	return 1 if ( user_has_homedir($hpc, $uid) );
	return 1 if ( user_has_datadir($hpc, $uid) );

	return 0;
}

	

	
#-------------------------------------------------------------------
#		Routines to delete users
#-------------------------------------------------------------------

sub _noshell_backticks($@)
#Calls an external command using pipes and forks so no shell gets invoked
#Returns ($err, @out) where $err is the error is the exit status of
#the command, and @out is the list of output returned, line by line.
#If $mode is 0, only STDOUT is returned in @out, 
#If $mode is non-zero, STDERR is dupped onto STDOUT and also returned.
{       my $mode = shift;
        my @cmd = @_;

        my ($err, @out, $PIPE, $res);
        my $chd_excode=254;

        if ( $res = open($PIPE, "-|" )  )
        {       #Parent
                if ( ! defined $res )
                {       my $tmp = join ' ', @cmd;
                        die "Pipe to '$tmp' failed: $!";
                }
                @out = <$PIPE>;
                $res = close $PIPE;
                $err = $?;
                if ( $err && ( ($err >> 8) == $chd_excode ) )
                {       #We (probably?) got an exception running exec in
                        #child process (e.g. taint issues).  Re-raise it
                        my $exc = join '', @out;
                        die $exc;
                }
                return ($err, @out);
        } else
        {       #Child
                #Duplicate stderr onto stdout if so requested
                if ( $mode )
                {       unless ( open(STDERR, '>&STDOUT') )
                        {       print "Cannot dup stderr to stdout in child";
                                die "Cannot dup stderr to stdout in child";
                        }
                }
                #Wrap exec in an eval, and exit on error, not die.
                #Otherwise, if _noshell_backticks_ is put in an eval block,
                #an exception raised by exec (e.g. taint issues) will result in
                #BOTH child and parent continuing from the user's eval block
                #See e.g. http://www.perlmonks.org/?node=166538
                eval { exec { $cmd[0] } @cmd; #Not subject to shell escapes
                };
                exit $chd_excode;
        }
}

sub delete_user($$)
#Takes hpc instance and username, and deletes the user.
#Actually, this is handled by the deluser script, so this just calls that.
#
#Returns false on success, true (error message) on non-fatal error
{	my $hpc = shift;
	my $uname = shift;
	my $me = "delete_user";

	my $dryrun = undef;
	$dryrun = '--dryrun' if $hpc->dryrun;

	my $delusers = "/cell_root/software/HPCC/sbin/delusers";
	my @deluser_options = 
	(	'--nonglue',
		'--logextra', "by cron_purge_expired_users",
		'--force',
		'--allprojects',
		'--user', $uname,
	);
	unshift @deluser_options, $dryrun if $dryrun;
	push @deluser_options, "--expiration=$EXPIRATION" if $EXPIRATION;

	print "[INFO] Deleting user $uname ", ($dryrun?'(dryrun)':''), "\n";
	my $perlinterp = "$^X";
	my @cmd = ( $perlinterp, $delusers, @deluser_options );
	
	my $mode = 1; #Include STDOUT and STDERR in @out
	my ($err, @out) =  _noshell_backticks($mode, @cmd);

	if ( $err || $DEBUG )
	{	if ( $err )
		{	print STDERR "ERROR in delusers\n";
		} else
		{	print STDERR "[DEBUG] delusers output\n";
		}
		my $txt = join " ", @cmd;
		print STDERR "cmd='$txt'\n";
		print STDERR "Output:\n", (join '', @out), "\n--------------\n";
	}
	die "$me: Error running delusers, returned $err at " if $err;

	return;
}
		
#-------------------------------------------------------------------
#		Routines to process results as requested
#-------------------------------------------------------------------

sub print_noglue_accounts($$)
#Given list ref of noglue accounts, print them
#Returns false on success, true (error message) on non-fatal errors
{	my $hpc = shift;
	my $noglue = shift;
	my $me = 'print_noglue_accounts';
	die "$me: Missing required parameter noglue at " unless $noglue;
	die "$me: Invalid value '$noglue' for noglue, expecting list ref, at "
		unless $noglue && ref($noglue) eq 'ARRAY';

	unless ( @$noglue )
	{	print "No users found w/out valid glue accounts.\n";
		return;
	}
	print "Users without valid Glue accounts\n";

	my @sorted = sort { $a->username cmp $b->username } @$noglue;
        foreach my $user (@sorted)
        {       my $uname = $user->username;
                my $uid = $user->uid;
                my $projects = Glue::HPCC::Project::PersonProject->get_projects_user_is_attached_to(
                        $hpc, $uname) || [];
                my $prjstr = join ", ", @$projects;
                print "\t$uname ($uid) [ $prjstr ]\n";
        }
	print "\n\n";
	return;
} 

sub email_noglue_accounts($$)
#Given list ref of noglue accounts, send email to sysadmins
#Returns false on success, true (error message) on non-fatal errors
{	my $hpc = shift;
	my $noglue = shift;
	my $me = 'email_noglue_accounts';
	die "$me: Missing required parameter noglue at " unless $noglue;
	die "$me: Invalid value '$noglue' for noglue, expecting list ref, at "
		unless $noglue && ref($noglue) eq 'ARRAY';

	return unless @$noglue; #Don't email if no such users

	my @sorted = sort { $a->username cmp $b->username } @$noglue;
	my $ulist = [];
        foreach my $user (@sorted)
	{	my $uname = $user->username;
		my $uid = $user->uid;
                my $projects = Glue::HPCC::Project::PersonProject->get_projects_user_is_attached_to(
                        $hpc, $uname) || [];
		my $urec = { username=>$uname, uid=>$uid, projects=>$projects };
		push @$ulist, $urec;
	}

	my $cluster = $hpc->db_clustername;
	my $template = "${cluster}_sysadmin_noglue_users.tmplate";
	my $macros = {	Cluster => $cluster, Ulist => $ulist };
	my $res = send_template_email($template, $macros);
	return "$me: Error from send_template_email: $res" if $res;
	return;
}

sub delete_noglue_accounts($$)
#Given list ref of noglue accounts, delete them. 
#Returns false on success, true (error message) on non-fatal errors
{	my $hpc = shift;
	my $noglue = shift;
	my $me = 'delete_noglue_accounts';
	die "$me: Missing required parameter noglue at " unless $noglue;
	die "$me: Invalid value '$noglue' for noglue, expecting list ref, at "
		unless $noglue && ref($noglue) eq 'ARRAY';

	return unless @$noglue; #Nothing to delete
        foreach my $user (@$noglue)
	{	my $uname = $user->username;
		delete_user($hpc, $uname);
	}
	return;
}


sub print_noproj_accounts($$)
#Given list ref of noproj accounts, print them
#Returns false on success, true (error message) on non-fatal errors
{	my $hpc = shift;
	my $noproj = shift;
	my $me = 'print_noproj_accounts';
	die "$me: Missing required parameter noproj at " unless $noproj;
	die "$me: Invalid value '$noproj' for noproj, expecting list ref, at "
		unless $noproj && ref($noproj) eq 'ARRAY';

	unless ( @$noproj )
	{	print "No users found w/out projects.\n";
		return;
	}
	print "Users without projects\n";

	my @sorted = sort { $a->username cmp $b->username } @$noproj;
        foreach my $user (@sorted)
        {       my $uname = $user->username;
                my $uid = $user->uid;
                my $projects = Glue::HPCC::Project::PersonProject->get_projects_user_is_attached_to(
                        $hpc, $uname) || [];
                my $prjstr = join ", ", @$projects;
                print "\t$uname ($uid) [ $prjstr ]\n";
        }
	print "\n\n";
	return;
} 

sub email_noproj_accounts($$)
#Given list ref of noproj accounts, send email to sysadmins
#Returns false on success, true (error message) on non-fatal errors
{	my $hpc = shift;
	my $noproj = shift;
	my $me = 'email_noproj_accounts';
	die "$me: Missing required parameter noproj at " unless $noproj;
	die "$me: Invalid value '$noproj' for noproj, expecting list ref, at "
		unless $noproj && ref($noproj) eq 'ARRAY';

	return unless @$noproj; #Don't email if no such users

	my @sorted = sort { $a->username cmp $b->username } @$noproj;
	my $ulist = [];
        foreach my $user (@sorted)
	{	my $uname = $user->username;
		my $uid = $user->uid;
                my $projects = Glue::HPCC::Project::PersonProject->get_projects_user_is_attached_to(
                        $hpc, $uname) || [];
		my $urec = { username=>$uname, uid=>$uid, projects=>$projects };
		push @$ulist, $urec;
	}

	my $cluster = $hpc->db_clustername;
	my $template = "${cluster}_sysadmin_noproj_users.tmplate";
	my $macros = {	Cluster => $cluster, Ulist => $ulist };
	my $res = send_template_email($template, $macros);
	return "$me: Error from send_template_email: $res" if $res;
	return;
}

sub delete_noproj_accounts($$)
#Given list ref of noproj accounts, delete them. 
#Returns false on success, true (error message) on non-fatal errors
{	my $hpc = shift;
	my $noproj = shift;
	my $me = 'delete_noproj_accounts';
	die "$me: Missing required parameter noproj at " unless $noproj;
	die "$me: Invalid value '$noproj' for noproj, expecting list ref, at "
		unless $noproj && ref($noproj) eq 'ARRAY';

	return unless @$noproj; #Nothing to delete
        foreach my $user (@$noproj)
	{	my $uname = $user->username;
		delete_user($hpc, $uname);
	}
	return;
}


sub print_restricted_accounts($$)
#Given list ref of restricted accounts, print them
#Returns false on success, true (error message) on non-fatal errors
{	my $hpc = shift;
	my $restricted = shift;
	my $me = 'print_restricted_accounts';
	die "$me: Missing required parameter restricted at " unless $restricted;
	die "$me: Invalid value '$restricted' for restricted, expecting list ref, at "
		unless $restricted && ref($restricted) eq 'ARRAY';

	unless ( @$restricted )
	{	print "No users found in ${RESTRICTED_NETGROUP} netgroup.\n";
		return;
	}
	print "Users in ${RESTRICTED_NETGROUP}:\n";

	my @sorted = sort { $a->username cmp $b->username } @$restricted;
        foreach my $user (@sorted)
        {       my $uname = $user->username;
                my $uid = $user->uid;
                my $projects = Glue::HPCC::Project::PersonProject->get_projects_user_is_attached_to(
                        $hpc, $uname) || [];
                my $prjstr = join ", ", @$projects;
                print "\t$uname ($uid) [ $prjstr ]\n";
        }
	print "\n\n";
	return;
} 

sub email_restricted_accounts($$)
#Given list ref of restricted accounts, send email to sysadmins
#Returns false on success, true (error message) on non-fatal errors
{	my $hpc = shift;
	my $restricted = shift;
	my $me = 'email_restricted_accounts';
	die "$me: Missing required parameter restricted at " unless $restricted;
	die "$me: Invalid value '$restricted' for restricted, expecting list ref, at "
		unless $restricted && ref($restricted) eq 'ARRAY';

	return unless @$restricted; #Don't email if no such users

	my $cluster = $hpc->db_clustername;
	my $template = "${cluster}_user_warn_restricted.tmplate";

        foreach my $user (@$restricted)
	{	my $uname = $user->username;
		my $umail = $user->email;
		my $fname = $user->fullname;
		
                my $projects = Glue::HPCC::Project::PersonProject->get_projects_user_is_attached_to(
                        $hpc, $uname) || [];
		
		my $pocemails = [];
		foreach my $pname (@$projects )
		{	my $tmpemails = Glue::HPCC::Project::PersonProject->get_poc_emails_for_project(
				$hpc, $pname);
			push @$pocemails, @$tmpemails;
		}
		#Remove duplicates
		my %temp = map { $_ => undef } @$pocemails;
		$pocemails = [ keys %temp ];

		my $macros = { 
			Cluster => $cluster,
			UserEmail => $umail,
			Username => $uname, 
			UserFullname => $fname,
			PocEmails => $pocemails,
		};
		my $res = send_template_email($template, $macros);
		die "$me: Error sending template for restricted user $uname: $res at "
			if $res;
	}
	return;
}

#-------------------------------------------------------------------
#		Main
#-------------------------------------------------------------------

my $help;
my $dryrun;
my $cluster;
my $now;
#Defaults
my $noprojmode = 'PRINT';
my $nogluemode = 'PRINT';
my $restrictmode = 'PRINT';
my $exclude_users = [];

my $res = GetOptions(
	'help!' => \$help,
	'debug!' => \$DEBUG,
	'mail|email!' => sub { my $n = shift; my $v = shift;
		#--mail is basically inverse of --dryrun
		$dryrun = ($v)?0:1;
		},
	'dryrun!' => \$dryrun,
	'verbose!' => \$VERBOSE,
	'cluster=s' => \$cluster,
	'noprojs-mode|noprojects-mode|noprojs_mode|noprojects_mode|noprojsmode|noprojectsmode|noprojects=s' => \$noprojmode,
	'noglue-mode|noglue_mode|nogluemode|noglue=s' => \$nogluemode,
	'restricted-mode|restricted_mode|restrictedmode|restricted=s' => \$restrictmode,
	'expiration|expiry=s' => \$EXPIRATION,
	'exclude=s' => $exclude_users,
);


unless ( $res ) 
{	usage;
	die "\nError processing options.\n";
}

if ( $help )
{	usage;
	exit 0;
}

if ( scalar(@ARGV) )
{       my $tmp = join ", ", @ARGV;
        usage;
        die "\nError: extraneous arguments [ $tmp ], aborting\n";
}

#Validate mode values

#Noprojects mode
$noprojmode = uc $noprojmode if $noprojmode;
unless ( $noprojmode eq 'EMAIL' || $noprojmode eq 'DELETE' ||
	$noprojmode eq 'IGNORE' || $noprojmode eq 'PRINT' )
{	usage;
	die "\nInvalid noprojects-mode '$noprojmode', expecting one of "
		. "EMAIL, DELETE, IGNORE, or PRINT.\n";
}

#Noglue mode
$nogluemode = uc $nogluemode if $nogluemode;
unless ( $nogluemode eq 'EMAIL' || $nogluemode eq 'DELETE' ||
	$nogluemode eq 'IGNORE' || $nogluemode eq 'PRINT' )
{	usage;
	die "\nInvalid noglue-mode '$nogluemode', expecting one of "
		. "EMAIL, DELETE, 'PRINT', or IGNORE.\n";
}

#restricted mode
$restrictmode = uc $restrictmode if $restrictmode;
unless ( $restrictmode eq 'EMAIL' || $restrictmode eq 'PRINT' ||
	$restrictmode eq 'IGNORE' )
{	usage;
	die "\nInvalid restricted-mode '$restrictmode', expecting one of "
		. "'EMAIL', 'PRINT',  or 'IGNORE'.\n";
}

#Default cluster if needed
my $hpc = Glue::HPCC::Cluster->new_from_clustername($cluster);
die "\nInvalid cluster $cluster at " if $cluster && ! $hpc && ! ref($hpc);
die "\nError instantiating cluster $hpc " unless $hpc && ref($hpc);
$cluster = $hpc->db_clustername;

$hpc->dryrun($dryrun);
#Glue::HPCC::Utils::MailUtils::verbose($VERBOSE);
Glue::HPCC::Utils::MailUtils::dryrun($dryrun);
#Glue::HPCC::ClusterUtil->verbose($VERBOSE);
Glue::HPCC::ClusterUtil->dryrun($dryrun);

#Figure out what users to exclude
my %excluded_users = ();
foreach my $tmpusers (@$exclude_users)
{	my @tmp = split /\s*,\s*/, $tmpusers;
	foreach my $tmpuser (@tmp)
	{	$excluded_users{$tmpuser} = undef;
	}
}

#Store results
my @noprojects = ();
my @noglue = ();
my @restricted = ();

my $netgroup = $hpc->netgroup;

my $allusers = Glue::HPCC::Project::Person->pdb_query($hpc);

USER: foreach my $user (@$allusers)
{	my $uname = $user->username;
	if ( exists $excluded_users{$uname} )
	{	print STDERR "[VERBOSE] Skipping excluded user $uname.\n"
			if $VERBOSE;
		next USER;
	}

	unless ( $user->active )
	{	print STDERR "[DEBUG] Skipping inactive user $uname.\n"
			if $DEBUG;
		next USER;
	}

	unless ( user_in_this_cluster($hpc, $uname) )
	{	print STDERR "[VERBOSE] Skipping user $uname, does not look like in this cluster.\n"
			if $VERBOSE;
		next USER;
	}

	#Check that user is in password file
	unless ( user_exists($uname) )
	{ 	push @noglue, $user;
		print STDERR "[DEBUG] user $uname not in password file\n" if $DEBUG;
		next USER;
	}

	#Check that user has projects
	my $projects = Glue::HPCC::Project::PersonProject->get_projects_user_is_attached_to(
		$hpc, $uname);
	unless ( $projects && ref($projects) eq 'ARRAY' )
	{	warn "ERROR: unable to lookup projects for $uname: $projects at ";
		next USER;
	}

	unless ( @$projects )
	{	#No projects for user
		#Ignore this if not in netgroup for cluster (e.g. DT1 only users
		#might appear w/out projects on DT2, etc, cause share some parts of PDB)
		unless ( user_in_netgroup($uname, $netgroup) )
		{	print STDERR "[DEBUG] user $uname: no projs, but not in $netgroup\n"
				if $DEBUG;
			next USER;
		}
		push @noprojects, $user;
		print STDERR "[DEBUG] user $uname has no projects\n" if $DEBUG;
		next USER;
	}

	#Check if user is RESTRICTED
	if (user_in_netgroup($uname, $RESTRICTED_NETGROUP) )
	{	push @restricted, $user;
		print STDERR "[DEBUG] user $uname in 'Restricted'\n" if $DEBUG;
		next USER;
	}
}

if ( $nogluemode eq 'PRINT' )
{	print_noglue_accounts($hpc,\@noglue);
} elsif ( $nogluemode eq 'EMAIL' )
{	email_noglue_accounts($hpc,\@noglue);
} elsif ( $nogluemode eq 'DELETE' )
{	delete_noglue_accounts($hpc,\@noglue);
} else {
	die "Illegal value '$nogluemode' for nogluemode at "
		unless $nogluemode eq 'IGNORE';
}

if ( $noprojmode eq 'PRINT' )
{	print_noproj_accounts($hpc,\@noprojects);
} elsif ( $noprojmode eq 'EMAIL' )
{	email_noproj_accounts($hpc,\@noprojects);
} elsif ( $noprojmode eq 'DELETE' )
{	delete_noproj_accounts($hpc,\@noprojects);
} else {
	die "Illegal value '$noprojmode' for noprojmode at "
		unless $noprojmode eq 'IGNORE';
}

if ( $restrictmode eq 'PRINT' )
{	print_restricted_accounts($hpc,\@restricted);
} elsif ( $restrictmode eq 'EMAIL' )
{	email_restricted_accounts($hpc,\@restricted);
} else {
	die "Illegal value '$restrictmode' for restrictmode at "
		unless $restrictmode eq 'IGNORE';
}

