diff --git a/_sakura/components/Configuration.php b/_sakura/components/Configuration.php index 3430917..aa3ff73 100644 --- a/_sakura/components/Configuration.php +++ b/_sakura/components/Configuration.php @@ -6,39 +6,39 @@ namespace Flashii; class Configuration { - + public static $_LCNF; public static $_DCNF; - + // Constructor public static function init($local) { - + // Store $local in $_LCNF if(is_array($local)) self::$_LCNF = $local; else die('

Failed to initialise configuration.

'); - + } - + // Initialise Database configuration values. - // Different from __construct as that is called before the database connection is initially + // Different from init as that is called before the database connection is initially // established public static function initDB() { - + $_DATA = Database::fetch('config', true); $_DBCN = array(); - + foreach($_DATA as $_CONF) $_DBCN[$_CONF[0]] = $_CONF[1]; - + self::$_DCNF = $_DBCN; - + } - + // Get values from the configuration on the file system public static function getLocalConfig($key, $subkey = null) { - + if(array_key_exists($key, self::$_LCNF)) { if($subkey) return self::$_LCNF[$key][$subkey]; @@ -46,31 +46,30 @@ class Configuration { return self::$_LCNF[$key]; } else return null; - + } // Dynamically set local configuration values, does not update the configuration file public static function setLocalConfig($key, $subkey, $value) { - + if($subkey) { - if(!isset(self::$_LCNF[$key])) { + if(!isset(self::$_LCNF[$key])) self::$_LCNF[$key] = array(); - } self::$_LCNF[$key][$subkey] = $value; } else { self::$_LCNF[$key] = $value; } - + } - + // Get values from the configuration in the database public static function getConfig($key) { - + if(array_key_exists($key, self::$_DCNF)) return self::$_DCNF[$key]; else return null; - + } - + } diff --git a/_sakura/components/Hashing.php b/_sakura/components/Hashing.php index 37cce25..076e635 100644 --- a/_sakura/components/Hashing.php +++ b/_sakura/components/Hashing.php @@ -32,13 +32,13 @@ namespace Flashii; class Hashing { - + // These variables can be changed without break the existing hashes private static $_PBKDF2_HASH_ALGORITHM = 'sha256'; private static $_PBKDF2_ITERATIONS = 1000; private static $_PBKDF2_SALT_BYTES = 24; private static $_PBKDF2_HASH_BYTES = 24; - + // Changing these will break it though private static $_HASH_ALGORITHM_INDEX = 0; private static $_HASH_ITERATION_INDEX = 1; @@ -48,13 +48,14 @@ class Hashing { // Returns an array formatted like: [algorithm, iterations, salt, hash] public static function create_hash($pass) { + $salt = base64_encode( \mcrypt_create_iv( self::$_PBKDF2_SALT_BYTES, MCRYPT_DEV_URANDOM ) ); - + $hash = base64_encode( self::pbkdf2( self::$_PBKDF2_HASH_ALGORITHM, @@ -65,24 +66,26 @@ class Hashing { true ) ); - + $passwordData = array( self::$_PBKDF2_HASH_ALGORITHM, self::$_PBKDF2_ITERATIONS, $salt, $hash ); - + return $passwordData; + } - + // Validates hashed password public static function validate_password($password, $params) { + if(count($params) < self::$_HASH_SECTIONS); return false; - + $pbkdf2 = base64_decode($params[self::$_HASH_PBKDF2_INDEX]); - + $validate = self::slow_equals( $pbkdf2, self::pbkdf2( @@ -94,21 +97,23 @@ class Hashing { true ) ); - + return $validate; + } - + // Compares two strings $a and $b in length-constant time. public static function slow_equals($a, $b) { + $diff = strlen($a) ^ strlen($b); - - for($i = 0; $i < strlen($a) && $i < strlen($b); $i++) { + + for($i = 0; $i < strlen($a) && $i < strlen($b); $i++) $diff |= ord($a[$i]) ^ ord($b[$i]); - } - + return $diff === 0; + } - + /* * PBKDF2 key derivation function as defined by RSA's PKCS #5: https://www.ietf.org/rfc/rfc2898.txt * $algorithm - The hash algorithm to use. Recommended: SHA256 @@ -124,16 +129,17 @@ class Hashing { * This implementation of PBKDF2 was originally created by https://defuse.ca * With improvements by http://www.variations-of-shadow.com */ - + private static function pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false) { + $algorithm = strtolower($algorithm); - + if(!in_array($algorithm, hash_algos(), true)) trigger_error('PBKDF2 ERROR: Invalid hash algorithm.', E_USER_ERROR); - + if($count <= 0 || $key_length <= 0) trigger_error('PBKDF2 ERROR: Invalid parameters.', E_USER_ERROR); - + if(function_exists('hash_pbkdf2')) { // The output length is in NIBBLES (4-bits) if $raw_output is false! if($raw_output) @@ -141,31 +147,32 @@ class Hashing { return hash_pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output); } - + $hash_length = strlen(hash($algorithm, '', true)); $block_count = ceil($key_length / $hash_length); - + $output = ''; - + for($i = 1; $i <= $block_count; $i++) { // $i encoded as 4 bytes, big endian. $last = $salt . pack('N', $i); - + // First iteration $last = $xorsum = hash_hmac($algorithm, $last, $password, true); - + // Perform the other $count - 1 interations - for($j = 1; $j < $count; $j++) { + for($j = 1; $j < $count; $j++) $xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true)); - } - + $output .= $xorsum; - + if($raw_output) return substr($output, 0, $key_length); else return bin2hex(substr($output, 0, $key_length)); + } + } - + } diff --git a/_sakura/components/Main.php b/_sakura/components/Main.php index e4d1a95..5ae12ec 100644 --- a/_sakura/components/Main.php +++ b/_sakura/components/Main.php @@ -5,121 +5,138 @@ namespace Flashii; -class Flashii { +class Main { public $_TPL; public $_MD; - + // Constructor - function __construct($config) { - + public static function init($config) { + // Stop the execution if the PHP Version is older than 5.4.0 if(version_compare(phpversion(), '5.4.0', '<')) die('

Upgrade your PHP Version to at least PHP 5.4!

'); - + // Start session if(session_status() != PHP_SESSION_ACTIVE) session_start(); - + // Configuration Management and local configuration Configuration::init($config); - + // Database Database::init(); - + // "Dynamic" Configuration Configuration::initDB(); - + // Templating engine - $this->initTwig(); - + self::initTwig(); + // Markdown Parser - $this->initParsedown(); - + self::initParsedown(); + } - + // Alias for Configuration::getConfig(), only exists because I'm lazy public static function getConfig($key) { - + return Configuration::getConfig($key); - + } - + // Initialise Twig - private function initTwig($templateName = null, $templatesFolder = null) { - - // Assign default values set in the configuration if $templateName and $templatesFolder are null - $templateName = is_null($templateName) ? Configuration::getLocalConfig('etc', 'design') : $templateName; - $templatesFolder = is_null($templatesFolder) ? Configuration::getLocalConfig('etc', 'templatesPath') : $templatesFolder; - + private static function initTwig() { + // Initialise Twig Filesystem Loader - $twigLoader = new \Twig_Loader_Filesystem($templatesFolder . $templateName); + $twigLoader = new \Twig_Loader_Filesystem(Configuration::getLocalConfig('etc', 'templatesPath') .'/'. Configuration::getLocalConfig('etc', 'design')); // And now actually initialise the templating engine - $this->_TPL = new \Twig_Environment($twigLoader, array( - // 'cache' => ROOT_DIRECTORY. $satoko['cacheFolder'] + self::$_TPL = new \Twig_Environment($twigLoader, array( + + // 'cache' => SATOKO_ROOT_DIRECTORY. self::getConfig('path', 'cache') // Set cache directory + )); - + // Load String template loader - $this->_TPL->addExtension(new \Twig_Extension_StringLoader()); - + self::$_TPL->addExtension(new \Twig_Extension_StringLoader()); + } - + // Initialise Parsedown private function initParsedown() { - - $this->_MD = new \Parsedown(); - + + self::$_MD = new \Parsedown(); + } - + + // Verify ReCAPTCHA + public static function verifyCaptcha($response) { + + // Attempt to get the response + $resp = @file_get_contents('https://www.google.com/recaptcha/api/siteverify?secret='. self::getConfig('recaptcha_private') .'&response='. $response); + + // In the highly unlikely case that it failed to get anything forge a false + if(!$resp) + return array('success' => false, 'error-codes' => array('Could not connect to the ReCAPTCHA server.')); + + // Decode the response JSON from the servers + $resp = json_decode($resp, true); + + // Return shit + return $resp; + + } + // Error Handler public static function ErrorHandler($errno, $errstr, $errfile, $errline) { - + // Set some variables to work with including A HUGE fallback hackjob for the templates folder $errstr = str_replace(self::getConfig('etc', 'localPath'), '', $errstr); $errfile = str_replace(self::getConfig('etc', 'localPath'), '', $errfile); $templates = (self::getConfig('etc', 'templatesPath') !== null && !empty(self::getConfig('etc', 'templatesPath'))) ? self::getConfig('etc', 'templatesPath') : '/var/www/flashii.net/_sakuya/templates/'; - + switch ($errno) { + case E_ERROR: case E_USER_ERROR: $error = 'FATAL ERROR: ' . $errstr . ' on line ' . $errline . ' in ' . $errfile; - break; + break; case E_WARNING: case E_USER_WARNING: $error = 'WARNING: ' . $errstr . ' on line ' . $errline . ' in ' . $errfile; - break; + break; case E_NOTICE: case E_USER_NOTICE: $error = 'NOTICE: ' . $errstr . ' on line ' . $errline . ' in ' . $errfile; - break; + break; default: $error = 'Unknown error type [' . $errno . ']: ' . $errstr . ' on line ' . $errline . ' in ' . $errfile; - break; + } - + // Use file_get_contents instead of Twig in case the problem is related to twig $errorPage = file_get_contents($templates. 'errorPage.tpl'); - + // str_replace {{ error }} on the error page with the error data $error = str_replace('{{ error }}', $error, $errorPage); - + // Truncate all previous outputs ob_clean(); // Die and display error message die($error); - + } - + // Legacy password hashing to be able to validate passwords from users on the old backend. public static function legacyPasswordHash($data) { - + return hash('sha512', strrev(hash('sha512', $data))); - + } - -} \ No newline at end of file + +} diff --git a/_sakura/components/database/mysql.php b/_sakura/components/database/mysql.php index e8d2eef..f1d7110 100644 --- a/_sakura/components/database/mysql.php +++ b/_sakura/components/database/mysql.php @@ -21,29 +21,29 @@ class Database { if(!extension_loaded('PDO')) { // Return error and die - die('

PDO extension not loaded.

'); + trigger_error('SQL Driver: PDO extension not loaded.', E_USER_ERROR); } // Initialise connection self::initConnect( ( - Configuration::getLocalConfig('db', 'unixsocket') ? + Board::getConfig('db', 'unixsocket') ? self::prepareSock( - Configuration::getLocalConfig('db', 'host'), - Configuration::getLocalConfig('db', 'database') + Board::getConfig('db', 'host'), + Board::getConfig('db', 'database') ) : self::prepareHost( - Configuration::getLocalConfig('db', 'host'), - Configuration::getLocalConfig('db', 'database'), + Board::getConfig('db', 'host'), + Board::getConfig('db', 'database'), ( - Configuration::getLocalConfig('db', 'port') !== null ? - Configuration::getLocalConfig('db', 'port') : + Board::getConfig('db', 'port') !== null ? + Board::getConfig('db', 'port') : 3306 ) ) ), - Configuration::getLocalConfig('db', 'username'), - Configuration::getLocalConfig('db', 'password') + Board::getConfig('db', 'username'), + Board::getConfig('db', 'password') ); } @@ -74,7 +74,7 @@ class Database { self::$sql = new PDO($DSN, $dbUname, $dbPword); } catch(PDOException $e) { // Catch connection errors - die("

" . $e->getMessage() . "

"); + trigger_error('SQL Driver: '. $e->getMessage(), E_USER_ERROR); } return true; @@ -82,30 +82,74 @@ class Database { } // Fetch array from database - public static function fetch($table, $fetchAll = true, $data = null) { - + public static function fetch($table, $fetchAll = true, $data = null, $order = null, $limit = null, $group = null, $distinct = false, $column = '*') { + // Begin preparation of the statement - $prepare = 'SELECT * FROM `' . Configuration::getLocalConfig('db', 'prefix') . $table . '`'; - + $prepare = 'SELECT '. ($distinct ? 'DISTINCT ' : '') . $column .' FROM `' . Board::getConfig('db', 'prefix') . $table . '`'; + // If $data is set and is an array continue if(is_array($data)) { - + $prepare .= ' WHERE'; - + foreach($data as $key => $value) { - $prepare .= ' `'. $key .'` '. $value[1] .' :'. $key .($key == key(array_slice($data, -1, 1, true)) ? ';' : ' AND'); + $prepare .= ' `'. $key .'` '. $value[1] .' :'. $key . ($key == key(array_slice($data, -1, 1, true)) ? '' : ' AND'); + + // Unset variables to be safe + unset($key); + unset($value); } - + + } + + // If $order is set and is an array continue + if(is_array($order)) { + + $prepare .= ' ORDER BY `'. $order[0] .'`'. (!empty($order[1]) && $order[1] ? ' DESC' : ''); + + } + + // If $group is set and is an array continue + if(is_array($group)) { + + $prepare .= ' GROUP BY'; + + foreach($group as $key => $value) { + $prepare .= ' `'. $value .'`'. ($key == key(array_slice($group, -1, 1, true)) ? '' : ','); + + // Unset variables to be safe + unset($key); + unset($value); + } + } + // If $limit is set and is an array continue + if(is_array($limit)) { + + $prepare .= ' LIMIT'; + + foreach($limit as $key => $value) { + $prepare .= ' '. $value . ($key == key(array_slice($limit, -1, 1, true)) ? '' : ','); + + // Unset variables to be safe + unset($key); + unset($value); + } + + } + + // Add the finishing semicolon + $prepare .= ';'; + // Actually prepare the preration $query = self::$sql->prepare($prepare); - + // Bind those parameters if $data is an array that is if(is_array($data)) { foreach($data as $key => $value) { $query->bindParam(':'. $key, $value[0]); - + // Unset variables to be safe unset($key); unset($value); @@ -114,23 +158,24 @@ class Database { // Execute the prepared statements with parameters bound $query->execute(); - + // Do fetch or fetchAll if($fetchAll) $result = $query->fetchAll(PDO::FETCH_BOTH); else $result = $query->fetch(PDO::FETCH_BOTH); - + + // And return the output return $result; - + } // Insert data to database public static function insert($table, $data) { // Begin preparation of the statement - $prepare = 'INSERT INTO `' . Configuration::getLocalConfig('db', 'prefix') . $table . '` '; + $prepare = 'INSERT INTO `' . Board::getConfig('db', 'prefix') . $table . '` '; // Run the foreach statement twice for (`stuff`) VALUES (:stuff) for($i = 0; $i < 2; $i++) { @@ -170,7 +215,7 @@ class Database { public static function update($table, $data) { // Begin preparation of the statement - $prepare = 'UPDATE `' . Configuration::getLocalConfig('db', 'prefix') . $table . '`'; + $prepare = 'UPDATE `' . Board::getConfig('db', 'prefix') . $table . '`'; // Run a foreach on $data and complete the statement foreach($data as $key => $values) { diff --git a/_sakura/sakura.php b/_sakura/sakura.php index f3a8dcd..1244ebb 100644 --- a/_sakura/sakura.php +++ b/_sakura/sakura.php @@ -4,11 +4,14 @@ * (c)Flashwave/Flashii Media 2013-2015 */ +// Declare Namespace +namespace Flashii; + // Start output buffering ob_start(); // Define Sakura version -define('SAKURA_VERSION', '20150221'); +define('SAKURA_VERSION', '20150321'); // Define Sakura Path define('ROOT_DIRECTORY', str_replace('_sakura', '', dirname(__FILE__))); @@ -39,7 +42,7 @@ else set_error_handler(array('Flashii\Flashii', 'ErrorHandler')); // Initialise Flashii Class -$flashii = new Flashii\Flashii($fiiConf); +Main::init($fiiConf); // Set base page rendering data $renderData = array( diff --git a/main/404.php b/main/404.php index e58cfb7..1d0e5d6 100644 --- a/main/404.php +++ b/main/404.php @@ -3,8 +3,11 @@ * Flashii.net Main Index */ +// Declare Namespace +namespace Flashii; + // Include components require_once('/var/www/flashii.net/_sakura/sakura.php'); // Print page contents -print $flashii->_TPL->render('errors/http404.tpl', $renderData); +print Main::$_TPL->render('errors/http404.tpl', $renderData); diff --git a/main/index.php b/main/index.php index 8e6e6a6..b403e5c 100644 --- a/main/index.php +++ b/main/index.php @@ -3,6 +3,9 @@ * Flashii.net Main Index */ +// Declare Namespace +namespace Flashii; + // Include components require_once('/var/www/flashii.net/_sakura/sakura.php'); @@ -12,4 +15,4 @@ $renderData['page'] = [ ]; // Print page contents -print $flashii->_TPL->render('main/index.tpl', $renderData); +print Main::$_TPL->render('main/index.tpl', $renderData);