#!/usr/bin/perl 
#!/usr/local/bin/perl 
use Getopt::Std;
use Sys::Hostname;
use lib ".";
use cmconfig;

###################################################################
#
# cmcheck.pl - Checks the /etc/password file on the lead cluster
# machine for consistency. Prints any inconsistencies on stderr, and
# for each course supported on the cluster, prints a report on stdout
# summarizing the the status of each student account.
#
# Configuration constants are defined in cmconfig.pm
#
# Use the cmupdate.pl script to update the password file.
###################################################################

$| = 1;      # Autoflush output on every print statement
umask(0077); # Any files created by script are readable only by user

#
# usage - print help message and terminate
#
sub usage {
    printf STDERR "$_[0]\n";
    printf STDERR "Usage: $0 [-haq]\n";
    printf STDERR "Options:\n";
    printf STDERR "  -h  Print this message.\n";
    printf STDERR "  -a  Print student account summary on stdout.\n";
    printf STDERR "  -q  Quiet, don't print any warning messages\n";
    die "\n";
}

#
# aterm - abnormal termination
#
sub aterm {
  my $msg = shift;     # message to print

  system("rm -rf $tmpfile"); 
  die "$msg\n";
}

#
# nterm - Normal termination
#
sub nterm {
  my $msg = shift;     # message to print

  system("rm -rf $tmpfile"); 
  print "$msg";
  exit;
}

##############
# Main routine
##############

# Parse the command line arguments
getopts('haq');
if ($opt_h) {
    usage("This is the cluster machine password file checker.");
}

# Build a hash of the excluded user names
$EXCLUDE = ();
foreach $username (@EXCLUDE_LIST) {
    $EXCLUDE{$username} = $username;
}

# Build a hash of the courses using this cluster
$COURSE = ();
foreach $coursenum (@COURSE_LIST) {
    $COURSE{$coursenum} = $coursenum;
}

# 
# Get the password file
# (from current host if $LEAD_HOST is not specified)
#
$tmpfile = "/tmp/cmcheck_passwd$$";
$thishost = hostname();
if ($LEAD_HOST eq "" or $thishost eq $LEAD_HOST) {
    print STDERR "Checking $CMCHECK_PASSWD_FILE on $thishost\n";
    system("cp $CMCHECK_PASSWD_FILE $tmpfile") == 0
	or aterm "$0: ERROR: Could not copy local file $CMCHECK_PASSWD_FILE.";
}
else {
    print STDERR "Checking $CMCHECK_PASSWD_FILE on $LEAD_HOST\n";
    system("scp -q $LEAD_HOST:$CMCHECK_PASSWD_FILE $tmpfile") == 0
	or aterm "$0: ERROR: Could not get $CMCHECK_PASSWD_FILE from host $LEAD_HOST.";
}

    

# 
# First, scan the password file for consistency
# 
$linenum = 0;
$in_student_section = 0;

%USER = ();    # init the hash of users keyed by user IDs

open(INFILE, $tmpfile)
    or aterm "$0: ERROR: could not open passw file ($tmpfile) for reading.";

while ($line = <INFILE>) {
    chomp($line);
    $linenum++;
	
    ($username, $x, $userid, $groupid, $fullname, $homedir, $shell) =  
	split(':', $line); 
    

    # Check for duplicate user IDs
    if ($USER{$userid}) {
        warn "Warning: Duplicate user IDs ($userid) for $username and $USER{$userid}\n";
    }
    else {
	$USER{$userid} = $username;
    }

    # Skip past all the staff acounts
    if ($username =~ /^$LAST_STAFF/) {
	$in_student_section = 1;
	next;
    } 
	
    # Check a student entry 
    if ($in_student_section) {

        # Is this a blank line?
	if (!$username or $username eq "" or $username =~ /^\s+$/) {
	    $opt_q or 
		warn "Warning: Blank line in line $linenum of $CMCHECK_PASSWD_FILE file\n"; 
	    next;
	}

	# Does this user have an Andrew account?
	if (! -e "$PATH_PREFIX/$username") {
	    $opt_q or 
		warn "Warning: Couldn't find user ($username) in Andrew\n";
	    next;
	}

	# Are students in this course allowed to use the machines?
	$homedir =~ /^.+\/(\d\d-\d\d\d)$/;
	$coursenum = $1;
	if (!$COURSE{$coursenum}) {
	    $opt_q or
		warn "Warning: Bad course number ($coursenum) in $line\n";
	}

	# Is this a valid home directory field?
	if (!($homedir =~ /^$PATH_PREFIX\/$username\/$coursenum/)) {
	    $opt_q or
		warn "Warning: Bad directory entry ($homedir) in $line\n"; 
	    next;
	}
    }
}
close(INFILE);

