* @copyright Yuji Iwashita * @version $Id: spamp.php,v 1.1 2010/03/03 15:07:23 YujiIwashita Exp YujiIwashita $ */ class spamp { /** * Configration params * * @access private * @var array */ var $Conf = array(); /** * Error Messages * * @access private * @var array */ var $Messages = array(); /** * 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(); } /* next job // Cookie validation On if ($this->Conf['cookieValidate']) $this->sendCookie(); */ // Save session $this->saveSession($formKey, null, null, $server); 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 boolean */ function isSpam ( $formKey = null, $ip = null, $ua = null, $server = null ) { $pass = false; // session if ($this->hasSession($formKey, $ip, $ua, $server)) { $pass = true; } // host if (null === $ip) { $ip = $_SERVER['REMOTE_ADDR']; } if (false === $pass) { $pass = $this->isAllowHost($ip); } elseif ($this->isDenyHost($ip)) { return false; } // words if (true === $pass && 'POST' === $_SERVER['REQUEST_METHOD'] && $this->isDenyWord(serialize($_POST)) ) { return false; } return $pass; } /** * 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->_getSessionFile($formKey, $ip, $ua, $server); $fp = fopen($sessFile, 'w'); fclose($fp); // Remove timeout session files $files = glob($this->Conf['workDir'].DIRECTORY_SEPARATOR.'*'.$this->Conf['sessSuffix']); if (is_array($files) && isset($files[0])) { $limit = time() - $this->Conf['lifeTime']; foreach ($files as $file) { if (filemtime($file) < $limit) @unlink($file); } } return true; } /** * 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->_getSessionFile($formKey, $ip, $ua, $server); $sess = false; // has session file if (is_file($sessFile)) { if (filemtime($sessFile) > (time() - $this->Conf['lifeTime'])) { $sess = true; } elseif (is_writable($sessFile)) { @unlink($sessFile); } } /* next job // Cookie Validate On if ($this->Conf['cookieValidate']) { $sess = $this->hasCookie() ; } else { $sess = false; } */ return $sess; } /** * Remove Session files * * @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->_getSessionFile($formKey, $ip, $ua, $server); if (!is_file($sessFile)) return true; return @unlink($sessFile); } /** * Get Request params * * @access public * @param boolean $addHttpParams false: default, true: add $_SERVER['HTTP_*'] params * @return array */ function getRequestParams ( $addHttpParams = false ) { $time = explode(' ', microtime()); $params = array(); $params['sec'] = (int)$time[1]; $params['msec'] = (float)$time[0]; $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['host'] = $_SERVER['REMOTE_ADDR']; $params['user'] = (isset($_SERVER['REMOTE_USER'])) ? $_SERVER['REMOTE_USER']: ''; $params['agent'] = (isset($_SERVER['HTTP_USER_AGENT'])) ? $_SERVER['HTTP_USER_AGENT']: ''; if ($addHttpParams) { $http = array(); foreach ($_SERVER as $key => $val) { if ('H' !== $key{0} || 'HTTP_' !== substr($key, 0, 5) || 'HTTP_HOST' === $key || 'HTTP_USER_AGENT' === $key) { continue; } $http[$key] = $val; } $params['http'] = $http; } return $params; } /** * 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 keyword? * * @access public * @param string $word * @return boolean */ function isDenyWord ( $word ) { static $words = null; if (null === $hosts) { $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, $words)) { 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; } /** * Has Cookie * * @access public * @return boolean */ function hasCookie () { if ( !isset($_COOKIE[$this->Conf['cookieName']]) || false === strpos($_COOKIE[$this->Conf['cookieName']], '/') ) { return false; } $cookie = explode('/', $_COOKIE[$this->Conf['cookieName']]); $uaId = (isset($_SERVER['HTTP_USER_AGENT'])) ? md5($this->Conf['signature'].'/'.$_SERVER['HTTP_USER_AGENT']): md5($this->Conf['signature'].'/'); if ($_SERVER['REMOTE_ADDR'] !== $cookie[0] || $uaId !== $cookie[1]) { return false; } return true; } /** * Send Cookie * * @access public * @return boolean */ function sendCookie () { $param = (isset($_SERVER['HTTP_USER_AGENT'])) ? $_SERVER['REMOTE_ADDR'].'/'.md5($this->Conf['signature'].'/'.$_SERVER['HTTP_USER_AGENT']): $_SERVER['REMOTE_ADDR'].'/'.md5($this->Conf['signature'].'/'); return setcookie( $this->Conf['cookieName'], $param, 0, $this->Conf['cookiePath'], $this->Conf['cookieDomain'] ); } /** * Save Deny log * * @access public * @param string $filename Log filename * @return boolean */ function saveDenylog ( $filename = 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 (!is_writable(dirname($filename))) { $this->_setMessage('saveDenylog(): Not writable attribute in work directory. '.dirname($filename)); return false; } return error_log(serialize($this->getRequestParams())."\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 messages * * @access public * @return array */ function getMessage () { return $this->Messages; } /** * 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['allowHostsFile'] = (!isset($allowHostsFile) || !$allowHostsFile) ? '': $allowHostsFile; $this->Conf['denyHostsFile'] = (!isset($denyHostsFile) || !$denyHostsFile) ? '': $denyHostsFile; $this->Conf['denyWordsFile'] = (!isset($denyHostsFile) || !$denyHostsFile) ? '': $denyWordsFile; $this->Conf['sessSuffix'] = (!isset($sessSuffix) || !$sessSuffix) ? '.sess.log': $sessSuffix; $this->Conf['cacheSuffix'] = (!isset($cacheSuffix) || !$cacheSuffix) ? '.cache': $cacheSuffix; $this->Conf['denyLogFile'] = (!isset($denyLogFile) || !$denyLogFile) ? 'deny.log': $denyLogFile; $this->Conf['errorLogging'] = (!isset($errorLogging) || !$errorLogFile) ? false: true; $this->Conf['errorLogFile'] = (!isset($errorLogFile) || !$errorLogFile) ? 'error.log': $errorLogFile; /* next job $this->Conf['cookieValidate'] = (!isset($cookieValidate) || !$cookieValidate) ? false: true; $this->Conf['cookieName'] = (!isset($cookieName) || !$cookieName) ? 'spamps': $cookieName; $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 _ganarateId ( $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); } /** * Get 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 _getSessionFile ( $formKey = null, $ip = null, $ua = null ) { $sessId = $this->_ganarateId($formKey, $ip, $ua); return $this->Conf['workDir'].DIRECTORY_SEPARATOR.$sessId.$this->Conf['sessSuffix']; } /** * 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; } /** * Put message * * @access protected * @param string $message Error messege * @param int $level * @return void */ function _setMessage ( $message = '', $level = 0 ) { $this->Messages[] = '[Spamp] '.$message; if ($this->Conf['errorLogging'] && $this->Conf['errorLogFile'] && 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); } } } /* end of spamp class */