Using ModPerl to Keep Zencart and phpBB2 Hackers Away

I’ve seen a bunch of attempts to hack at my zencart admin area and an old phpBB2 install that is no longer there. Both of these don’t work, and just return a 404 error, but it’s kind of annoying to keep spending compute cycles on this sort of behavior. Besides, now that I have installed the statsd and graphite packages, I am set up to provide a graph of whatever I want. So why not track the hackers, lock them out, and provide a graph of activity at the same time? So here goes.

2016-12-28 edit: see my github repo at https://github.com/dminear/apache_hack_check

I also run mod_perl in the Apache2 server, so I wanted to hook in to the PerlAccessHandler and do the work. The following is the Apache2 config lines:

        PerlModule ModPerl::DanHandler
        <Location />
                #SetHandler perl-script
                PerlAccessHandler ModPerl::DanHandler
        </Location>

And the following is the Perl module that is in the /usr/local/lib/site_perl/ModPerl directory on my Ubuntu machine:

package ModPerl::DanHandler;
use strict;
use warnings;
use FileHandle;
use IO::Socket::INET;

use Apache2::Log;
use Apache2::RequestRec ();
use Apache2::Connection ();

use Apache2::Const -compile => qw(FORBIDDEN OK :log);

BEGIN {
mkdir "/tmp/bad_ips";
chmod 0777, "/tmp/bad_ips"
}

my $sock = IO::Socket::INET->new(PeerPort => 8125,
				PeerAddr => '127.0.0.1',
				Proto => 'udp');

sub handler {
	my $r = shift;

	my $str = $r->connection->remote_ip();

	# if there is an attempt to access "zencart/admin", then put the ip on the block list
	if ($r->unparsed_uri() =~ /zencart\/admin$/ ||
		$r->unparsed_uri() =~ /zencart\/+admin\/+/ ||
		$r->unparsed_uri() =~ /phpbb2/i ) {
		#$r->log_error("BAD IP: $str");
		$sock->send( "hacker.unparsed_uri." . $r->unparsed_uri() . ":1|c\n" ) if defined $sock;
		my $fh = FileHandle->new( "> /tmp/bad_ips/$str");
		if (defined $fh) {
			print $fh "dummy";
			$fh->close;
		}
	}

	# check the block list
	if (-e "/tmp/bad_ips/$str") {
		$sock->send( "request.blocked:1|c\n" ) if defined $sock;
		return Apache2::Const::FORBIDDEN;
	} else {
		$sock->send( "request.allowed:1|c\n" ) if defined $sock;
		$sock->send( "request.hostname." . $r->hostname() . ":1|c\n" ) if defined $sock;
		return Apache2::Const::OK;
	}
}
1;

So now when someone comes in and tries to access /zencart/admin (or some gyrations thereof), the IP address gets stored in the tmp directory as a filename. On every request, the remote IP address is checked, and if found returns a 403 Forbidden response. The nice thing is that this happens for any request thereafter.  Because it’s early in the request stage, there’s not too much overhead. Plus I get the satisfaction of watching the banned IP addresses grow.

Then there’s some logic to update the statsd server based on good or bad requests. Here’s a screen capture of it in action (click on the image to enlarge):

Monitoring Graph

Processing Quicken QFX Files

I recently wanted to download my bank transactions from E*Trade, but they do not support Excel format for the bank statements (only brokerage).  You can, however, download 3 months of Quicken QFX data at a time.  I downloaded several files spanning the year-to-date, but then needed a program to parse up the files and output a tab delimited text file.  So, for a try in formatting code, here’s what I got:

#!/usr/bin/perl -w
# Dan Minear
# 2011-09-01
#
# process Quicken QFX files and output tab delimited text file
#
# call 

use strict;
use FileHandle;
use Data::Dumper;

my $data = {};
my $capture = 0;		# don't capture
my %txntypes = ();

if (@ARGV < 1) {
	die "Syntax:  $0 \nExample:  $0 09*.QFX\n";
}