# Make sure we encountered the last staff entry we expected
if (!$in_student_section) {
    aterm "ERROR: Didn't find expected last staff entry($LAST_STAFF).";
}


# Exit if we don't want a report on the user accounts
nterm() unless $opt_a;


# 
# Print a report for each course represented in the password file
#
foreach $coursenum (sort keys %COURSE) {
    # Initialize things for this iteration of the main loop
    %GOODHOMEDIR = (); # Those with valid accounts in /etc/passwd
    %NOHOMEDIR = ();   # Those without readable home directories
    %BADHOMEDIR = ();  # Those lacking readable .klogin and .login files.
    
    $num_goodhomedir = 0; # Number of students with good home directories
    $num_nohomedir = 0;   # Number of students with no home directories
    $num_badhomedir = 0;  # Number of students with bad home directories
    
    $linenum = 0;
    $in_student_section = 0;
    
    open(INFILE, $tmpfile)
	or aterm "$0: ERROR: could not open passwd file ($tmpfile).";

    while ($line = <INFILE>) {
	chomp($line);
	$linenum++;
    
	($username, $x, $userid, $groupid, $fullname, $homedir, $shell) =  
	    split(':', $line); 
    
	# Are we past the staff accounts
	if ($username =~ /^$LAST_STAFF/) {
	    $in_student_section = 1;
	    next;
	} 
	
	# Ignore anyone in the excluded list
	if ($EXCLUDE{$username}) {
	    next;
	}
	
	# Check out a student entry
	if ($in_student_section) {

	    # Skip this entry if it's not the course we're interested in
	    if (!$homedir =~ /$coursenum/) {
		next;
	    }

	    # Check whether the student has properly run the checkin script
	    # and has their permissions correctly set
	    if (!-e "$homedir" or !-d "$homedir") {
		$num_nohomedir++;
		$NOHOMEDIR{$username} = $username;
	    }
	    elsif (!-e "$homedir/.klogin"  or !-e "$homedir/.login") {
		$num_badhomedir++;
		$BADHOMEDIR{$username} = $username;
	    }
	    else {
		$num_goodhomedir++;
		$GOODHOMEDIR{$username} = $username;
	    }
	}
    }
    close(INFILE);

    print "*********************************************************\n";
    print "Status of $coursenum student accounts on ", $LEAD_HOST, "\n";
    print "Last updated by cmcheck.pl on ", scalar localtime, "\n";
    print "*********************************************************\n";
    
    print STDOUT <<"EOF";

I. The following $num_nohomedir students have an account, but you cannot login 
yet because you do not seem to have a ~/$coursenum subdirectory in your Andrew 
home directory.  There are two likely reasons for this: (1) You haven't run the 
'checkin' script yet, or (2) Your home directory is unreadable.  In either case, 
please see the course Web page for instructions on how to fix it.

EOF

    foreach $username (sort keys %NOHOMEDIR) {
        print "$username\n";
    }

    print STDOUT <<"EOF";

II. The following $num_badhomedir students have an account and a ~/$coursenum 
subdirectory, but you cannot login yet because ~/$coursenum does not  contain 
readable .klogin and .login files. There are two likely reasons for this: (1) You 
created ~/$coursenum manually, instead of letting the 'checkin' script do it, or 
(2) Your ~/$coursenum directory is not readable.  In either case, see the class 
Web page for directions on how to fix it.

EOF

    foreach $username (sort keys %BADHOMEDIR) {
	print "$username\n";
    }

    print "\n";
    print "III. The remaining $num_goodhomedir students have valid accounts:\n";
    print "\n";
    foreach $username (sort keys %GOODHOMEDIR) {
        print "$username\n";
    }

}

# clean up and exit
nterm();
