Note: You are viewing an old version of this page. View the current version.

#!/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 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 badHost($ip, $line) {
  global $bad_hosts, $bad_hosts_timeout, $bad_hosts_limit;
  //printf("==> badHost(%s)\n", $ip);
  // 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 banBadHost($ip) {
  // Magical Gnomes
  global $bad_hosts;
  logit("Banning %s for grinding.", $ip);
  $cmd = sprintf("/root/siteban/autoban %s", escapeshellarg($ip));
  exec($cmd);
  unset($bad_hosts[$ip]);
}

function matchLine($pattern, $key, $line) {
  global $trig_vars;
  $regs = Array();
  if ($res = preg_match($pattern, $line, $regs)) {
    $out = Array();
    //printf("[%s]=> (%s) %s \n", $key, $res, $line);
    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;
}

tailLog($log);

$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");

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);

}

?>