#!/usr/bin/perl -w
#
# MSCHAP - OpenRADIUS module for authenticating MSCHAP requests
#   and generating MPPE Encryption keys
#
#   Usage: mschap [-d]
#   	   mschap -h
#
# -d increases verbosity on stderr and allows module to run standalone
#
# This module accepts 4 str parameters and will return a success or fail
# int value based on whether the password matches or not. The module also returns
# the master mppe send and receive keys formatted properly into a 32 byte value
# these keys must then be encrypted similar to the pap encryption scheme and send back
# to the client.  An example of this is shown in the included behavior file
#
# The four parameters passed to the module are as follows.
#
#    MS-CHAP-Challenge
#    MSRESP2-Peer-Challenge
#    User-Name
#    MSRESP2-Response
#
# The module must also run in binary send mode.  This may be rewritten but it was
# easier to extract the challenge strings from a binary stream than the ascii stream
#
#
# Author: David Madsen davidm@et.byu.edu
#
# History:
#   2-26-04 - DM - Initial file creation
#
#
#

use Digest::MD4 qw(md4 md4_hex md4_base64);
use Getopt::Long;
use Crypt::DES;
use Digest::SHA1;
use DBI;
use Crypt::Blowfish_PP;

#the following lines are Customized settings for password retrieval from inhouse database
$key = "somethingorother";
$chapuser = "raduser";
$chappasswd = "rt243507sdlkj";
$dbstring = "DBI:mysql:database=duser;hostname=mysql.exmple.com";
$blowfish=new Crypt::Blowfish_PP($key);


Getopt::Long::Configure("bundling");
GetOptions("h"  => \$usage,
           "d+" => \$debug,
           "n"  => \$noint,
           "c"  => \$check);

$| = 1;   # Important - no buffering

if ($usage) {
    die("Usage: mschap [-d]\n");
}

# Check that we're running under OpenRADIUS, interface version 1

unless ($debug ||
        $ENV{'RADIUSINTERFACEVERSION'} &&
        $ENV{'RADIUSINTERFACEVERSION'} == 1) {
        die "radsql: ERROR: not running under OpenRADIUS, interface v1!\n";
}


