* @copyright Yuji Iwashita * @version $Id: spamp.php,v 1.3 2010/03/19 18:59:42 YujiIwashita Exp YujiIwashita $ */ class spamp { /** * Configration params * * @access protected * @var array */ var $Conf = array(); /** * Error Messages * * @access private * @var array */ var $__Messages = array(); /** * Public methods: */ /** * Constructer for PHP4 * * @access public * @param string $filename Configration filename */ function spamp ( $filename = null ) { $this->_loadConf($filename); } /** * Constructer for PHP5 * * @access public * @param string $filename Configration filename */ function __construct ( $filename = null ) { $this->_loadConf($filename); } /** * Put 1x1 GIF image * * @access public * @param string $formKey default $conf['formKey'] or $_SERVER['HTTP_REFERER'] * @param string $server default $_SERVER['HTTP_HOST'] * @return void */ function putImage ( $formKey = null, $server = null ) { if (headers_sent($file, $line)) { $this->_setMessage('putImage(): headers already sent. '.$file.':'.$line); exit(); } // Remove spam cookie if ($this->Conf['cookieValidate']) { $this->removeSpamCookie(); } // Save session $this->saveSession($formKey, null, null, $server); // Put GIF Image header('X-Powered-By: PHP'); header('Cache-Control: no-cache'); header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); header('Content-Type: image/gif'); header('Content-Length: 39'); echo base64_decode('R0lGODlhAQABAGAAACH5BAEKAP8ALAAAAAABAAEAAAgEAP8FBAA7'); exit(); } /** * Is SPAM Request? * * @access public * @param string $formKey default $conf['formKey'] or $_SERVER['HTTP_REFERER'] * @param string $ip default $_SERVER['REMOTE_ADDR'] * @param string $ua default $_SERVER['HTTP_USER_AGENT'] * @param string $server default $_SERVER['HTTP_HOST'] * @return int 0:No spam, 1:Cookie, 2:Session, 4:DenyHost, 8:DenyWord */ function isSpam ( $formKey = null, $ip = null, $ua = null, $server = null ) { if (null === $ip) { $ip = $_SERVER['REMOTE_ADDR']; } $result = 0; // spam cookie if ($this->Conf['cookieValidate'] && $this->hasSpamCookie()) { if (!$this->isAllowHost($ip)) { $result = 1; if ($this->Conf['denyLogging']) { $this->saveDenylog($this->Conf['denyLogFile'], $result); } return $result; } $this->removeSpamCookie(); } // session if (!$this->hasSession($formKey, $ip, $ua, $server)) { $result = 2; } // host if ($result) { if ($this->isAllowHost($ip)) { $result = 0; } } else { if ($this->isDenyHost($ip)) { $result = 4; } } // form param if (!$result && 'POST' === $_SERVER['REQUEST_METHOD']) { // words if ($this->isDenyWord(serialize($_POST))) { $result = 8; } } if ($result) { // send spam cookie if ($this->Conf['cookieValidate']) { $this->setSpamCookie(); } // deny logging if ($this->Conf['denyLogging']) { $this->saveDenylog($this->Conf['denyLogFile'], $result); } } // Remove timeout files if (3 & time()) { $this->removeSessionAll(); } return $result; } /** * Has Session? * * @access public * @param string $formKey default $conf['formKey'] or $_SERVER['HTTP_REFERER'] * @param string $ip default $_SERVER['REMOTE_ADDR'] * @param string $ua default $_SERVER['HTTP_USER_AGENT'] * @param string $server default $_SERVER['HTTP_HOST'] * @return boolean */ function hasSession ( $formKey = null, $ip = null, $ua = null, $server = null ) { $sessFile = $this->_generateSessFile($formKey, $ip, $ua, $server); if (is_file($sessFile)) { $fileTime = filemtime($sessFile); if ( ($fileTime + $this->Conf['lifeTime']) > time() && ($fileTime + $this->Conf['waitingTime']) < time() ) { return true; } elseif (is_writable($sessFile)) { @unlink($sessFile); } } return false; } /** * Save Session * * @access public * @param string $formKey default $conf['formKey'] or $_SERVER['HTTP_REFERER'] * @param string $ip default $_SERVER['REMOTE_ADDR'] * @param string $ua default $_SERVER['HTTP_USER_AGENT'] * @param string $server default $_SERVER['HTTP_HOST'] * @return boolean */ function saveSession ( $formKey = null, $ip = null, $ua = null, $server = null ) { if (!is_writable($this->Conf['workDir'])) { $this->_setMessage('saveSession(): Not writable attribute in work directory. '.$this->Conf['workDir']); return false; } if (null === $server) { $server = (isset($_SERVER['HTTP_HOST'])) ? $_SERVER['HTTP_HOST']: ''; } $sessFile = $this->_generateSessFile($formKey, $ip, $ua, $server); $fp = fopen($sessFile, 'w'); fclose($fp); return true; } /** * Remove Session * * @access public * @param string $formKey default $conf['formKey'] or $_SERVER['HTTP_REFERER'] * @param string $ip default $_SERVER['REMOTE_ADDR'] * @param string $ua default $_SERVER['HTTP_USER_AGENT'] * @return boolean */ function removeSession ( $formKey = null, $ip = null, $ua = null, $server = null ) { if (!is_writable($this->Conf['workDir'])) { $this->_setMessage('removeSession(): Not writable attribute in work directory. '.$this->Conf['workDir']); return false; } $sessFile = $this->_generateSessFile($formKey, $ip, $ua, $server); if (!is_file($sessFile)) { return true; } return @unlink($sessFile); } /** * Remove Session files all * * @access public * @param string $all true: all, false: timeout only (default) * @return boolean */ function removeSessionAll ( $all = false ) { if (!is_writable($this->Conf['workDir'])) { $this->_setMessage('removeSessionAll(): Not writable attribute in work directory. '.$this->Conf['workDir']); return false; } $sessFiles = glob($this->Conf['workDir'].DIRECTORY_SEPARATOR.'*'.$this->Conf['sessSuffix']); if (!is_array($sessFiles) || !isset($sessFiles[0])) { return true; } if ($all) { foreach ($sessFiles as $file) { @unlink($file); } } else { $limit = time() - $this->Conf['lifeTime']; foreach ($sessFiles as $file) { if ($limit > filemtime($file)) { @unlink($file); } } } return true; } /** * Is Allow host? * * @access public * @param string $ip default $_SERVER['REMOTE_ADDR'] * @return boolean */ function isAllowHost ( $ip = null ) { static $hosts = null; if (null === $hosts) { $hosts = $this->_loadData($this->Conf['confDir'].DIRECTORY_SEPARATOR.$this->Conf['allowHostsFile']); } if (!is_array($hosts) || !isset($hosts[0])) { return false; } if (null === $ip) { $ip = $_SERVER['REMOTE_ADDR']; } foreach ($hosts as $host) { if ($this->inNetwork($ip, $host)) { return true; } } return false; } /** * Is Deny host? * * @access public * @param string $ip default $_SERVER['REMOTE_ADDR'] * @return boolean */ function isDenyHost ( $ip = null ) { static $hosts = null; if (null === $hosts) { $hosts = $this->_loadData($this->Conf['confDir'].DIRECTORY_SEPARATOR.$this->Conf['denyHostsFile']); } if (!is_array($hosts) || !isset($hosts[0])) { return false; } if (null === $ip) { $ip = $_SERVER['REMOTE_ADDR']; } foreach ($hosts as $host) { if ($this->inNetwork($ip, $host)) { return true; } } return false; } /** * Is Deny Word? * * @access public * @param string $word * @return boolean */ function isDenyWord ( $word ) { static $words = null; if (null === $words) { $words = $this->_loadData($this->Conf['confDir'].DIRECTORY_SEPARATOR.$this->Conf['denyWordsFile']); } if (!is_array($words) || !isset($words[0])) { return false; } foreach ($words as $wd) { if (in_array($wd, $word)) { return true; } } return false; } /** * In Network address * * @access public * @param string $ip IPv4 (000.000.000.000) * @param string $network IPv4 or CIDR (000.000.000.000/00) * @return boolean */ function inNetwork ( $ip, $network ) { if (false === strpos($network, '/')) { return ($ip === $network) ? true: false; } list($net, $mask) = explode('/', $network); $shift = 32 - $mask; $ip = ip2long($ip) >> $shift; $net = ip2long($net) >> $shift; return ($ip === $net) ? true: false; } /** * Get Request params * * @access public * @param int $addFlag 0: none (default), 1: $_SERVER['HTTP_*'], 2: $_POST, 3: $_SERVER['HTTP_*'] | $_POST * @return array */ function getRequestParams ( $addFlag = 0 ) { $params = array(); // basic params $params['sec'] = time(); $params['server'] = (isset($_SERVER['HTTP_HOST'])) ? $_SERVER['HTTP_HOST']: ''; $params['method'] = $_SERVER['REQUEST_METHOD']; $params['protocol'] = $_SERVER['SERVER_PROTOCOL']; $params['uri'] = $_SERVER['REQUEST_URI']; $params['referer'] = (isset($_SERVER['HTTP_REFERER'])) ? $_SERVER['HTTP_REFERER']: ''; $params['host'] = $_SERVER['REMOTE_ADDR']; $params['user'] = (isset($_SERVER['REMOTE_USER'])) ? $_SERVER['REMOTE_USER']: ''; $params['agent'] = (isset($_SERVER['HTTP_USER_AGENT'])) ? $_SERVER['HTTP_USER_AGENT']: ''; // add $_SERVER['HTTP_*'] if (1 & $addFlag) { $http = array(); foreach ($_SERVER as $key => $val) { if ('H' !== $key{0} || 'HTTP_' !== substr($key, 0, 5) || 'HTTP_HOST' === $key || 'HTTP_USER_AGENT' === $key || 'HTTP_REFERER' === $key) { continue; } $http[$key] = $val; } $params['http'] = $http; } // add $_POST if (2 & $addFlag && isset($_POST) && count($_POST)) { if (function_exists('mb_convert_encoding')) { $outEnc = (null === $this->Conf['logEncoding']) ? 'UTF-8': $this->Conf['logEncoding']; $inEnc = mb_detect_encoding(serialize($_POST)); if ('ASCII' === $inEnc || $outEnc === $inEnc) { $params['post'] = $_POST; } else { $params['post'] = $this->_convertEncoding($_POST, $outEnc, $inEnc); } } else { $params['post'] = $_POST; } } return $params; } /** * Has SPAM Cookie * * @access public * @return boolean */ function hasSpamCookie () { if (isset($_COOKIE[$this->Conf['cookieName']])) { return true; } return false; } /** * Set SPAM Cookie * * @access public * @return boolean */ function setSpamCookie () { if (headers_sent()) { return false; } return setcookie( $this->Conf['cookieName'], $this->_generateCookieParam(), time() + $this->Conf['cookieLifeTime'], $this->Conf['cookiePath'], $this->Conf['cookieDomain'] ); } /** * Get SPAM Cookie Params * * @access public * @return array */ function getSpamCookie () { if (!isset($_COOKIE[$this->Conf['cookieName']])) { return array(); } if (false === strpos($_COOKIE[$this->Conf['cookieName']], '-')) { return array(); } $params = explode('-', $_COOKIE[$this->Conf['cookieName']]); $cookieParam = array(); $cookieParam['sec'] = (int)$params[0]; $cookieParam['host'] = $params[1]; $cookieParam['agent'] = $params[2]; return $cookieParam; } /** * Remove SPAM Cookie * * @access public * @return boolean */ function removeSpamCookie () { if (!isset($_COOKIE[$this->Conf['cookieName']])) { return true; } if (headers_sent()) { return false; } unset($_COOKIE[$this->Conf['cookieName']]); return setcookie( $this->Conf['cookieName'], '', 1, $this->Conf['cookiePath'], $this->Conf['cookieDomain'] ); } /** * Save Deny log * * @access public * @param string $filename Log filename * @param int $result add result * @return boolean */ function saveDenylog ( $filename = null, $result = null ) { if (null === $filename) { if (!$this->Conf['denyLogFile']) { $this->_setMessage("saveDenylog(): Empty 'denyLogFile'."); return false; } $filename = $this->Conf['workDir'].DIRECTORY_SEPARATOR.$this->Conf['denyLogFile']; } if ('/' !== $filename{0} && ':' !== $filename{1}) { $filename = $this->Conf['workDir'].DIRECTORY_SEPARATOR.$filename; } if (!is_writable(dirname($filename))) { $this->_setMessage('saveDenylog(): Not writable attribute in work directory. '.dirname($filename)); return false; } $record = $this->getRequestParams($this->Conf['denyLoggingAddParam']); $record = $this->_serializeEncode($record); if (is_integer($result) && 0 < $result) { $record['result'] = $result; } return error_log(serialize($record)."\n", 3, $filename); } /** * Load Deny log * * @access public * @param string $filename Log filename * @return array */ function loadDenylog ( $filename = null ) { if (null === $filename) { if (!$this->Conf['denyLogFile']) { $this->_setMessage("loadDenylog(): Empty 'denyLogFile'."); return false; } $filename = $this->Conf['workDir'].DIRECTORY_SEPARATOR.$this->Conf['denyLogFile']; } if (is_file($filename) || !is_readable($filename)) { $this->_setMessage("loadDenylog(): Empty 'denyLogFile'."); return false; } $lines = file($filename); if (!isset($lines[0])) { return array(); } $log = array(); foreach ($lines as $line) { $line = trim($line); if (!$line) { continue; } $log[] = unserialize($line); } return $log; } /** * Get Error messages * * @access public * @return array */ function getMessage () { return $this->__Messages; } /** * Protected methods: */ /** * Load Configration & Initialize * * @access protected * @param string $filename Configration filename * @return boolean */ function _loadConf ( $filename = null ) { $confDir = dirname(__FILE__); $confFile = 'spamp.conf.php'; $hasFile = true; // Load Configration file if (null === $filename) { $filename = $confDir.DIRECTORY_SEPARATOR.$confFile; } if (!is_file($filename)) { if (is_dir($filename)) { $confDir = rtrim($filename, '/\\'); $filename = $confDir.DIRECTORY_SEPARATOR.$confFile; if (!is_file($filename)) { $this->_setMessage('_loadConf(): Not exists configration file. '.$filename); $hasFile = false; } } else { $this->_setMessage('_loadConf(): Not exists configration file. '.$filename); $hasFile = false; } } if ($hasFile) { if (is_readable($filename)) { $this->Conf['confDir'] = dirname($filename); include $filename; } else { $this->_setMessage('_loadConf(): Not readable configration file. '.$filename); $hasFile = false; } } if (!isset($workDir) || !$workDir) { $workDir = ($hasFile) ? $confDir.DIRECTORY_SEPARATOR.'work': dirname(__FILE__).DIRECTORY_SEPARATOR.'work'; } if (!is_dir($workDir)) { $this->_setMessage('_loadConf(): Not exists work directory. '.$workDir); } if (!is_writable($workDir)) { $this->_setMessage('_loadConf(): Not writable attribute in work directory. '.$workDir); } $this->Conf['workDir'] = $workDir; $this->Conf['signature'] = (is_string($signature)) ? $signature: ''; $this->Conf['formKey'] = (is_string($formKey)) ? $formKey: null; $this->Conf['server'] = (is_string($server)) ? $server: null; $this->Conf['lifeTime'] = (!isset($lifeTime) || 0 >= $lifeTime) ? 3600: (int)$lifeTime; $this->Conf['waitingTime'] = (!isset($waitingTime) || !$waitingTime) ? 0: (int)$waitingTime; $this->Conf['allowHostsFile'] = (!isset($allowHostsFile) || !$allowHostsFile) ? 'allow.host.php': $allowHostsFile; $this->Conf['denyHostsFile'] = (!isset($denyHostsFile) || !$denyHostsFile) ? 'deny.host.php': $denyHostsFile; $this->Conf['denyWordsFile'] = (!isset($denyWordsFile) || !$denyWordsFile) ? 'deny.word.php': $denyWordsFile; $this->Conf['sessSuffix'] = (!isset($sessSuffix) || !$sessSuffix) ? '.sess': $sessSuffix; $this->Conf['cacheSuffix'] = (!isset($cacheSuffix) || !$cacheSuffix) ? '.cache': $cacheSuffix; $this->Conf['denyLogging'] = (!isset($denyLogging) || !$denyLogging) ? false: true; $this->Conf['denyLoggingAddParam'] = (!isset($denyLoggingAddParam) || !$denyLoggingAddParam) ? 3: (int)$denyLoggingAddParam; $this->Conf['denyLogFile'] = (!isset($denyLogFile) || !$denyLogFile) ? 'deny.log': $denyLogFile; $this->Conf['logEncoding'] = (!isset($logEncoding) || !$logEncoding) ? null: $logEncoding; $this->Conf['errorLogging'] = (!isset($errorLogging) || !$errorLogging) ? false: true; $this->Conf['errorLogFile'] = (!isset($errorLogFile) || !$errorLogFile) ? 'error.log': $errorLogFile; $this->Conf['cookieValidate'] = (!isset($cookieValidate) || !$cookieValidate) ? false: true; $this->Conf['cookieName'] = (!isset($cookieName) || !$cookieName) ? 'spamp': $cookieName; $this->Conf['cookieLifeTime'] = (!isset($cookieLifeTime) || !$cookieLifeTime) ? 0: (int)$cookieLifeTime; $this->Conf['cookiePath'] = (!isset($cookiePath) || !$cookiePath || '/' !== $cookiePath{0}) ? '/': $cookiePath; $this->Conf['cookieDomain'] = (!isset($cookieDomain) || !$cookieDomain) ? $_SERVER['HTTP_HOST']: $cookieDomain; return true; } /** * Ganarate Session ID * * @access protected * @param string $formKey default $conf['formKey'] or $_SERVER['HTTP_REFERER'] * @param string $ip default $_SERVER['REMOTE_ADDR'] * @param string $ua default $_SERVER['HTTP_USER_AGENT'] * @return string */ function _ganarateSessId ( $formKey = null, $ip = null, $ua = null, $server = null ) { if (null === $formKey) { if (null === $this->Conf['formKey']) { $formKey = (isset($_SERVER['HTTP_REFERER'])) ? $_SERVER['HTTP_REFERER']: ''; } else { $formKey = $this->Conf['formKey']; } } if (null === $ip) { $ip = $_SERVER['REMOTE_ADDR']; } if (null === $ua) { $ua = (isset($_SERVER['HTTP_USER_AGENT'])) ? $_SERVER['HTTP_USER_AGENT']: ''; } if (null === $server) { if (null === $this->Conf['server']) { $server = (isset($_SERVER['HTTP_HOST'])) ? $_SERVER['HTTP_HOST']: ''; } else { $server = $this->Conf['server']; } } return md5($this->Conf['signature'].'/'.$formKey.'/'.$ip.'/'.$ua.'/'.$server); } /** * Generate Session filename * * @access protected * @param string $formKey default $conf['formKey'] or $_SERVER['HTTP_REFERER'] * @param string $ip default $_SERVER['REMOTE_ADDR'] * @param string $ua default $_SERVER['HTTP_USER_AGENT'] * @return string :filename */ function _generateSessFile ( $formKey = null, $ip = null, $ua = null ) { $sessId = $this->_ganarateSessId($formKey, $ip, $ua); return $this->Conf['workDir'].DIRECTORY_SEPARATOR.$sessId.$this->Conf['sessSuffix']; } /** * Generate Cookie Param * * @access public * @param int $sec timestamp default time() * @param string $host IP address default $_SERVER['REMOTE_ADDR'] * @param string $agent User-Agent default $_SERVER['HTTP_USER_AGENT'] * @return string */ function _generateCookieParam ( $sec = null, $host = null, $agent = null ) { if (null === $time) { $time = time(); } if (null === $host) { $host = $_SERVER['REMOTE_ADDR']; } if (null === $agent) { $agent = (isset($_SERVER['HTTP_USER_AGENT'])) ? $_SERVER['HTTP_USER_AGENT']: ''; } $param = $time. '-'. $host. '-'. md5($agent); return $param; } /** * Load Data file * * @access protected * @param string $filename AllowHosts or DanyHosts files * @return array */ function _loadData ( $filename ) { $cacheTime = $this->_hasCache($filename); if ($cacheTime && $cacheTime >= filemtime($filename)) { return $this->_loadCache($filename); } if (!is_file($filename) || !is_readable($filename)) { return false; } $lines = file($filename); if (!is_array($lines) || !isset($lines[0])) { return array(); } $data = array(); foreach ($lines as $line) { $line = trim($line); if ('' === $line || '#' === $line{0}) { continue; } $data[] = $line; } $this->_saveCache($filename, $data); return $data; } /** * Has Cache Data * * @access protected * @param string $filename * @param int: timestamp */ function _hasCache ( $filename ) { $cacheFile = $this->Conf['workDir'].DIRECTORY_SEPARATOR.md5($filename).$this->Conf['cacheSuffix']; if (!is_file($cacheFile) || !is_readable($cacheFile)) { return false; } return filemtime($cacheFile); } /** * Load Cache Data * * @access protected * @param string $filename * @return array */ function _loadCache ( $filename ) { $cacheFile = $this->Conf['workDir'].DIRECTORY_SEPARATOR.md5($filename).$this->Conf['cacheSuffix']; if (!is_file($cacheFile) || !is_readable($cacheFile)) { return false; } return unserialize(trim(file_get_contents($cacheFile))); } /** * Save Cache Data * * @access protected * @param string $filename * @param mixed $params * @return boolean */ function _saveCache ( $filename, $param ) { $cacheFile = $this->Conf['workDir'].DIRECTORY_SEPARATOR.md5($filename).$this->Conf['cacheSuffix']; if (!is_writable(dirname($cacheFile))) { $this->_setMessage('_saveCache(): Not writable attribute in work directory. '.dirname($cacheFile)); return false; } $fp = fopen($cacheFile, 'wb'); if (!is_resource($fp)) { $this->_setMessage("_saveCache(): Don't open file. ".$cacheFile); return false; } $len = fwrite($fp, serialize($param)); fclose($fp); return (false === $len) ? false: true; } /** * Set message * * @access protected * @param string $message Error messege * @param int $level * @return void */ function _setMessage ( $message = '', $level = 0 ) { $this->__Messages[] = '[Spamp] '.$message; if (isset($this->Conf['errorLogging']) && $this->Conf['errorLogging'] && isset($this->Conf['errorLogFile']) && $this->Conf['errorLogFile'] && isset($this->Conf['workDir']) && is_writable($this->Conf['workDir']) ) { error_log( date('Y-m-d H:i:s', time()).' : '.$message."\n", 3, $this->Conf['workDir'].DIRECTORY_SEPARATOR.$this->Conf['$errorLogFile'] ); } if (0 < $level) { trigger_error('[Spamp] '.$message, E_USER_WARNING); } } /** * Convert Encoding for Array * * @access protected * @param mix $params Strings or Array * @param string $outEnc output encoding * @param string $inEnc input encoding * @return mix */ function _convertEncoding ( $params, $outEnc, $inEnc ) { if (is_string($params)) { $params = mb_convert_encoding($params, $outEnc, $inEnc); } elseif (is_array($params) && count($params)) { foreach ($params as $key => $param) { if (is_string($param)) { $params[$key] = mb_convert_encoding($param, $outEnc, $inEnc); } elseif (is_array($param)) { $params[$key] = $this->_convertEncoding($param, $outEnc, $inEnc); } } } return $params; } /** * Serialize Encode for Arrey * * @access protected * @param mix $params Strings or Array * @return mix */ function _serializeEncode ( $params ) { if (is_string($params)) { $params = preg_replace('/[\x00-\x1f\x7f]/', ' ', $params); } elseif (is_array($params) && count($params)) { foreach ($params as $key => $param) { if (is_string($param)) { $params[$key] = preg_replace('/[\x00-\x1f\x7f]/', ' ', $param); } elseif (is_array($param)) { $params[$key] = $this->_serializeEncode($param); } } } return $params; } } /* end of spamp class */