my $fo = FileHandle->new( "> out.txt");
if (! defined $fo) {
	die "Cannot write file";
}

while (my $fname = shift) {
	print $fname . "\n";
	my $fi = FileHandle->new("< $fname");
	if (defined $fi) {
		my $txn = {};
		while(<$fi>) {
			chomp;
			chop;
			# look for  
			if (/^/) {	#start of record
				$capture = 1;
				$txn = {};
				next;
			}
			if (/<\/STMTTRN>/) {	# end of record
				$capture = 0;
				# add to data hash
				if (defined $data->{$txn->{FITID}}) { 
					print "FITID $txn->{FITID} already defined\n";
				} else {
					$data->{$txn->{FITID}} = $txn;
					$txntypes{$txn->{TRNTYPE}} = 1;
				}
			}
			if ($capture) {
				/<(\w+)>(.+)$/;
				$txn->{$1} = $2;	
			}
		}
	} else {
		die "cannot open $fname for reading";
	}
	$fi->close;
}

print "there are " . keys(%$data) . " transactions in files\n";

#print $fo Dumper( $data );
print $fo "DATE\tTYPE\tCREDIT\tDEBIT\tNAME\tMEMO\n";
foreach my $i (keys(%$data)) {
	my $t = $data->{$i};	# ref to hash
	my $date = $t->{DTPOSTED};

	if ($t->{TRNAMT} < 0) {	# it's a debit
		print $fo substr($date,0,4) . "-" . substr($date,4,2) . "-" . substr($date,6,2) . "\t" .
			$t->{TRNTYPE} . "\t" .
			"\t" .
			$t->{TRNAMT} . "\t" .
			$t->{NAME} . "\t" . 
			$t->{MEMO} . "\n";
	} else {	# it's a credit
		print $fo substr($date,0,4) . "-" . substr($date,4,2) . "-" . substr($date,6,2) . "\t" .
			$t->{TRNTYPE} . "\t" .
			$t->{TRNAMT} . "\t" .
			"\t" .
			$t->{NAME} . "\t" . 
			$t->{MEMO} . "\n";
	}
}

$fo->close;

=head1 uncomment to print out transaction types
foreach (keys(%txntypes)) {
	print $_ . "\n";
}
=cut

Automating my sprinklers

I have been spending a few hours creating a sprinkler controller.  The basic structure is to have a Perl program do the hard core logic, and then issue a webservice request to trigger the sprinkler controller on for a watering cycle.  So far, I have the embedded controller ready to interface to a hacked sprinkler controller .  The controller just takes a simple command to water a zone for so many seconds.  There is no higher level sequencing that is in place yet.  In reality, I probably need to send a sequence to the embedded controller so the power supply only powers one water solenoid at a time. The alternative is  to put that logic in the Perl program, so that’s where I am now.

I have been working on the Perl program to figure out my watering algorithm.  So far, I just make a request to a NOAA weather site for an airport  about 2 miles from me. The document is slurped as XML data that XML::Simple throws into a hash.  From there, I take the relative humidity and integrate the “dryness”,  or 100 minus the humidity, for the given time span.  I have decided to water my grass every 3 days as a ballpark until I see how things are working. The program just prints out a trigger message for now.  In the future, this will spawn off a sequence of webservice requests to water the lawn.

So far, we have been having some interesting weather.  There have been off-and-on rain storms coming through, and this week is setting up for a rapid change into hotter weather.  A weather description that matches “Rain” will cause a reset of the integration so the lawn is not watered.  The script logs some pertenant data to the end of the script so I can rerun a scenario in the future with real world data.

A side product of this project is to tie it to my Doggie Dumpcam, which captures realtime image changes on my front lawn.  The tree is growing bigger lately, which is creeping outside of the mask area and triggering a lot of image captures.  Some interesting things have been captured, including dogs, birds, and hornets.  This Perl program will have the capability to trigger a particular sprinkler zone between, say, the 6am and 8am hours for a small timespan.