#!/usr/bin/perl

###########################################################################
#
# kpautotyper - KeePassX autotyper
#
# Author: Moritz Meintker <moritz ät thinksilicon dot de>
#
# This tool hooks up to a named pipe and acts based on the strings received
# The usual way is to combine this tool with a program such as actkbd
# which will write into the named pipe whenever a certain button is pressed.
#
# To set up the named pipe just use `mkfifo /var/run/keypad.pipe`. You can
# write to the pipe with e.g. `echo 1 > /var/run/keypad.pipe`
#
# When using an external USB-Numpad configure actkbd to use just that device:
# `actkbd -d /dev/input/by-id/usb-VENDOR_Keyboard_Hub-event-kbd`
#
# To disable any keytypes of that device in X try to figure out which one it
# is and then disable it with xinput:
# `xinput list` - note down ID(s)
#
# A numpad usually has two IDs, one for Num-Lock ON and one for OFF.
# You have to disable both:
# `xinput set-prop <ID> "Device Enabled" 0`
#
# This tool will only type the password after you press the "PW-key" at the
# same time. It's for safety meassures so it won't type any password just
# because you spilled your coffee on that numpad. You can choose any key as
# "PW-key". Right now it's the left Windows-Key (SUPER_L).
#
# Configuration of the strings on the named pipe vs. the password-entry in
# KeePassX is done in key_to_entry()-subroutine.
# Please note:
# You need a unique title for every entry in your passdb. I didn't figure
# out how to address the specific IDs of the entries. If you know please
# send me a mail!
#
# This tool requires File::KeePass, File::KeePass::Agent and for autotyping
# File::KeePass::Agent::unix. The unix-package is a rough draft of where
# this module is heading to. So it might be that future versions of this
# module are not compatible with this tool anymore. As of now I'm using
# version 2.01
#
###########################################################################

use strict;
use warnings;

use Fcntl;

use File::KeePass;
use File::KeePass::Agent;
use File::KeePass::Agent::unix;

our $VERSION = '0.1';

# Check if we have zenity installed ... no there's no fallback if not... shame on me, I'm lazy
my $tool_name = "zenity";
my $tool_path = '';
for my $path ( split /:/, $ENV{PATH} ) {
	if ( -f "$path/$tool_name" && -x _ ) {
		$tool_path = "$path/$tool_name";
		last;
	}
}
die "No $tool_name command available\n" unless ( $tool_path );

my $file = $ARGV[0];
chomp( $file ) if defined( $file );
if( defined( $file ) && -e $file ) {
	my $pwd = `zenity --password`;
	chomp( $pwd );

	my $kp = File::KeePass->new;
	$kp->load_db( $file, $pwd ) or die( "Wrong password!\n" );

	my $kpa = File::KeePass::Agent->new;
	$kp->unlock;

	my $key_pressed = "";
	do {
		sysopen( FIFO, "/var/run/keypad.pipe", O_RDONLY ) or die( "Unable to read pipe: $!\n" );
		$key_pressed = <FIFO>;
		close( FIFO );
		if( defined( $key_pressed ) ) {
			chomp( $key_pressed );
			last if( $key_pressed eq "q" );

			if( $key_pressed eq "lock" ) {
				$kp->lock;
			}
			
			if( $key_pressed eq "unlock" ) {
				$kp->unlock;
			}

			print "Key: $key_pressed\n";
			my $entry = $kp->find_entry( key_to_entry( $key_pressed ) );

			if( $entry ) {
				if( $kp->is_locked ) {
					warn "Password DB is locked! Unlock first!\n";
				} else {
					if( $kpa->is_key_pressed( 'Super_L' ) ) { # Windows-Taste
					print "Typing password for ".$entry->{'title'}."\n";

					$kpa->send_key_press( $entry->{'password'}."\n\0", $entry, "", "" );
					} else {
						warn "Please press PW-key!";
					}
				}
			} else {
				warn "No entry found!\n";
			}
		}
	} while( $key_pressed ne "q" );

	$kp->lock;

	exit( 0 );
} else {
	die( "Could not load passdb!\n" );
}

