#!/usr/bin/perl -T -w
use strict;
use warnings;
use Digest::HMAC_SHA1 qw/ hmac_sha1_hex /;

## Set up usernames to secrets. Minimum length, 16 chars.
my %users = (
	"username"        => "ABCDEFGHIJKLMNOP",
);

## Catch Ctrl+C and Ctrl+Z...
$SIG{'INT'} = sub { fail(); };
$SIG{'TSTP'} = sub { fail(); };

sub decodeBase32 {
	my ($val) = @_;
	# turn into binary characters
	$val =~ tr|A-Z2-7|\0-\37|;
	# unpack into binary
	$val = unpack('B*', $val);
	# cut off the 000 prefix
	$val =~ s/000(.....)/$1/g;
	# trim off some characters if not 8 character aligned
	my $len = length($val);
	$val = substr($val, 0, $len & ~7) if $len & 7;
	# pack back up
	$val = pack('B*', $val);
	return $val;
}
sub totp_token {
	my $secret = shift;
	my $key = unpack("H*", decodeBase32($secret));
	my $lpad_time = sprintf("%016x", int(time()/30));
	my $hmac = hmac_sha1_hex_string($lpad_time, $key);
	my $offset = sprintf("%d", hex(substr($hmac, -1)));
	my $part1 = 0 + sprintf("%d", hex(substr($hmac, $offset*2, 8)));
	my $part2 = 0 + sprintf("%d", hex("7fffffff"));
	my $token = substr("".($part1 & $part2), -6);
	return $token;
}
sub hmac_sha1_hex_string {
	my ($data, $key) = map pack('H*', $_), @_;
	hmac_sha1_hex($data, $key);
}
sub fail {
	kill( -1, 0 );
	exit 1;
}

my $username = $ENV{"LOGNAME"};
if ( !defined $users{$username} ) {
	print "No token configured. Bye.\n";
	fail();
}

print "Enter Code: ";
my $input_code = <STDIN>;
chomp($input_code);

my $calculated_code = totp_token($users{$username});
if ( $calculated_code ne $input_code ) {
	fail();
}

## Auth passed.
exit 0;
