#!/usr/bin/perl

#---------------------------------------------------------------------
# this is quite a hack.  You may find it useful to set your tabstops
# to 4.
#
# This script is designed to handle authentication requeusts from
# Checkpoint's Firewall-1 firewall.  I've only experienced the
# behaviour in one environment, so I don't know what other
# scenarios there are...
#
# in any case, this script waits for TCP connections on port 261,
# and when on comes, it pops up a dialog window where the 6-digit
# code from a SecurID card can be typed in.
#
# The PIN that belongs to the SecurID card can be entered when
# the script is first started.  This is, of course, less secure.
#
# you'll need perlTk for this...
#
# Rob Urban
# <urban@brickbat.de>
#---------------------------------------------------------------------

use FileHandle;
use Tk;
use Socket;
#use IO::Poll;
#use Fcntl;


BEGIN {
	$DEBUG		= 0;
	$USER		= 'firewall-user-name';		# FIX THIS
	$XUSER		= 'local-user-name';		# FIX THIS
	$PIN		= '';
	$LOGFILE	= '/var/log/fw-auth.log';
}

openLog();

# get login dir of XUSER
$login_dir = (getpwnam($XUSER))[7];
$DEBUG && print "login dir = [$login_dir]\n";

# set XAUTHORITY var
$ENV{XAUTHORITY} = "$login_dir/.Xauthority";

getPIN();

#my $PORT = 3333;
$PORT = 261;
my $PROTO = getprotobyname('tcp');

socket(SERVER, PF_INET, SOCK_STREAM, $PROTO)	|| die "socket: $!";
setsockopt(SERVER, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) || die "setsockopt: $!";
bind(SERVER, sockaddr_in($PORT, INADDR_ANY))	|| die "bind: $!";
listen(SERVER, SOMAXCONN)						|| die "listen: $!";

logmsg("listener started on ".localtime);

$SIG{CHLD} = \&REAPER;

createDialog();
$TOP->after(50, [\&waitForConnection]);
MainLoop;
closeLog();
exit;

#========================================================================
# subs
#========================================================================

sub waitForConnection
{
	$DEBUG && print "- entering waitForConnection() -\n";
	my $paddr = accept(CLIENT, SERVER);
	$DEBUG && print "- after accept -\n";
	my($port,$iaddr) = sockaddr_in($paddr);
	#my $name = gethostbyaddr($iaddr,AF_INET);
	logmsg("connection from ".inet_ntoa($iaddr));
	#CLIENT->autoflush(1);
	select(CLIENT); $| = 1; select(STDOUT);
	handleConnection();
	$DEBUG && print "- leaving waitForConnection() -\n";
}

sub createDialog
{
	$TOP = MainWindow->new;
	$TOP->withdraw;
	$DEBUG && print "after MainWindow...\n";
	$TOP->wm(title => 'FW-1 Authentication');

	#----------------------------------------------------
	# label
	#----------------------------------------------------
	my $lab = $TOP->Label(-text => "Authenticate user $USER");
	$lab->pack(-side => 'top');

	#----------------------------------------------------
	# frame for entry + abort-button
	#----------------------------------------------------
	my $f = $TOP->Frame->pack(-side => 'top');

	#----------------------------------------------------
	# entry
	#----------------------------------------------------
	my $code_entry = $f->Entry(
		-width			=> 12,
		-textvariable	=> \$CODE,
		-show			=> '*',
	);
	$code_entry->focus;
	$code_entry->bind('<Return>', sub {
			if ($SEND_CODE) {
				$DEBUG && print "sending code from binding.\n";
				my $nwrite = syswrite(CLIENT, "$PIN$CODE\n");
				$DEBUG && print "Wrote [$nwrite] bytes.\n";
				$SEND_CODE = 0;
			} else {
				$DEBUG && print "send_code not set.\n";
			}
			$HAVE_CODE = 1;
		}
	);
	$code_entry->pack(-side => 'left');

	#----------------------------------------------------
	# abort-button
	#----------------------------------------------------
	my $abort = $f->Button(
		-text => 'Abort',
		-command => [ \&closeConnection ],
	);
	$abort->pack(-side => 'right');

	#----------------------------------------------------
	# message
	#----------------------------------------------------
	my $msg = $TOP->Message(
		-text		=> '',
		-foreground => 'red',
		-relief		=> 'flat',
		-width		=> '6c',
	);
	$msg->pack(-side => 'top');
	sub message {
		my $m = shift;
		$msg->configure(-text => $m);
		$msg->after(3000, sub { $msg->configure(-text => ''); });
	}
	$TOP->update;
}

