#!/usr/bin/perl -w
# Copyright (c) 2007 <openbsd -at- otterhole.ca>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

# If you use this software, please send a note to the above email
# address indicating anything interesting you have to share.  This
# is a request and imposses no obligation.

# Hopefully nobody has patented this obvious idea.

########################################################################
# Tue Mar  3 16:32:51 EST 2009
#       v0.4a Moved variables for log to follow, and page to look for, near top
# Mon Nov 26 10:23:30 EST 2007
#       v0.4 changed logging to read Pageknock
# Tue Jun 19 21:15:25 EDT 2007
#       v0.3 added some syslog functions
# Sat May 26 10:04:40 EDT 2007
#       v0.2 added a remove clause
# Mon Apr 16 18:48:06 EDT 2007, ARM
#       v0.1 initial version.  And it works o.k.
#
# concept:
#   1. ssh clients first use a web browser to request <site>/<page>
#   2. this looks through the web log file, and finds clients which 
#      requests <site>/<page>
#   3. when this finds an entry, it adds the source address to
#      a table in the packet filter
#   4. with this table entry, the desired access (say, ssh) is allowed
#      via the appropriate packet filter rule
#      table <sshknock> persist
#      rdr pass on $ext_if inet proto tcp from <sshknock> to ($ext_if) port 443 -> 127.0.0.1 port 22
#      pass in log quick on $ext_if inet proto tcp from <sshknock> to ($ext_if1) port 22 keep state
#
# support:
#   1. Apache, or some other web service, needs to be running.
#      (security: apache is running anyway...
#                 apache is in a chrooted environment, ssh is not
#      )
#   2. A pf table, <sshknock> needs to be loaded into the packet
#      filter rules which allows the desired access.
#
# todo:
#   be more graceful, 
#    - trap HUP, so it stays in the background
#    - capture output (stderr) of pfctl and send it to /dev/null or a log
#    - keep a hash, or something, so that we don't try to populate
#      the same address multiple times per second.
#    - hopefully, deal with autoexpiring <tables> in pf for OpenBSD 4.1
#
# status:
#    as of Apr 16: working.
########################################################################

use strict;
use Unix::Syslog qw(:macros); 
use Unix::Syslog qw(:subs); 

# User configurable knobs
my $LOG="/var/www/logs/access_log";
my $PAGE="/access.html";
my $NOPAGE="/noaccess.html";

# OpenBSD's tail follows log files as they are rotated, so we
# don't have to worry about that unless we deal with another
# platform
my $cmd="/usr/bin/tail -n 0 -f $LOG|";

open (FLOG, $cmd) or die "crapped out";
           $| = 1;
openlog "knock", LOG_PID, LOG_AUTH;       # don't forget this


while (<FLOG>) {
  # This regular expression works for my system.
  if (m#^([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})[^"]{20,40}"GET $PAGE#) {
    system ("pfctl -t sshknock -Tadd " . $1);
    # print "hello: $1"; # I used this during testing
    syslog LOG_NOTICE, "Pageknock added %s", $1;
  } elsif (m#^([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})[^"]{20,40}"GET $NOPAGE#) {
    system ("pfctl -t sshknock -Tdelete " . $1);
    syslog LOG_NOTICE, "Pageknock removed %s", $1;
  } else {
    # I used this block during testing
    syslog LOG_DEBUG, "Pageknock following logs", $1;
  } 
}

closelog; # we probably never get here...

# This is an example of my logfile
#XX6.X7.XX9.XX2 - - [16/Apr/2007:18:12:50 -0400] "GET /access.html HTTP/1.1"

