PHP Classes

File: src/Module/SpamBot.php

Recommend this page to a friend!
  Classes of stefan   contao PHP Spambot Detection   src/Module/SpamBot.php   Download  
File: src/Module/SpamBot.php
Role: Class source
Content type: text/plain
Description: Class source
Class: contao PHP Spambot Detection
Detect and block spam bots from accessing sites
Author: By
Last change:
Date: 1 year ago
Size: 13,096 bytes
 

Contents

Class file image Download
<?php declare(strict_types=1); /* * sync*gw SpamBot Bundle * * @copyright https://syncgw.com, 2013 - 2022 * @author Florian Daeumling, https://syncgw.com * @license http://opensource.org/licenses/lgpl-3.0.html */ namespace syncgw\SpamBotBundle\Module; use Contao\Environment; use Contao\System; class SpamBot extends System { // modes const MOD_FIRST = 1; const MOD_SPAM = 2; const MOD_HAM = 3; // module type const TYP_IP = 1; const TYP_MAIL = 2; // spam types const NOTFOUND = 0x01; const SPAM = 0x02; const HAM = 0x04; const WHITEL = 0x08; const BLACKL = 0x10; const LOADED = 0x20; // text translation public static $Status = [ self::NOTFOUND => 'NotFound', self::SPAM => 'Spam', self::HAM => 'Ham', self::WHITEL => 'WhiteList', self::BLACKL => 'BlackList', self::LOADED => 'Loaded', ]; /* * module ID * @var int */ public $modID; // extended information public $ExtInfo; // error message public $ErrMsg; // HTTP header received public $Header = []; // database pointer public $Db; /* * internal cache * @var array */ protected $arrData = []; // use IP as is protected $Raw = FALSE; // HTTP buffer protected $Buffer = []; /** * Initialize class * * @param module id */ public function __construct(int $modID = 0) { parent::__construct(); $this->Db = \Contao\Database::getInstance(); $this->modID = $modID; } /** * Load variables */ public function __get($strKey) { if (!isset($this->arrData[$strKey])) { // record data already loaded? if (NULL === $this->Fields) return NULL; $rc = $this->Db->prepare('SELECT '.implode(',', array_keys($this->Fields)).' from tl_module WHERE id=?')->execute($this->modID); foreach ($this->Fields as $k => $v) { if ($v) { $this->arrData[$k] = deserialize($rc->$k); if (!is_array($this->arrData[$k])) $this->arrData[$k] = []; } else $this->arrData[$k] = $rc->$k; } $this->Fields = NULL; } return $this->arrData[$strKey]; } /** * Save variable */ public function __set($strKey, $varValue) { $this->arrData[$strKey] = $varValue; } /** * Perform parallel checking * * @param function to call * @param IP address * @param mail address * @param call mod * @param 1=Execution time; 2=Additional info * * @return array (SpamBot::Status, status message, execution time) */ public function callMods(int $func, string $ip, string $mail, int $mod = self::MOD_SPAM, int $info = 0): array { // get configured engines $conf = $this->Db->prepare('SELECT spambot_engines FROM tl_module WHERE id=?')->execute($this->modID); if (!is_array($conf = deserialize($conf->spambot_engines))) return ['Intern' => [self::NOTFOUND, 'No providwr selected', 0]]; // start time $start = []; // handler ID $hd = []; // end time $end = []; // return array: name => array (SpamBot::Status, status message, execution time) $rc = []; // number of available active handlers $active = 0; // call all engines in parallel foreach ($conf as $name) { if ($info) $start[$name] = microtime(TRUE); $rc[$name] = NULL; // var_dump(Environment::get('httpHost').'/bundles/spambot/SpamBotCall.php'.'?Mod='.$this->modID.'&Class='.$name.'&Func='.$func. // '&IP='.base64_encode($ip).'&Mail='.base64_encode($mail).'&ExtInfo='.(2 === $info ? '1' : '0')); if (!($hd[$name] = self::openHTTP(Environment::get('httpHost'), '/bundles/spambot/SpamBotCall.php'. '?Mod='.$this->modID.'&Class='.$name.'&Func='.$func. '&IP='.base64_encode($ip).'&Mail='.base64_encode($mail).'&ExtInfo='.(2 === $info ? '1' : '0')))) { $rc[$name] = [self::NOTFOUND, $name.': '.$this->ErrMsg]; } else $active++; } // wait until all handler has terminated while ($active > 0) { foreach ($conf as $name) { // done? if (!$hd[$name]) continue; // get response $r = self::readHTTP($hd[$name]); if ($info) $end[$name] = microtime(TRUE); // validate return value if ($r === FALSE) { // simualte not found on error $rc[$name] = [self::NOTFOUND, $name.': '.$this->ErrMsg, 0]; fclose($hd[$name]); $hd[$name] = NULL; $r = NULL; } // any valid response if (is_null($r) || !strlen($r)) continue; // check wait mode switch ($mod) { case self::MOD_FIRST: $active = 1; // no break case self::MOD_SPAM: case self::MOD_HAM: default: $active--; break; } // build return value if (FALSE === ($a = unserialize($r))) $rc[$name] = [self::NOTFOUND, $name.': '.$r, 0]; else { $rc[$name] = $a; // set no execution time $rc[$name][2] = 0; } } } // cleanup foreach ($conf as $name) { if ($hd[$name]) fclose($hd[$name]); if ($info) $rc[$name][2] = round(($end[$name] - $start[$name]) * 1000, 3); } return $rc; } /** * Default check data * * @param typ to check * @param IP address * @param mail address * * @return array (SpamBot::Status, status message) or received status codes (raw=TRUE) */ public function check(int $typ, string $ip, string $mail): array { if (self::TYP_MAIL === $typ) return [self::NOTFOUND, sprintf($GLOBALS['TL_LANG']['SpamBot']['SpamBot']['notfound'], $this->Name)]; // build query $chk = $this->Raw ? $ip : implode('.', array_reverse(explode('.', $ip))).$GLOBALS['SpamBot']['Engines'][$this->Name]['DNSBL']; $rc = []; $ext = FALSE; if (!$this->Raw) $this->ExtInfo = '<fieldset style="padding:3px"><div style="color:blue;">'. 'Checking <strong>'.(self::TYP_IP === $typ ? $ip : $mail).'</strong> <br />'; if (version_compare(PHP_VERSION, '5.3', '>=')) { if ($r = @dns_get_record($chk, DNS_A + DNS_TXT)) { foreach ($r as $s) { if ('A' === $s['type']) { $rc[] = $s['ip']; $this->ExtInfo .= 'Status received is <strong>'.$s['ip'].'</strong><br />'; } else { $ext = TRUE; if (isset($s['txt'])) $this->ExtInfo .= 'TXT record is <strong>'.$s['txt'].'</strong><br />'; } } } // hack for spamcop.net which does not return "A" record if ($ext && !count($rc)) $rc[] = '127.0.0.2'; } else { $rc[0] = gethostbyname($chk); $this->ExtInfo .= '<strong>Return code:</strong> '.$rc[0].'<br />'; } $this->ExtInfo .= '</div></fieldset>'; if (!count($rc)) return [self::NOTFOUND, sprintf($GLOBALS['TL_LANG']['SpamBot']['generic']['notfound'], $this->Name)]; if ('127' !== substr($rc[0], 0, 3)) return [self::NOTFOUND, sprintf($GLOBALS['TL_LANG']['SpamBot']['generic']['err'], $this->Name, $rc[0])]; // raw call? if ($this->Raw) return serialize($rc); // spam list $codes = is_array($GLOBALS['SpamBot']['Engines'][$this->Name]['Codes']) ? $GLOBALS['SpamBot']['Engines'][$this->Name]['Codes'] : []; // module configuration array if (!is_array($conf = $this->{'spambot_'.strtolower($this->Name).'_mods'})) $conf = $GLOBALS['SpamBot']['Engines'][$this->Name]['Spam']; // check for Spam foreach ($rc as $stat) { foreach ($codes as $k => $v) { if (!in_array($k, $conf, TRUE)) continue; // wild card? if ($p = strpos($k, '*')) { if (substr($k, 0, $p) === substr($stat, 0, $p)) return [self::SPAM, $this->Name.': '.$v]; } if ($k === $stat) return [self::SPAM, $this->Name.': '.$v]; } } // check for Ham foreach ($rc as $stat) { foreach ($codes as $k => $v) { if (in_array($k, $conf, TRUE)) continue; // wild card? if ($p = strpos($k, '*')) { if (substr($k, 0, $p) === substr($stat, 0, $p)) return [self::HAM, $this->Name.': '.$v]; } if ($k === $stat) return [self::HAM, $this->Name.': '.$v]; } } // not found return [self::NOTFOUND, sprintf($GLOBALS['TL_LANG']['SpamBot']['generic']['unsup'], $this->Name, $rc)]; } /** * Open connection to host * * @param host name * @param URI * @param timeout * * @return mixed $pointer */ public function openHTTP(string $host, string $uri, int $timeout = 10) { // prepare request (no fopen() usage because "allow_url_fopen=FALSE" may be set in PHP.INI) $req = 'GET '.$uri." HTTP/1.0\r\nHost: ".$host."\r\n\r\n"; $sock = 80 != $_SERVER['SERVER_PORT'] ? 'ssl://' : NULL; $err = NULL; if (($fp = fsockopen($sock.$host, intval($_SERVER['SERVER_PORT']), $err, $this->ErrMsg, 10))) { fwrite($fp, $req); stream_set_timeout($fp, $timeout); $this->Header[(int) ($fp)] = []; $this->Buffer[(int) ($fp)] = NULL; if (($this->Buffer[(int) ($fp)] = self::readHTTP($fp)) === FALSE) { fclose($fp); return FALSE; } } else $this->ErrMsg = '['.$err.'] '.$this->ErrMsg; return $fp; } /** * Read data * * @param file pointer * * @return FALSE on error; else string */ public function readHTTP($fp) { if (!is_resource($fp) || !$fp) { $this->ErrMsg = 'Connection to "'.$this->Name.'" is not a resource'; return FALSE; } if ($this->Buffer[(int) ($fp)]) { $wrk = $this->Buffer[(int) ($fp)]; $this->Buffer[(int) ($fp)] = NULL; return $wrk; } if (FALSE === ($wrk = fread($fp, 8192))) { $info = stream_get_meta_data($fp); if ($info['timed_out']) $this->ErrMsg = 'Connection to "'.$this->Name.'" timed out ('.$info['timed_out'].' sec.)'; else $this->ErrMsg = 'Unspecific connection error to "'.$this->Name.'"'; return FALSE; } if (is_array($this->Header[(int) ($fp)])) $do = !count($this->Header[(int) ($fp)]) ? 1 : 0; else $do = 0; if ($do && $wrk) { $this->Header[(int) ($fp)] = explode("\r\n", substr($wrk, 0, $pos = strpos($wrk, "\r\n\r\n"))); $wrk = substr($wrk, $pos + 4); if (!$wrk) $wrk = NULL; // we use this approach to get "HTTP/1.0 200 OK" as well as "HTTP/1.1 200 OK" if (count($this->Header[(int) ($fp)]) && FALSE === strpos($this->Header[(int) ($fp)][0], '200 OK')) { $this->ErrMsg = $this->Header[(int) ($fp)][0]; return FALSE; } } return $wrk; } } ?>