sub key_to_entry {
	my $key = shift;

	my $lookup = {};

	# Not dependant on Num-Lock
	$lookup->{"enter"} = { 'title' => 'LDAP' };
	$lookup->{"slash"} = { 'title' => 'Windows' };
	$lookup->{"star"} = { 'title' => 'XYZ' };
	$lookup->{"back"} = { 'title' => 'Blah' };

	$lookup->{"plus"} = { 'title' => '' };
	$lookup->{"5"} = { 'title' => '' };

	# Num-Lock ON
	$lookup->{"7"} = { 'title' => '' };
	$lookup->{"8"} = { 'title' => '' };
	$lookup->{"9"} = { 'title' => '' };
	$lookup->{"minus"} = { 'title' => '' };

	$lookup->{"4"} = { 'title' => '' };
	$lookup->{"6"} = { 'title' => '' };

	$lookup->{"1"} = { 'title' => '' };
	$lookup->{"2"} = { 'title' => '' };
	$lookup->{"3"} = { 'title' => '' };

	$lookup->{"0"} = { 'title' => '' };
	$lookup->{"dot"} = { 'title' => '' };

	# Num-Lock OFF
	$lookup->{"home"} = { 'title' => '' };
	$lookup->{"up"} = { 'title' => '' };
	$lookup->{"pgup"} = { 'title' => '' };
	$lookup->{"-"} = { 'title' => '' };

	$lookup->{"left"} = { 'title' => '' };
	$lookup->{"right"} = { 'title' => '' };

	$lookup->{"end"} = { 'title' => '' };
	$lookup->{"down"} = { 'title' => '' };
	$lookup->{"pgdown"} = { 'title' => '' };

	$lookup->{"ins"} = { 'title' => '' };
	$lookup->{"del"} = { 'title' => '' };


	if( defined( $lookup->{ $key } ) ) {
		return $lookup->{ $key };
	} else {
		return( { 'title' => '' } );
	}
}

=head1 NAME

kpautotyper - KeePassX autotyper (per entry auto-type hotkey)

=head1 DESCRIPTION

This tool is aimed to provide an interface to type a password for every defined hotkey.
A numpad comes handy for this purpose as you will have about 25 possibilities to fire
a password type with a single numpad.

=head1 USAGE

This tool expects the filename of the passdb as its first and only parameter.
kpautotyper.pl <path/to/passdb.kpx>

=head1 PREREQUISITES

This program requires these non-core modules:

C<File::KeePass>               - Interface to KeePass V1 and V2 database files
C<File::KeePass::Agent>        - Application agent for working with File::KeePass files
C<File::KeePass::Agent::unix>  - platform specific utilities for Agent

You will furthermore need the programm `zenity` on your host system to enter the master
password for your passdb.

=head1 RUNNING kpautotyper

=head2 1. Create a named pipe that is writable only by root
	sudo mkfifo /var/run/numpad.pipe

=head2 2. Configure actkbd to listen on your numpad
	actkbd -d /dev/input/by-id/usb-VENDOR_Keyboard_Hub-event-kbd -c /etc/actkbd.conf
You can use actkbd -s to see which keycodes are available. For a numpad there will
be different codes for NumLock ON and OFF

Example configuration for actkbd.conf:
	98:::echo "slash" > /var/run/keypad.pipe
	55:::echo "star" > /var/run/keypad.pipe
	14:::echo "back" > /var/run/keypad.pipe
	69+71:::echo "7" > /var/run/keypad.pipe
	69+72:::echo "8" > /var/run/keypad.pipe
	69+73:::echo "9" > /var/run/keypad.pipe
	69+74:::echo "minus" > /var/run/keypad.pipe
	69+75:::echo "4" > /var/run/keypad.pipe
	69+76:::echo "5" > /var/run/keypad.pipe
	69+77:::echo "6" > /var/run/keypad.pipe
	78:::echo "plus" > /var/run/keypad.pipe
	69+79:::echo "1" > /var/run/keypad.pipe
	69+80:::echo "2" > /var/run/keypad.pipe
	69+81:::echo "3" > /var/run/keypad.pipe
	69+82:::echo "0" > /var/run/keypad.pipe
	
	69+83:::echo "dot" > /var/run/keypad.pipe
	28:::echo "enter" > /var/run/keypad.pipe
	
	# Without numlock:
	102:::echo "home" > /var/run/keypad.pipe
	103:::echo "up" > /var/run/keypad.pipe
	104:::echo "pgup" > /var/run/keypad.pipe
	74:::echo "-" > /var/run/keypad.pipe
	
	105:::echo "left" > /var/run/keypad.pipe
	106:::echo "right" > /var/run/keypad.pipe
	
	107:::echo "end" > /var/run/keypad.pipe
	108:::echo "down" > /var/run/keypad.pipe
	109:::echo "pgdown" > /var/run/keypad.pipe
	
	110:::echo "ins" > /var/run/keypad.pipe
	111:::echo "del" > /var/run/keypad.pipe


=head2 3. Configure X so it will ignore any keypress on your numpad
	xinput list
will give you a list of input devices. Search for your device and note down the IDs
A numpad usually has two IDs, one for NumLock ON one for OFF

	xinput set-prop <ID> "Device Enabled" 0
Disable both IDs

=head2 5. Fire up kpautotyper.pl
	kpautotyper.pl <path/to/passdb.kpx>
It will ask for your master-password and unlock the database. You can however lock
and unlock it with sending "lock" or "unlock" into the pipe. If the DB is locked
no autotypes will be performed.

You can quit the tool by pressing Ctrl+C or sending "q" to the pipe.

=head1 AUTHOR

Moritz Meintker <moritz ät thinksilicon dot de>

=head1 LICENSE

This program may be distributed under the same terms as Perl itself.

=head1 CREDITS

Special thanks to Paul Seamons, author of C<File::KeePass> and C<File::KeePass::Agent>
