LegacyTechops/doorman/doorman.pl
2024-04-21 14:46:15 -05:00

243 lines
6.1 KiB
Perl

#!/usr/bin/perl -W
###############################################################################
#
# doorman.pl - Use inexpensive USB RFID reader to allow access to a physical
# portal controlled by an inexpensive USB relay, via a background
# HTTP portal for authentication.
#
#################################################################[ AJC 2018 ]##
use strict;
use Linux::Input;
use Data::Dumper;
use WWW::Mechanize;
use File::Basename qw{basename};
use IO::Select;
###############################################################################
#
# Configuration directoves
#
###############################################################################
# Direct /dev link to device. Use by-id when possible
my $device = "/dev/input/by-id/usb-13ba_Barcode_Reader-event-kbd";
my $usbrelay = "/usr/bin/usbrelay";
my $usbrelay_device = "3X9XI_1";
# The URL of the authentication server
my $url = "http://doors.pfv.turnsys.net/";
# Location of syslog's userspace logger utility
my $rsyslog = "/usr/bin/logger";
# Card IDs that bypass network auth
my %backdoors = (
"0009399422" => 'Charles NW',
"0009106067" => 'Josef C',
);
# For logging syslog messages to stdout as well as syslog.
my $debug = "true";
#my $debug = "false";
###############################################################################
#
# Subroutines
#
###############################################################################
#################################################
# logger - Send logs to syslog, or stdout if
# DEBUG is defined.
#################################################
sub logger {
my $logtext = shift;
if ($debug eq "true") { print localtime() . " " . basename($0) . ": " . "$logtext\n" };
system( $rsyslog . " \"" . basename($0) . ": " . "$logtext\"" );
}
#################################################
# unlock_door - Handle the USB relay to unlock
# actuator.
#################################################
sub unlock_door() {
system $usbrelay . " " . $usbrelay_device . "=1 >/dev/null 2>&1";
sleep 10;
system $usbrelay . " " . $usbrelay_device . "=0 >/dev/null 2>&1";
logger "Locking door.";
return 0;
}
#################################################
# authenticate - check backdoors and legit HTTP
# source to see if ID is valid.
# Unlocks door if appropriate.
#################################################
sub authenticate {
my $id = shift;
if ( defined $backdoors{$id} ) {
logger $backdoors{$id} . " override key accepted. Unlocking door.";
unlock_door;
} else {
# Open a connection to auth server.
my $connection = WWW::Mechanize->new( autocheck => 0, quiet => 1);
$connection->get ($url . $id);
# Touch a file named as the card ID number to make the card work.
if ($connection->status == 200) {
logger "Card $id is valid. Unlocking door.";
unlock_door;
} else {
# File is not cleanly returned, so it's invalid.
logger "Card $id is invalid! (" . $connection->status() . ")";
}
}
}
#################################################
# keytranslate - Reduce the returned keypress
# value, and translate into the
# actual number pressed.
#################################################
sub keytranslate {
my $keypress = shift;
return 1 if ($keypress == 30);
return 2 if ($keypress == 31);
return 3 if ($keypress == 32);
return 4 if ($keypress == 33);
return 5 if ($keypress == 34);
return 6 if ($keypress == 35);
return 7 if ($keypress == 36);
return 8 if ($keypress == 37);
return 9 if ($keypress == 38);
return 0 if ($keypress == 39);
return $keypress;
}
###############################################################################
#
# main - This is where the whole thing comes together.
#
###############################################################################
# scoped values for loop work
my $oldvalue = 0;
my $dupe_flag = 0;
my $id = "";
my $done_flag = 0;
# Open all USB input devices, and stash in a hash until needed.
my @devicenames = map { "/dev/input/by-id/" . basename($_) } </dev/input/by-id/*>;
my %devices;
my $selector = IO::Select->new();
foreach (@devicenames) {
my $device = Linux::Input->new($_);
$selector->add($device->fh);
$devices{$device->fh} = $device;
}
logger basename($0) . " started.";
system $usbrelay . " " . $usbrelay_device . "=0 >/dev/null 2>&1";
# stay in an infinite loop.
while ( 1 ) {
# Do this if we have a device with some input
while (my @fh = $selector->can_read) {
# For each file handle that has stuff to read
foreach my $fh (@fh) {
my $input = $devices{$fh};
# Read the input that's waiting
while (my @events = $input->poll(0.01)) {
# Convert keystrokes to useful data
foreach (@events) {
# break up the input struct into useful scalars.
my $code = %$_{code};
my $value = %$_{value};
my $type = %$_{type};
my $number = keytranslate($value & 127);
# This seems to indicate a KEYP/KEYDOWN event
if ( $code == 4 && $type == 4 ) {
# If we just saw this, it's KEYUP now, so ignore
if ($value == $oldvalue && $dupe_flag == 0) {
$dupe_flag = 1;
next
} else {
$dupe_flag = 0;
}
# 0-9 for number, 40 for end of string.
if ( $number > 9 && $number != 40) { logger "Problem reading ID\n"; next}
if ( $number == 40) {
$done_flag = 1;
} else {
$id .= $number;
}
$oldvalue = $value;
} # if ($code == 4 && $type == 4)
} # foreach (@events)
} # while (my @events = $input->poll(0.01))
} # foreach my $fh (@fh)
# Do something with the completed code.
if ( $done_flag == 1) {
logger "ID $id scanned.";
authenticate($id);
$id = "";
$done_flag = 0;
} else {
next
}
} # while (my @fh = $selector->can_read)
} # while (1)