#!/usr/bin/env php
<?php
$log = '/var/log/auth.log';
$ignore_hosts = Array(
"yiff.myftp.org"
);
$ignore_hosts_cache = Array(); // ip => Array('ip' => <ip>, 'name' => <name>, 'expires' => <time>)
$ignore_hosts_cache_timeout = 60;
$bad_hosts = Array(); // ip => Array('expires' => <time>, 'evidence' => Array(<evidence>))
$bad_hosts_timeout = 60;
$bad_hosts_limit = 10;
openlog('StopGrind', LOG_PID|LOG_PERROR, LOG_AUTHPRIV);
function logit() {
//printf("%s: ", strftime("%Y-%m-%d:%H:%M:%S"));
$args = func_get_args();
$line = call_user_func_array('sprintf', $args);
syslog(LOG_NOTICE, $line);
//printf("\n");
//printf("%s: %s\n", strftime("%Y-%m-%d:%H:%M:%S"), $line);
}
function loadState() {
logit("fixme:STUB:loadState()");
}
function saveState($state) {
logit("fixme:STUB:saveState()");
}
function authorizedHost($ip) {
global $ignore_hosts, $ignore_hosts_cache, $ignore_hosts_cache_timeout;
// Expire cache entries
$now = time();
foreach ($ignore_hosts_cache as $key => $ignore_host)
if ($ignore_host['expires'] < $now)
unset($ignore_hosts_cache[$key]);
// Now look up a cached entry
if (isset($ignore_hosts_cache[$ip]))
return TRUE;
// Now look up the ignore_hosts one by one until we get a match
foreach ($ignore_hosts as $host) {
$record = dns_get_record($host, DNS_ANY);
foreach($record as $entry) {
if ($entry['ip']) {
$ignore_hosts_cache[] = Array(
'ip' => $entry['ip'],
'name' => $entry['host'],
'expires' => time() + $entry['ttl']
);
if ($entry['ip'] == $ip)
return TRUE;
}
}
}
return FALSE;
}
function banBadHost($ip) {
// Magical Gnomes
global $bad_hosts;
logit("bad host banned: %s", $ip);
$cmd = sprintf("/root/siteban/autoban %s", escapeshellarg($ip));
//exec($cmd);
unset($bad_hosts[$ip]);
}
function badHost($ip, $line) {
global $bad_hosts, $bad_hosts_timeout, $bad_hosts_limit;
// Expire
foreach($bad_hosts as $key => $value)
if ($value['expires'] < time())
unset($bad_hosts[$key]);
// Check
if (!is_array($bad_hosts[$ip]))
$bad_hosts[$ip] = Array('evidence' => Array());
$bad_hosts[$ip]['expires'] = time() + $bad_hosts_timeout;
$bad_hosts[$ip]['evidence'][] = $line;
if (count($bad_hosts[$ip]['evidence']) > $bad_hosts_limit)
banBadHost($ip);
}
function matchLine($pattern, $key, $line) {
global $trig_vars;
$regs = Array();
if ($res = preg_match($pattern, $line, $regs)) {
$out = Array();
foreach($trig_vars[$key] as $index => $name) {
if ($name) $out[$name] = $regs[$index];
}
if (!authorizedHost($out['ip'])) {
badHost($out['ip'], $line);
}
}
}
function tailLog($log) {
global $source;
if (is_resource($source))
pclose($source);
$cmd = sprintf("tail -fn 1024 %s", escapeshellarg($log));
//logit("%s", $cmd);
$source = popen($cmd, "r");
// Ignore the first line, in case it's a newsyslog message.
fgets($source, 4096);
return $source;
}
function handle($line) {
global $trig_pats;
$line = trim($line);
array_walk($trig_pats, "matchLine", $line);
}
$trig_pats = Array(
"/Failed ([^ ]+) for( invalid user){0,1} ([^ ]+) from ([^ ]+) port ([^ ]+)/",
"/Invalid user ([^ ]+) from ([^ ]+)/",
);
$trig_vars = Array(
Array(
"",
"method",
"",
"username",
"ip",
"port"
),
Array(
"",
"username",
"ip"
)
);
logit("Starting scanner");
$state = loadState();
$lines = 0;
$running = TRUE;
$buf = "";
while ($running) {
$read = Array(STDIN);
$write = NULL;
$except = NULL;
$timeout = 60;
if ($res = stream_select($read, $write, $except, $timeout)) {
// Handle incoming data
if (0 == strlen($tmp = fread(STDIN, 4096))) {
// EOF
logit("No more input. Night night.");
$running = FALSE;
} else {
$buf.= $tmp;
while (FALSE !== ($pt = strpos($buf, "\n"))) {
$lines++;
$line = substr($buf, 0, $pt);
$buf = substr($buf, $pt + 1, strlen($buf));
handle($line);
}
}
} else {
// Timeout or interrupt. Die.
logit("Timeout. Night night.");
$running = FALSE;
}
}
logit("Processed %u lines", $lines);
saveState($state);
return 0;
tailLog($log);
while ($line = fgets($source, 16384)) {
$line = trim($line);
// Check for newsyslog message
if (preg_match('/newsyslog[^:]+: logfile turned over/', $line)) {
//logit($line);
logit("Reloading logfile\n");
pclose($source);
die();
tailLog($log);
}
array_walk($trig_pats, "matchLine", $line);
}
pclose($source);
?>