sub closeConnection
{
	$TOP->fileevent(CLIENT, 'readable', '');	# delete
	logmsg("closing connection\n");
	close(CLIENT);
	$SEND_CODE = 0;
	$HAVE_CODE = 0;
	$CODE = '';
	$TOP->after(50, [\&waitForConnection]);
	$DEBUG && print "- withdraw -\n";
	$TOP->withdraw;
}

sub handleConnection
{
	$DEBUG && print "in handleConnection()\n";
	my $code;

	# bring up dialog box
	$DEBUG && print "- deiconify -\n";
	$TOP->deiconify;
	$TOP->raise;

	$DEBUG && print "before fileevent...\n";

	$TOP->fileevent(CLIENT, 'readable', sub {
		$DEBUG && print "- fileevent enter -\n";
		my $nread = sysread(CLIENT, $_, 8192);
		if ($nread == 0) {
			$DEBUG && print "EOF\n";
			closeConnection();
			return;
		}
		#$DEBUG && dumpLine($_);
		s/[\n\r]*$//g;
		my @lines = split(/\n/, $_);
		$DEBUG && print "LINES:\n\t".join("\n\t", @lines)."\n";
		foreach (@lines) {
			logmsg("SERVER: $_");
			$DEBUG && print "LOOP: [$_]\n";
			if (/^331 User:/) {
				$DEBUG && print "- matched 'user:' -\n";
				my $nwrite = syswrite(CLIENT, "$USER\n");
				$DEBUG && print "Wrote [$nwrite] bytes.\n";
				$DEBUG && print "sent user-string\n";
				logmsg('sent user-id');
			} elsif (/^331 \*PASSCODE:/) {
				$DEBUG && print "- matched 'code:', waiting for enter -\n";
				if ($HAVE_CODE) {
					$DEBUG && print "sending code from event-loop.\n";
					$nwrite = syswrite(CLIENT, "$PIN$code\n");
					$DEBUG && print "Wrote [$nwrite] bytes.\n";
					logmsg('sent passcode');
					$HAVE_CODE = 0;
				} else {
					$SEND_CODE = 1;
				}
			} elsif (/^331 \*next PASSCODE:/) {
				$code = '';
				message('Enter Next CODE...');
				$SEND_CODE = 1;
			} elsif (/^200 (.*)$/) {
				$LAST_RESPONSE = $1;
			} elsif (/^530 NOTOK/) {
				$DEBUG && print "got NOTOK.\n";
				$code = '';
				message('Authentication failed. Retry.');
				logmsg("authentication failure: $LAST_RESPONSE");
				$HAVE_CODE = 0;
			} elsif (/^230 OK/m) {
				$DEBUG && print "got OK.\n";
				logmsg('authentication OK');
			}
			$DEBUG && print "- fileevent leave -\n";
		}
	});

	$DEBUG && print "leaving handleConnection\n";
}

sub openLog
{
	$LOG_FH = FileHandle->new(">>$LOGFILE");
	if (!defined($LOG_FH)) { die "open $LOGFILE for append"; }
	$LOG_FH->autoflush(1);
}

sub logmsg
{
	my $msg = shift;

	print $LOG_FH "$msg\n";
}

sub closeLog
{
	$LOG_FH->close;
}

sub dumpLine
{
	my $line = shift;

	foreach my $chr (split(//, $line)) {
		print '<'.ord($chr).'> ';
	}
	print "\n";
}

sub getPIN
{
	my $pin;

	my $mw = MainWindow->new;
	$mw->wm(title => 'Enter PIN');
	my $lab = $mw->Label(-text => "enter PIN for user $USER");
	$lab->pack(-side => 'top');
	my $pin_entry = $mw->Entry(
		-width			=> 8,
		-textvariable	=> \$PIN,
		-show			=> '*',
	);
	$pin_entry->focus;
	$pin_entry->pack(-side => 'top');

	$pin_entry->bind('<Return>', sub {
			$DEBUG && print "PIN entered.\n";
			$mw->destroy;
		}
	);

	MainLoop;
}