while( read(STDIN,$str,4) ) {


	$return_str = "";
	$return_length = 0;

	read(STDIN,$buf,4);
	$length = unpack("N", $buf);

	$authenticatorchallenge = read_next_token();	
	$peerchallenge = read_next_token();
	$username = read_next_token();	
	$response = read_next_token();

	$db=DBI->connect($dbstring,$chapuser,$chappasswd) or &downtime($!);
	$query = "select caedmhash from users where name=\"$username\" and disabled != 1";
	$cmd = $db->prepare($query);
	$cmd->execute();

	($caedmhash) = $cmd->fetchrow_array();
	$cmd->finish();
	$db->disconnect();

	$counter = length($caedmhash);

	$unpass = "";
        while($counter > 0) {
              $ciphertextBlock=pack("H16",$caedmhash);
              $plaintext=$blowfish->decrypt($ciphertextBlock);
              $unpass = $unpass . $plaintext;
              $counter = $counter - 16;
              @caedmhash = split //,$caedmhash;
              splice(@caedmhash,0,16);
              $caedmhash = join("",@caedmhash);
	}

        @newlist=();
        @charlist = split //,$unpass;
        @charlist2 = split //,$unpass;
        foreach $char (@charlist2) {
        	if ($char =~ /[[:word:][:punct:]]/) {
        		push @newlist,splice(@charlist,0,1);
        	} else {
        		splice(@charlist,0,1);
        	}
        }
        $unpass = join("",@newlist);

	$pass=$unpass;

	$unipass = $pass;
	$unipass =~ s/(.)/$1\0/g;
	$md4pass = md4($unipass);

	if ($debug) {
		print STDERR "username: $username\n";
		print STDERR "pass: $pass\n";
		print STDERR "blowfish: $caedmhash\n";
       		print STDERR "UNI: ". unpack("H*", $unipass)."\n";
       		print STDERR "MD4: ". unpack("H*", $md4pass)."\n";
	}


	$md4pack = $md4pass . pack("x5");


	$sha1 = Digest::SHA1->new;
	$sha1->add($peerchallenge);
	$sha1->add($authenticatorchallenge);
	$sha1->add($username);
	$challenge = $sha1->digest;
	$challenge = substr($challenge, 0, 8);

	if ($debug) {
	     	print STDERR "challenge: ".unpack("H*", $challenge)."\n";
	}		

	$des1 =  substr($md4pack, 0 , 7);
	$des2 =  substr($md4pack, 7 , 7);
	$des3 =  substr($md4pack, 14, 7);

	if ($debug) {
		print STDERR "des1-raw: ".unpack("H*", $des1)."\n";
		print STDERR "des1: ".unpack("H*", str_to_key($des1))."\n";
		print STDERR "des2-raw: ".unpack("H*", $des2)."\n";
		print STDERR "des2: ".unpack("H*", str_to_key($des2))."\n";
		print STDERR "des3-raw: ".unpack("H*", $des3)."\n";
		print STDERR "des3: ".unpack("H*", str_to_key($des3))."\n";
	}

	$cipher1 = new Crypt::DES str_to_key($des1);
	$cipher2 = new Crypt::DES str_to_key($des2);
	$cipher3 = new Crypt::DES str_to_key($des3);
	$ciphertext1 = $cipher1->encrypt($challenge);
	$ciphertext2 = $cipher2->encrypt($challenge);
	$ciphertext3 = $cipher3->encrypt($challenge);

	$challenge_response = $ciphertext1.$ciphertext2.$ciphertext3;
				
	$magic1 = pack("H*", "4D616769632073657276657220746F20636C69656E74207369676E696E6720636F6E7374616E74");
      	$magic2 = pack("H*", "50616420746F206D616B6520697420646F206D6F7265207468616E206F6E6520697465726174696F6E");

	$md4passhash = md4($md4pass);
	$sha2 = Digest::SHA1->new;

	$sha2->add($md4passhash);
	$sha2->add($challenge_response);
	$sha2->add($magic1);
	$digest = $sha2->digest;

	$sha2 = Digest::SHA1->new;
	$sha2->add($digest);
	$sha2->add($challenge);
	$sha2->add($magic2);
	$digest2 = $sha2->digest;

	$authenticator_response = "S=" . uc(unpack("H*", $digest2)) . "  ";
	$response_len = length($authenticator_response);
				
	$key_magic1 = pack("H*", "5468697320697320746865204D505045204D6173746572204B6579");
	$key_magic2 = pack("H*", "4F6E2074686520636C69656E7420736964652C2074686973206973207468652073656E64206B65793B206F6E207468652073657276657220736964652C206974206973207468652072656365697665206B65792E");
	$key_magic3 = pack("H*", "4F6E2074686520636C69656E7420736964652C2074686973206973207468652072656365697665206B65793B206F6E207468652073657276657220736964652C206974206973207468652073656E64206B65792E");
	$sha_pad1 = pack("H*", "00000000000000000000000000000000000000000000000000000000000000000000000000000000");
	$sha_pad2 = pack("H*", "F2F2F2F2F2F2F2F2F2F2F2F2F2F2F2F2F2F2F2F2F2F2F2F2F2F2F2F2F2F2F2F2F2F2F2F2F2F2F2F2");


	$mppekeypasshash = md4(substr($md4pass,0,16));
	$sha2 = Digest::SHA1->new;
	$sha2->add($mppekeypasshash);
	$sha2->add($challenge_response);
	$sha2->add($key_magic1);
	$masterkey = $sha2->digest;
	$masterkey = substr($masterkey,0,16);

	if ($debug) {
		print STDERR "mppe_pass_hash: ".unpack("H*", $mppekeypasshash)."\n";
		print STDERR "masterkey: ".unpack("H*", $masterkey)."\n";
	}

	$sha2 = Digest::SHA1->new;
	$sha2->add($masterkey);
	$sha2->add($sha_pad1);
	$sha2->add($key_magic3);
	$sha2->add($sha_pad2);
	$mastersendkey = $sha2->digest;
	$mastersendkey = substr($mastersendkey,0,16);

	$sha2 = Digest::SHA1->new;
	$sha2->add($masterkey);
	$sha2->add($sha_pad1);
	$sha2->add($key_magic2);
	$sha2->add($sha_pad2);
	$masterreceivekey = $sha2->digest;
	$masterreceivekey = substr($masterreceivekey,0,16);

	if ($debug) {
		print STDERR "masterreceivekey: ".unpack("H*", $masterreceivekey)."\n";
		print STDERR "mastersendkey: ".unpack("H*", $mastersendkey)."\n";
	}

	$plaintextsend = pack("H*", "10"). $mastersendkey . pack("x15");
	$plaintextreceive = pack("H*", "10"). $masterreceivekey . pack("x15");

	if ($debug) {
		print STDERR unpack("H*", $challenge_response)."\n";
	       	print STDERR $authenticator_response;
	}

	add_token(100, 0, 4, $response_len, $authenticator_response);
	
	if ($response eq $challenge_response) {
		$success =  pack("N", 1);
	} else {
		$success =  pack("N", 0);
	}

	add_token(100, 0, 1, 4, $success);

	add_token(100, 0, 4, 32, $plaintextsend);
	add_token(100, 0, 4, 32, $plaintextreceive);

	send_token();


}

sub send_token {

	$return_length += 4*2;
	$return_str =  pack("N", $return_length) . $return_str;
	$return_str = pack("N", 0xdeadbeef) . $return_str;
	print $return_str;
	if ($debug) {
		print STDERR unpack("H*", $return_str);
	}

}

sub add_token {

	my $space = shift;
	my $vendor = shift;
	my $attribute = shift;
	my $length = shift;
	my $value = shift;

        $return_str .= pack("N", $space);
        $return_str .= pack("N", $vendor);
        $return_str .= pack("N", $attribute);
        $return_str .= pack("N", $length);
        $return_str .= $value;
	
	$return_length += 4*4+$length;
	
}

sub read_next_token {
 
	my $ret_val;
	my $buf;
	my $length;

	read(STDIN,$buf,4);
	read(STDIN,$buf,4);
	read(STDIN,$buf,4);
	read(STDIN,$buf,4);
	$length = unpack("N", $buf);

	if ($debug) {
		print STDERR $length."\n";
	}

	read(STDIN,$ret_val,$length);
	if ($debug) {
        	print STDERR unpack("H*", $ret_val)."\n";
	}
	if ($length%4) {
		read(STDIN,$buf,4-($length%4));
	}
	return $ret_val;
}

sub str_to_key {

	$str = shift;
	$unpack_str = unpack("B*", $str);
	$pack_str = $unpack_str;

	$pack_str =~ s/(.......)/$1)1/g;
	$pack_str =~ s/\)//g;
	$ret_str = pack("B*", $pack_str);
	return $ret_str;
}
