diff --git a/README.md b/README.md
index 3207efb..f3abdf6 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,12 @@
HackThis
========
-[](http://stillmaintained.com/HackThis/hackthis.co.uk)
-
-This repository contains all code for http://www.hackthis.co.uk.
+This repository contains the majority of the code for security challenge site http://www.hackthis.co.uk.
## Installation Instructions
You can set up the site on your own local machine and help the development.
The specific instructions differ depending on your operating system.
-Following are instructions for Windows and Ubuntu. In the end you can find a general description of the process for any other OS.
+Following are instructions for Windows and Ubuntu. In the end, you can find a general description of the process for any other OS.
### Ubuntu Installation
@@ -17,7 +15,7 @@ Following are instructions for Windows and Ubuntu. In the end you can find a gen
git clone http://github.com/HackThis/hackthis.co.uk
```
-2. Run the the installation script by using the following command
+2. Run the installation script by using the following command
```
sudo ./install_hackthis_ubuntu.sh
```
@@ -55,7 +53,7 @@ Following are instructions for Windows and Ubuntu. In the end you can find a gen
Follow the instructions of the script until it's done.
If an error occurs, the script will let you know what to do.
Fix what's wrong and re-run the script until it ends successfully.
-6. Open your broswer and navigate to
+6. Open your browser and navigate to
```
http://localhost/hackthis/?generate
@@ -115,13 +113,13 @@ Following are instructions for Windows and Ubuntu. In the end you can find a gen
nano html/.htaccess
```
-8. Create and configure config file. Change path to the path of your hackthis.co.uk directory, without trailing slash. Next set MySQL credentials to match those used in setup, database is `hackthis`. Facebook, twitter and lastfm API keys are not required but some features will not work correctly.
+8. Create and configure config file. Change path to the path of your hackthis.co.uk directory, without trailing slash. Next set MySQL credentials to match those used in setup, database is `hackthis`. Facebook, Twitter and Lastfm API keys are not required but some features will not work correctly.
```
cp files/example.config.php files/config.php
nano files/config.php
```
-9. Create and set new folder privilages
+9. Create and set new folder privileges
```
mkdir html/files/css/min
mkdir html/files/css/min/light
diff --git a/files/cache/version_history.md b/files/cache/version_history.md
old mode 100644
new mode 100755
index c5ad416..5675978
--- a/files/cache/version_history.md
+++ b/files/cache/version_history.md
@@ -1,9 +1,125 @@
+## 09-07-2016
+* Fixed missing userbar image in user settings
+
+## 06-07-2016
+* Added Real level 7
+
+## 30-06-2016
+* Fixed Intermediate level 5
+* Added spoiler tag to WYSIWYG editor - [L3gand](/user/L3gand)
+
+## 16-06-2015
+* Disabled users from logging in with old password hashes, forcing password reset
+
+## 15-06-2015
+* Added online indicators to levels that rely on external services
+* Stricted forum checks added for newly registered users
+
+## 12-06-2015
+* Added two-factor authentication using Google Authenticator - [CygnusH33L](/user/CygnusH33L)
+
+## 07-06-2015
+* Spelling mistake in forum email - [MrCyph3r](/user/MrCyph3r)
+
+## 06-06-2015
+* Main authentication method switched to LDAP
+
+## 08-05-2015
+* Error in privacy document - [MrCyph3r](/user/MrCyph3r)
+* Spelling mistake in Terms - [Rex-Mundi](/user/Rex_Mundi)
+
+## 03-05-2015
+* Hide latest news article from homepage after 2 weeks
+
+## 01-05-2015
+* Changed user profile history links for forum posts to go to the correct page
+
+## 09-03-2015
+* Tweaked IRC stats to format numbers and dates
+
+## 15-02-2015
+* Added basic forum stats
+
+## 19-01-2015
+* Grammatical fix - [tl0tr](/user/tl0tr)
+
+## 21-11-2014
+* Allow transition from dropdown message creator to full view - [singleton](/user/singleton)
+
+## 25-09-2014
+* Fixed XSS on profile - [darkl33ch](/user/darkl33ch)
+
+## 20-09-2014
+* Added Crypt 9
+* Improved forum flagging
+
+## 11-09-2014
+* Notifications are automatically marked as read when viewing thread
+
+## 09-09-2014
+* Fixed profile level details
+* Fixed access to solutions forum sections
+* Fixed levels dropdown menu
+
+## 07-09-2014
+* Changed level layout
+
+## 24-08-2014
+* Switched emails to Mandrell
+
+## 22-08-2014
+* Fixed Basic+ forum links on level pages
+
+## 16-08-2014
+* Added privacy controls to show/hide users in online and scoreboard lists
+* Added website field to user profile
+* Converted more pages to be rendered by Twig
+
+## 05-08-2014
+* Added statuses to contact tickets
+
+## 04-08-2014
+* Trigger added to handle changes in medal rewards across all users
+
+## 30-07-2014
+* Added regex solutions to levels
+
+## 29-07-2014
+* Allow new threads to be created outside of leaf nodes
+* Fixed solutions showing up to non-authorized users
+
+## 28-07-2014
+* Basic+ Level 6 added
+
+## 26-07-2014
+* Auto-login added
+
+## 24-07-2014
+* Solved spacing in BBCode blocks
+* Fixed double spacing in BBCode code blocks - [DJDavid98](/user/djdavid98)
+* Changed styling of home forum widget, added section details and created date
+* Fixed missing breadcrumbs on forum thread list
+* Added restricted solution discussion forum sections
+
+## 23-07-2014
+* Added the year to short dates not for the current year - [DJDavid98](/user/djdavid98)
+
+## 21-07-2014
+* Crypt Level 8 added - [sabretooth](/user/sabretooth)
+
+## 19-07-2014
+* Fixed bug in account deletion
+
+## 02-07-2014
+* Karma controls are now still accessible when a post is hidden
+
## 29-06-2014
* Added min length and filtered special characters in AJAX search - [verath](/user/verath)
* Made WeChall user scores depend only on solved levels - [dloser](/user/dloser)
* Navbar fix when no levels added - [dloser](/user/dloser)
## 22-06-2014
+* Real Level 6 added
* Repaired friend removal from settings menu - [dloser](/user/dloser)
## 10-06-2014
@@ -13,7 +129,7 @@
* Added WeChall API pages
## 08-06-2014
-* Fixed gramatical error in privacy statement - [sabretooth](/user/sabretooth)
+* Fixed grammatical error in privacy statement - [sabretooth](/user/sabretooth)
## 09-05-2014
* Added H3 tag to BBCode
@@ -75,7 +191,7 @@
* W3C validation fixes - [DJDavid98](/user/djdavid98)
* Styling fix for invisible select elements - [DJDavid98](/user/djdavid98)
-## 23-02-2104
+## 23-02-2014
* Code added to handle plural articles on article contributors - [DJDavid98](/user/djdavid98)
* Footer grammar fix - [kamzhik](/user/kamzhik)
@@ -105,4 +221,11 @@
* Included a detailed list of changes for each new version
* Added slimdown, a markdown parser
* Added contributor medal
-* Uploaded wider background image to match new site width
+* Uploaded wider background image to match new site width
+
+
+
+# Vulnerability disclosures
+* [Pseudonym](/user/pseudonym) - 12/01/2013 - XSS, search results for forum title
+* [Pseudonym](/user/pseudonym) - 05/01/2013 - PM subject showing up over multiple lines in navigation using script comments
+* [Pseudonym](/user/pseudonym) - 05/01/2013 - Forum title, showing up in latest and feed
diff --git a/files/class.admin.php b/files/class.admin.php
index 52c5433..f8b2d91 100644
--- a/files/class.admin.php
+++ b/files/class.admin.php
@@ -4,20 +4,21 @@ class admin {
private $forum_reasons_posts = array('This post is not relevant to the thread. If you need help or want to post something that has not been discussed then please create a new thread. If you want to ask a user a specific question unrelated to the current topic please use the PM system.',
'This post is primarily an answer or has far more detail than is necessary to be helpful.',
'This post is primarily an advertisement with no disclosure. It is not useful or relevant, but promotional. If you are interested in advertising on our platform please contact us.',
- 'This post has severe formatting or content problems. Please be more considered when posting in future.',
- 'The communities first and only language is English. If you are feel you need to talk in another language please find another member who can speak that language and contact them directly via PM.',
- 'This post refers to a post that longer exists and is being removed just to tidy things up. Don\'t worry about this report.');
+ 'This post has severe formatting or content problems. Please be more cautious when posting in future.',
+ 'The communities first and only language is English. If you feel you need to talk in another language please find another member who can speak that language and contact them directly via PM.',
+ 'This post refers to a post that no longer exists and is being removed just to tidy things up. Don\'t worry about this report.');
private $forum_reasons_threads = array('This thread is not relevant to this site. If you want to ask a user a specific question unrelated to the site topic please use the PM system.',
'This thread is primarily an answer or has far more detail than is necessary to be helpful.',
'This thread is primarily an advertisement with no disclosure. It is not useful or relevant, but promotional. If you are interested in advertising on our platform please contact us.',
- 'This thread has severe formatting or content problems. Please be more considered when posting in future.',
- 'The communities first and only language is English. If you are feel you need to talk in another language please find another member who can speak that language and contact them directly via PM.',
+ 'This thread has severe formatting or content problems. Please be more cautious when posting in future.',
+ 'The communities first and only language is English. If you feel you need to talk in another language please find another member who can speak that language and contact them directly via PM.',
'This thread has been removed to tidy things up. Don\'t worry about this report.');
public function __construct($app) {
$this->app = $app;
}
+ /******* TICKETS *******/
public function getUnreadTickets() {
$sql = "SELECT `mod_contact`.*, COUNT(a.message_id) AS `replies` FROM `mod_contact`
LEFT JOIN `mod_contact` a
@@ -32,20 +33,17 @@ public function getUnreadTickets() {
return $count;
}
- public function getLatestForumFlags($limit = true) {
- $sql = "SELECT MAX(forum_posts_flags.time) AS `latest`, COUNT(forum_posts_flags.post_id) AS `flags`, forum_posts_flags.reason, users.username, forum_threads.thread_id, forum_threads.slug, forum_threads.title, forum_posts.post_id, forum_posts.body
- FROM forum_posts_flags
- INNER JOIN forum_posts
- ON forum_posts_flags.post_id = forum_posts.post_id
- INNER JOIN forum_threads
- ON forum_posts.thread_id = forum_threads.thread_id
- INNER JOIN users
- ON users.user_id = forum_posts.author
- WHERE forum_posts.deleted = 0 AND forum_threads.deleted = 0
- GROUP BY forum_posts_flags.post_id
- ORDER BY `flags` DESC, `latest` DESC";
- if ($limit) $sql .= " LIMIT 5";
+
+ /******* LOGS *******/
+ public function getModeratorLogs($limit = true) {
+ $sql = "SELECT `report_id`, `type`, `subject`, username, `time`
+ FROM mod_reports
+ INNER JOIN `users`
+ ON `users`.user_id = `mod_reports`.user_id
+ ORDER BY `report_id` DESC";
+ if ($limit) $sql .= " LIMIT 5";
+
$st = $this->app->db->prepare($sql);
$st->execute();
$result = $st->fetchAll();
@@ -53,6 +51,9 @@ public function getLatestForumFlags($limit = true) {
return $result;
}
+
+
+ /******* ARTICLES *******/
public function getLatestArticleSubmissions($limit = true) {
$sql = "SELECT articles_draft.article_id, articles_draft.title, articles_draft.time, articles_categories.title AS `category`, users.username
FROM articles_draft
@@ -71,10 +72,48 @@ public function getLatestArticleSubmissions($limit = true) {
return $result;
}
+ public function getLatestArticleComments() {
+ $sql = "SELECT users.username, articles.title, articles_comments.time, articles_comments.comment
+ FROM articles_comments
+ INNER JOIN users
+ ON users.user_id = articles_comments.user_id
+ INNER JOIN articles
+ ON articles.article_id = articles_comments.article_id
+ WHERE articles_comments.deleted IS NULL
+ ORDER BY `time` DESC
+ LIMIT 5";
+
+ $st = $this->app->db->prepare($sql);
+ $st->execute();
+ $result = $st->fetchAll();
+
+ return $result;
+ }
- // Forum
+ /******* FORUM *******/
+ public function getLatestForumFlags($limit = true) {
+ $sql = "SELECT MAX(forum_posts_flags.time) AS `latest`, COUNT(forum_posts_flags.post_id) AS `flags`, forum_posts_flags.reason, users.username, forum_threads.thread_id, forum_threads.slug, forum_threads.title, forum_posts.post_id, forum_posts.body
+ FROM forum_posts_flags
+ INNER JOIN forum_posts
+ ON forum_posts_flags.post_id = forum_posts.post_id
+ INNER JOIN forum_threads
+ ON forum_posts.thread_id = forum_threads.thread_id
+ INNER JOIN users
+ ON users.user_id = forum_posts.author
+ WHERE forum_posts.deleted = 0 AND forum_threads.deleted = 0 AND forum_posts_flags.response = 0
+ GROUP BY forum_posts_flags.post_id
+ ORDER BY `flags` DESC, `latest` DESC";
+ if ($limit) $sql .= " LIMIT 5";
+
+ $st = $this->app->db->prepare($sql);
+ $st->execute();
+ $result = $st->fetchAll();
+
+ return $result;
+ }
+
public function removeForumThread($thread_id, $reason, $extra) {
// Delete post
$deleted = $this->app->forum->deleteThread($thread_id);
@@ -147,5 +186,47 @@ public function removeForumPost($post_id, $reason, $extra) {
return true;
}
+
+
+
+ /******* USER MANAGEMENT *******/
+ public function getModerators() {
+ $query = "SELECT username, users.user_id AS `uid`, users_priv.*
+ FROM users_priv
+ INNER JOIN users
+ ON users.user_id = users_priv.user_id
+ WHERE users_priv.site_priv > 1 OR
+ users_priv.pm_priv > 1 OR
+ users_priv.forum_priv > 1 OR
+ users_priv.pub_priv > 1";
+
+ $st = $this->app->db->prepare($query);
+ $st->execute();
+ $result = $st->fetchAll();
+
+ return $result;
+ }
+
+ public function setModeratorPriv($user_id, $priv, $priv_value) {
+ // Check user has privilages
+ if ($this->app->user->site_priv < 2 || $user_id == '69') {
+ echo "No";
+ return;
+ }
+
+ if ($priv != 'site' && $priv != 'pm' && $priv != 'forum' && $priv != 'pub') {
+ return;
+ }
+
+ $priv = $priv.'_priv';
+
+ $this->app->db->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
+
+ $st = $this->app->db->prepare("INSERT INTO users_priv (`user_id`, ".$priv.") VALUES (:uid, :priv_value) ON DUPLICATE KEY UPDATE ".$priv."=:priv_value");
+ $status = $st->execute(array(':uid'=>$user_id, ':priv_value'=>$priv_value));
+
+ print_r($status);
+ }
+
}
-?>
\ No newline at end of file
+?>
diff --git a/files/class.api.php b/files/class.api.php
index 3c262c9..dee2691 100644
--- a/files/class.api.php
+++ b/files/class.api.php
@@ -1,6 +1,9 @@
privileges) && $this->app->user->loggedIn) {
- $this->privileges = "inherit";
+ // Check if user is banned from the site
+ if ($this->app->user->site_priv < 1) {
+ $this->respond(401);
+ }
+
+ // Check the CSRF is being used
+ if (!isset($_GET['ajax_csrf_token']) || $_GET['ajax_csrf_token'] != $this->app->user->csrf_basic) {
+ $this->respond(400);
+ }
+
+ $this->privileges = array();
+
+ array_push($this->privileges, 'user.profile');
+
+ if ($this->app->user->site_priv > 1) {
+ array_push($this->privileges, 'user.admin.*');
+ }
+ if ($this->app->user->forum_priv > 1 || $this->app->user->site_priv > 1) {
+ array_push($this->privileges, 'forum.admin.*');
+ }
}
if (!isset($this->privileges)) {
@@ -27,14 +49,184 @@ public function handleRequest($method, $data) {
$this->respond(400);
}
- switch ($method) {
- case 'user.profile': $this->user('profile'); break;
+ // Check privileges
+ $this->hasPrivilege($method);
+
+ $subject = explode('.', $method);
+ $method = substr($method, strlen($subject[0])+1);
+
+ switch ($subject[0]) {
+ case 'irc': $this->logIrc(); break;
+ case 'user': $this->user($method); break;
+ default: $this->respond(400);
+ }
+
+ // switch ($method) {
+ // case 'irc.log': $this->logIrc(); break;
+ // case 'user.login': $this->user('login'); break;
+ // case 'user.login.gauth': $this->user('login.gauth'); break;
+ // case 'user.register': $this->user('register'); break;
+ // case 'user.profile': $this->user('profile'); break;
+ // case 'user.admin.priv': $this->user('admin.priv'); break;
+ // case 'user.admin.priv': $this->user('admin.priv'); break;
+ // default: $this->respond(400);
+ // }
+ }
+
+
+ //=====================================================
+ // REQUEST HANDLERS
+ //=====================================================
+
+ //-----------------------------------------------------
+ // User
+ //-----------------------------------------------------
+ private function user($request) {
+ $response = new stdClass();
+
+ switch ($request) {
+ case 'login': $response = $this->userLogin(); break;
+ case 'login.gauth': $response = $this->userLoginGAuth(); break;
+ case 'register': $response = $this->userRegister(); break;
+ case 'profile': $response->profile = $this->userProfile(); break;
+
+ case 'admin.priv': $this->userAdminPriv(); break;
+ case 'admin.medal': $response->status = $this->userAdminMedal(); break;
+ default: $this->respond(400);
+ }
+
+ $this->respond(200, $response);
+ }
+
+ private function userProfile() {
+ $profile = new profile($_GET['user'], true);
+
+ unset($profile->email);
+
+ return $profile;
+ }
+
+ private function userLogin() {
+ $response = new stdClass();
+
+ $response->valid = $this->app->user->login($_POST['username'], $_POST['password']);
+
+ if ($response->valid) {
+ $this->app->user->get_details();
+ $response->uid = $this->app->user->uid;
+ $response->username = $this->app->user->username;
+ } else {
+ if ($this->app->user->g_auth) {
+ $response->error = "Google Authentication code required";
+ $response->g_auth = $this->app->user->g_auth;
+ } else {
+ $response->error = $this->app->user->login_error;
+ }
+ }
+
+
+ return $response;
+ }
+
+ private function userLoginGAuth() {
+ $response = new stdClass();
+
+ if(is_numeric($_POST['code']) && is_numeric($_POST['gauth'])) {
+ $response->valid = $this->app->user->googleAuth($_POST['code'], $_POST['gauth']);
+ } else {
+ $response->valid = false;
+ }
+
+ if ($response->valid) {
+ $this->app->user->get_details();
+ $response->uid = $this->app->user->uid;
+ $response->username = $this->app->user->username;
+ } else {
+ $response->error = $this->app->user->login_error;
+ }
+
+ return $response;
+ }
+
+ private function userRegister() {
+ $username = $_POST['username'];
+ $password = $_POST['password'];
+ $email = $_POST['email'];
+
+ $response = new stdClass();
+
+ $registration = $this->app->user->register($username, $password, $email);
+
+ if (is_numeric($registration)) {
+ $response->valid = true;
+
+ $this->app->user->get_details();
+ $response->uid = $this->app->user->uid;
+ $response->username = $this->app->user->username;
+ } else {
+ $response->valid = false;
+ $response->error = $registration;
+ }
+
+ return $response;
+ }
+
+ private function userAdminPriv() {
+ $user_id = $_POST['uid'];
+ $priv = $_POST['priv'];
+ $priv_value = $_POST['priv_value'];
+ $this->app->admin->setModeratorPriv($user_id, $priv, $priv_value);
+ }
+
+ private function userAdminMedal() {
+ $user_id = $_POST['uid'];
+ $medal = $_POST['medal'];
+ $medal_value = $_POST['medal_value'];
+
+ if ($medal == 'contributor' || $medal == 'helper') {
+ if ($medal_value == 1) {
+ return $this->app->user->awardMedal($medal, 4, $user_id);
+ } else {
+ return $this->app->user->removeMedal($medal, 4, $user_id);
+ }
}
- $this->respond(400);
+ return "error";
+ }
+
+
+ //-----------------------------------------------------
+ // IRC
+ //-----------------------------------------------------
+ private function logIrc() {
+ if (!isset($_POST['nick']) || !isset($_POST['chan']) || !isset($_POST['msg']))
+ throw new Exception('Missing data fields');
+
+ $_POST['msg'] = preg_replace('/\x01/', '', $_POST['msg']);
+
+ $st = $this->app->db->prepare('INSERT INTO irc_logs (`nick`, `channel`, `log`)
+ VALUES (:nick, :chan, :msg)');
+ $result = $st->execute(array(':nick' => $_POST['nick'], ':chan' => $_POST['chan'], ':msg' => $_POST['msg']));
+
+
+ // Calculate stats
+ $st = $this->app->db->prepare('INSERT INTO irc_stats (`nick`, `lines`, `words`, `chars`)
+ VALUES (:nick, :lines, :words, :chars)
+ ON DUPLICATE KEY UPDATE `lines`=`lines`+:lines, `words`=`words`+:words, `chars`=`chars`+:chars, `time`=NOW()');
+
+ $st->bindValue(':nick', $_POST['nick'], PDO::PARAM_INT);
+ $st->bindValue(':lines', 1, PDO::PARAM_INT);
+ $st->bindValue(':words', str_word_count($_POST['msg']), PDO::PARAM_INT);
+ $st->bindValue(':chars', strlen($_POST['msg']), PDO::PARAM_INT);
+ $result = $st->execute();
+
+ $this->respond(200);
}
- public function respond($status, $data=null) {
+ //=====================================================
+ // HELPER FUNCTIONS
+ //=====================================================
+ private function respond($status, $data=null) {
if (!$data) {
$data = new stdClass();
}
@@ -78,55 +270,24 @@ private function checkKey($key) {
}
- private function user($request) {
- $response = new stdClass();
+ private function hasPrivilege($privilege) {
+ // get subject
+ $subject = explode('.', $privilege);
- if ($request == 'profile') {
- $response->profile = new profile($_GET['user'], true);
+ if ($subject[1] == 'admin') {
+ $globalPrivilege = $subject[0] . '.admin.*';
+ } else {
+ $globalPrivilege = $subject[0] . '.*';
}
- $this->respond(200, $response);
- }
-
-
-
-
-
- public function process() {
- if (!isset($_GET['action']))
- throw new Exception('Invalid request');
-
- switch ($_GET['action']) {
- case 'irc.log': $this->logIrc(); break;
- default: throw new Exception('Invalid request');
+ if (!in_array($privilege, $this->privileges) &&
+ !in_array($globalPrivilege, $this->privileges)) {
+ $this->respond(403);
}
}
+ }
+?>
- /* IRC */
- public function logIrc() {
- if (!isset($_POST['nick']) || !isset($_POST['chan']) || !isset($_POST['msg']))
- throw new Exception('Missing data fields');
-
- $_POST['msg'] = preg_replace('/\x01/', '', $_POST['msg']);
-
- $st = $this->app->db->prepare('INSERT INTO irc_logs (`nick`, `channel`, `log`)
- VALUES (:nick, :chan, :msg)');
- $result = $st->execute(array(':nick' => $_POST['nick'], ':chan' => $_POST['chan'], ':msg' => $_POST['msg']));
-
-
- // Calculate stats
- $st = $this->app->db->prepare('INSERT INTO irc_stats (`nick`, `lines`, `words`, `chars`)
- VALUES (:nick, :lines, :words, :chars)
- ON DUPLICATE KEY UPDATE `lines`=`lines`+:lines, `words`=`words`+:words, `chars`=`chars`+:chars, `time`=NOW()');
-
- $st->bindValue(':nick', $_POST['nick'], PDO::PARAM_INT);
- $st->bindValue(':lines', 1, PDO::PARAM_INT);
- $st->bindValue(':words', str_word_count($_POST['msg']), PDO::PARAM_INT);
- $st->bindValue(':chars', strlen($_POST['msg']), PDO::PARAM_INT);
- $result = $st->execute();
- }
- }
-?>
\ No newline at end of file
diff --git a/files/class.app.php b/files/class.app.php
index 4d112bd..03eb937 100644
--- a/files/class.app.php
+++ b/files/class.app.php
@@ -14,7 +14,10 @@ function __construct($minimal = false) {
$this->config['log'] = $this->config['path'] . "/files/log/";
// Connect to database
- $this->connectDB($this->config['db']);
+ $this->connectDB($this->config['db'], false);
+
+ // Connect to LDAP
+ $this->ldap = new ldap($this);
//get version number
$this->cache = new cache($this);
@@ -99,7 +102,7 @@ protected function connectDB($config, $debug=true) {
$dsn .= (!empty($config['port'])) ? ';port=' . $config['port'] : '';
$dsn .= ";dbname={$config['database']}";
$this->db = new PDO($dsn, $config['username'], $config['password']);
-
+
if ($debug) {
$this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
diff --git a/files/class.cache.php b/files/class.cache.php
index b1fe29c..9256e35 100644
--- a/files/class.cache.php
+++ b/files/class.cache.php
@@ -7,11 +7,11 @@ function __construct($app) {
function get($file, $freshness=null) {
$file = $this->app->config['cache'] . $file;
$data = false;
- if (file_exists($file) && (!$freshness || filemtime($file) > (time() - 60 * $freshness ))) {
+ if (file_exists($file) && (!$freshness || filemtime($file) > (time() - (60 * $freshness )))) {
$fh = @fopen($file, "rb");
if (!$fh)
return false;
- $data = fread($fh, filesize($file));
+ $data = stream_get_contents($fh, filesize($file));
fclose($fh);
}
diff --git a/files/class.donations.php b/files/class.donations.php
index fc1f36a..71dad0c 100644
--- a/files/class.donations.php
+++ b/files/class.donations.php
@@ -46,7 +46,7 @@ public function makeTransaction($amount, $size) {
curl_setopt($ch, CURLOPT_URL, 'https://api.paypal.com/v1/oauth2/token');
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_setopt($ch, CURLOPT_USERPWD, $config['client'] . ":" . $config['secret']);
- curl_setopt($ch, CURLOPT_SSLVERSION, 3);
+// curl_setopt($ch, CURLOPT_SSLVERSION, 3);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
@@ -65,6 +65,7 @@ public function makeTransaction($amount, $size) {
curl_setopt_array($ch, $options);
$response = curl_exec($ch);
+
$header = substr($response, 0, curl_getinfo($ch,CURLINFO_HEADER_SIZE));
$body = json_decode(substr($response, curl_getinfo($ch,CURLINFO_HEADER_SIZE)));
@@ -72,7 +73,6 @@ public function makeTransaction($amount, $size) {
$token = $body->access_token;
-
// Make actually request
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.paypal.com/v1/payments/payment');
@@ -112,7 +112,6 @@ public function makeTransaction($amount, $size) {
curl_setopt_array($ch, $options);
-
$response = curl_exec($ch);
$header = substr($response, 0, curl_getinfo($ch,CURLINFO_HEADER_SIZE));
@@ -183,4 +182,4 @@ public function storeDonation($amount, $id) {
}
}
-?>
\ No newline at end of file
+?>
diff --git a/files/class.feed.php b/files/class.feed.php
index fc3390c..173d619 100644
--- a/files/class.feed.php
+++ b/files/class.feed.php
@@ -99,7 +99,7 @@ public function get($last=0, $user_id=null) {
if (!$n)
unset($result[$key]);
else
- $res->uri = "{$res->uri}#post-{$res->item_id}";
+ $res->uri = "{$res->uri}?post={$res->item_id}";
} else if ($res->type == 'article' || $res->type == 'favourite') {
// uri, title
$st = $this->app->db->prepare("SELECT articles.title, articles.category_id, CONCAT(IF(articles.category_id = 0, '/news/', '/articles/'), articles.slug) AS uri
@@ -200,4 +200,4 @@ public function remove($id) {
return $result;
}
}
-?>
\ No newline at end of file
+?>
diff --git a/files/class.forum.php b/files/class.forum.php
index a57823f..26eaf8c 100644
--- a/files/class.forum.php
+++ b/files/class.forum.php
@@ -102,6 +102,8 @@ public function getSections($parent=null) {
}
public function printThreadPost($post, $first=false, $last=false, $admin=false) {
+ if (!$post) return;
+
$post->first = $first;
$post->last = $last;
@@ -414,14 +416,7 @@ public function newThread($section, $title, $body) {
if (!$title || strlen($title) < 3)
return "Title must be longer than three characters";
- // Check for spam - pretty much always new threads
- $regex = "/(". implode('|', $this->banned) .")/i";
- $matched = preg_match_all($regex, $title, $matches) + preg_match_all($regex, $body, $matches);
- if ($matched > 2) {
- // Could implement http://www.stopforumspam.com and ban user
- return "Banned words found in content";
- }
-
+ // Post validation happens within transaction
$section_id = $section->id;
$slug = $section->slug . '/' . $this->app->utils->generateSlug($title);
@@ -741,9 +736,6 @@ public function newPost($thread_id, $body) {
VALUES (:uid, :thread_id, 1) ON DUPLICATE KEY UPDATE `watching` = 1");
$st->execute(array(':thread_id'=>$thread_id, ':uid'=>$this->app->user->uid));
-
-
-
// Check for forum medal
$st = $this->app->db->prepare('SELECT COUNT(post_id) AS posts FROM forum_posts
@@ -927,13 +919,13 @@ public function flagPost($post_id, $reason, $extra) {
return $st->execute(array(':post_id'=>$post_id, ':uid'=>$this->app->user->uid, ':reason'=>$reason, ':extra'=>$extra));
}
- public function removeFlags($post_id, $reward=false) {
+ public function removeFlags($post_id, $reward=false, $flag_id = null) {
if (!$this->app->user->admin_forum_priv)
return false;
// If reward give all users who flagged a medal
- if ($reward) {
- $st = $this->app->db->prepare("SELECT user_id FROM forum_posts_flags WHERE post_id = :post_id");
+ if ($post_id && $reward) {
+ $st = $this->app->db->prepare("SELECT user_id FROM forum_posts_flags WHERE post_id = :post_id AND response = 0");
$st->execute(array(':post_id'=>$post_id));
if ($result = $st->fetchAll()) {
foreach($result AS $res) {
@@ -942,15 +934,26 @@ public function removeFlags($post_id, $reward=false) {
}
}
- $st = $this->app->db->prepare("DELETE FROM forum_posts_flags WHERE post_id = :post_id");
- return $st->execute(array(':post_id'=>$post_id));
+ if ($reward) {
+ $response = 1;
+ } else {
+ $response = -1;
+ }
+
+ if ($post_id) {
+ $st = $this->app->db->prepare("UPDATE forum_posts_flags SET response = :response WHERE post_id = :post_id");
+ return $st->execute(array(':response'=>$response, ':post_id'=>$post_id));
+ } else {
+ $st = $this->app->db->prepare("UPDATE forum_posts_flags SET response = :response WHERE flag_id = :flag_id");
+ return $st->execute(array(':response'=>$response, ':flag_id'=>$flag_id));
+ }
}
public function getPostFlags($post_id) {
if (!$this->app->user->admin_forum_priv)
return false;
- $st = $this->app->db->prepare("SELECT username, reason, details FROM forum_posts_flags INNER JOIN users ON users.user_id = forum_posts_flags.user_id where post_id = :post_id");
+ $st = $this->app->db->prepare("SELECT flag_id AS id, username, reason, details FROM forum_posts_flags INNER JOIN users ON users.user_id = forum_posts_flags.user_id where post_id = :post_id AND response = 0");
$st->execute(array(':post_id'=>$post_id));
return $st->fetchAll();
}
@@ -975,7 +978,54 @@ function validatePost($body, $edit=false) {
return false;
}
+ // Check for spam - pretty much always new threads
+ $regex = "/(". implode('|', $this->banned) .")/i";
+ $matched = preg_match_all($regex, $body, $matches);
+ if ($matched > 2) {
+ // Could implement http://www.stopforumspam.com and ban user
+ $this->error = "Banned words found in content";
+ return false;
+ }
+
if (!$edit) {
+ // Don't allow users to post in the first 10 mins of being a member
+ $st = $this->app->db->prepare('SELECT TIMESTAMPDIFF(MINUTE, joined, NOW()) AS `joined` from users_activity WHERE user_id = :uid');
+ $st->execute(array(':uid'=>$this->app->user->uid));
+ $res = $st->fetch();
+
+ if ($res->joined < 15) {
+ $this->error = "New users can not post in the forum for the first 15 minutes. You have been registered for {$res->joined} minutes.";
+ return false;
+ }
+
+ $minutes_joined = $res->joined;
+ $hours_joined = ceil($minutes_joined / 60);
+
+ if ($hours_joined < 24) {
+ // Don't allow users to post on more than 2 threads, per hour for the first 24 hours
+ $st = $this->app->db->prepare('SELECT COUNT(DISTINCT(`thread_id`)) AS `threads` FROM forum_posts where author = :uid');
+ $st->execute(array(':uid'=>$this->app->user->uid));
+ $res = $st->fetch();
+
+ if ($res->threads > $hours_joined * 2) {
+ $this->error = "New users are only allowed to post in two threads, per hour, for the first 24 hours.";
+ return false;
+ }
+ }
+
+ // Don't allow user to post if they have karma < 0 and score <= 0
+ $st = $this->app->db->prepare('SELECT user_id, karma.karma
+ FROM users
+ LEFT JOIN (SELECT SUM(karma) AS karma, forum_posts.author FROM users_forum INNER JOIN forum_posts ON users_forum.post_id = forum_posts.post_id AND forum_posts.deleted = 0 GROUP BY forum_posts.author) karma
+ ON karma.author = users.user_id
+ WHERE user_id = :uid AND (karma < 0 AND score <= 0)
+ LIMIT 1');
+ $st->execute(array(':uid'=>$this->app->user->uid));
+ if ($res = $st->fetch()) {
+ $this->error = "You have been temporarily banned from posting messages";
+ return false;
+ }
+
//check when last post was made
// The time left calculation was previously done using
@@ -1005,5 +1055,29 @@ function validatePost($body, $edit=false) {
return true;
}
+
+ public function getStats() {
+ $stats = $this->app->cache->get('forum_stats', 5);
+
+ if ($stats)
+ return json_decode($stats);
+
+ $stats = new stdClass();
+
+ $st = $this->app->db->query("SELECT count(*) AS `count` FROM forum_threads WHERE deleted = 0");
+ $result = $st->fetch();
+ $stats->threads = $result->count;
+
+ $st = $this->app->db->query("SELECT count(*) AS `count` FROM forum_posts WHERE deleted = 0");
+ $result = $st->fetch();
+ $stats->posts = $result->count;
+
+ $result = $this->app->db->query('SELECT `author` FROM forum_posts WHERE deleted = 0 GROUP BY author');
+ $stats->members = $result->rowCount();
+
+ $this->app->cache->set('forum_stats', json_encode($stats));
+
+ return $stats;
+ }
}
?>
diff --git a/files/class.ldap.php b/files/class.ldap.php
new file mode 100644
index 0000000..9f8d741
--- /dev/null
+++ b/files/class.ldap.php
@@ -0,0 +1,131 @@
+app = $app;
+ $this->config = $config = $this->app->config['ldap'];
+ }
+
+ private function bind($authenticated=false) {
+ if ($this->connected) {
+ if ($authenticated && !$this->authenticated) {
+ ldap_bind($this->connection, $this->config['username'] . ',' . $this->config['dn'], $this->config['password']);
+ }
+
+ return;
+ }
+
+ ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, 7);
+
+ try {
+ $connection = ldap_connect($this->config['host']);
+
+ // Change protocol
+ ldap_set_option($connection, LDAP_OPT_PROTOCOL_VERSION, 3);
+ ldap_set_option($connection, LDAP_OPT_REFERRALS, 0);
+
+ if ($authenticated) {
+ ldap_bind($connection, $this->config['username'] . ',' . $this->config['dn'], $this->config['password']);
+ }
+
+ // Start TLS
+ // ldap_start_tls($connection);
+ } catch (Exception $e) {
+ print_r($e);
+ return;
+ }
+
+ $this->connected = true;
+ $this->connection = $connection;
+ }
+
+ public function checkLogin($username, $password) {
+ if (!$username || !$password) return false;
+
+ $username = $this->escapeUsername($username);
+ if (!$username) return false;
+
+ $this->bind();
+
+ $dn = 'cn=' . $username . ',' . $this->config['dn'];
+
+ $authenticated = ldap_bind($this->connection, $dn, $password);
+ if (!$authenticated) {
+ return false; // User details where invalid
+ }
+
+ $result = ldap_search($this->connection, $this->config['dn'], 'cn= ' . $username);
+ if (!$result) {
+ return false; // Couldn't find user
+ }
+
+ $info = ldap_get_entries($this->connection, $result);
+ $user_id = intval($info[0]['uid'][0]);
+ if (!$user_id) {
+ return false; // No user_id defined, or invalid
+ }
+
+ return $user_id; // Login successful
+ }
+
+ public function createUser($user_id, $username, $password) {
+ $username = $this->escapeUsername($username);
+ if (!$username) return false;
+
+ $this->bind(true);
+
+ $info = array();
+
+ // prepare data
+ $info['cn'] = $username;
+ $info['sn'] = $username;
+ $info['objectClass'][0] = "top";
+ $info['objectClass'][1] = "person";
+ $info['objectClass'][2] = "inetOrgPerson";
+ $info['uid'] = $user_id;
+ $info['userPassword'] = $encodedPassword = "{SHA}" . base64_encode(pack("H*", sha1($password)));
+
+ $dn = 'cn=' . $username . ',' . $this->config['dn'];
+
+ // add data to directory
+ return ldap_add($this->connection, $dn, $info);
+ }
+
+ public function changePassword($username, $newPassword) {
+ if (!$username || !$newPassword) return false;
+
+ $username = $this->escapeUsername($username);
+ if (!$username) return false;
+
+ $this->bind(true);
+
+ // Check old password was correct
+// if ($this->checkLogin($username, $oldPassword)) {
+// return "Old password is incorrect";
+// }
+
+ // Set password
+ $encodedPassword = "{SHA}" . base64_encode(pack("H*", sha1($newPassword)));
+ $entry = array();
+ $entry["userPassword"] = $encodedPassword;
+
+ $dn = 'cn=' . $username . ',' . $this->config['dn'];
+
+ return ldap_modify($this->connection, $dn, $entry);
+ }
+
+ private function escapeUsername($username) {
+ if ($this->app->utils->check_user($username)) {
+ return $username;
+ } else {
+ return false;
+ }
+ }
+
+ }
+
+?>
diff --git a/files/class.levels.php b/files/class.levels.php
index a6d2e00..a5688e4 100644
--- a/files/class.levels.php
+++ b/files/class.levels.php
@@ -14,16 +14,16 @@ public function getList($uid=null, $category=null) {
if (!$levels) {
$sql = 'SELECT levels.level_id AS `id`, CONCAT(levels_groups.title, " Level ", levels.name) as `title`, levels.name, levels_groups.title as `group`,
- LOWER(CONCAT("/levels/", CONCAT_WS("/", levels_groups.title, levels.name))) as `uri`, levels_completed.completed as `total_completed`,
- levels_data.value AS `reward`
- FROM levels
- INNER JOIN levels_groups
- ON levels_groups.title = levels.group
- LEFT JOIN levels_data
- ON levels_data.level_id = levels.level_id AND levels_data.key = "reward"
- LEFT JOIN (SELECT COUNT(user_id) AS `completed`, level_id FROM users_levels WHERE completed > 0 AND user_id != 69 GROUP BY level_id) `levels_completed`
- ON levels_completed.level_id = levels.level_id
- ORDER BY levels_groups.order ASC, levels.level_id ASC';
+ LOWER(CONCAT("/levels/", CONCAT_WS("/", levels_groups.title, levels.name))) as `uri`, levels_completed.completed as `total_completed`,
+ levels_data.value AS `reward`
+ FROM levels
+ INNER JOIN levels_groups
+ ON levels_groups.title = levels.group
+ LEFT JOIN levels_data
+ ON levels_data.level_id = levels.level_id AND levels_data.key = "reward"
+ LEFT JOIN (SELECT COUNT(user_id) AS `completed`, level_id FROM users_levels WHERE completed > 0 AND user_id != 69 GROUP BY level_id) `levels_completed`
+ ON levels_completed.level_id = levels.level_id
+ ORDER BY levels_groups.order ASC, levels.level_id ASC';
$st = $this->app->db->prepare($sql);
$st->execute();
@@ -78,40 +78,36 @@ public function getLevelFromID($id) {
$st->execute();
$res = $st->fetch();
- if ($res)
+ if ($res) {
return $this->getLevel($res->group, $res->name, true);
- else
+ } else{
return false;
+ }
}
public function getLevel($group, $name, $noSkip=false) {
- $before_after_sql = 'SELECT `level_id`, `name`, LOWER(CONCAT("/levels/", CONCAT_WS("/", levels_groups.title, levels.name))) as `uri`
- FROM levels
- INNER JOIN levels_groups
- ON levels_groups.title = levels.group
- WHERE `group` = :group
- ORDER BY level_id';
+ // Check cache for level data
+ $cacheKey = 'level_data_' . $group . '_' . $name;
+ $cache = $this->app->cache->get($cacheKey, 5);
- $sql = "SELECT levels.level_id, levels.name, levels_groups.title AS `group`, CONCAT(`group`, ' Level ', levels.name) as `title`,
- IF(users_levels.completed > 0, 1, 0) as `completed`, users_levels.completed as `completed_time`, `started`,
- IFNULL(users_levels.attempts, 0) as `attempts`,
- levels_before.uri AS `level_before_uri`, levels_after.uri AS `level_after_uri`
- FROM levels
- INNER JOIN levels_groups
- ON levels_groups.title = levels.group
- LEFT JOIN ({$before_after_sql} DESC) levels_before
- ON levels_before.level_id < levels.level_id
- LEFT JOIN ({$before_after_sql} ASC) levels_after
- ON levels_after.level_id > levels.level_id
- LEFT JOIN users_levels
- ON users_levels.user_id = :uid AND users_levels.level_id = levels.level_id
- WHERE levels.name = :level AND levels.group = :group";
+ if ($cache):
+ $level = unserialize($cache);
- $st = $this->app->db->prepare($sql);
- $st->execute(array(':level'=>$name, ':group'=>$group, ':uid'=>$this->app->user->uid));
- $level = $st->fetch();
+ $sql = "SELECT
+ IF(users_levels.completed > 0, 1, 0) as `completed`,
+ users_levels.completed as `completed_time`,
+ users_levels.started,
+ IFNULL(users_levels.attempts, 0) as `attempts`
+ FROM users_levels
+ WHERE level_id = :levelid AND user_id = :uid";
+
+ $st = $this->app->db->prepare($sql);
+ $st->execute(array(':levelid' => $level->level_id, ':uid' => $this->app->user->uid));
+ $tmpLevel = $st->fetch();
+
+ // Merge users stats into cache response
+ $level = (object) array_merge((array) $level, (array) $tmpLevel);
- if ($level) {
//Check if user has access
if (isset($level->level_before_uri) && strtolower($level->group) == 'main') {
$sql = 'SELECT IF(users_levels.completed > 0, 1, 0) as `completed` FROM levels
@@ -120,7 +116,7 @@ public function getLevel($group, $name, $noSkip=false) {
WHERE `group` = :group AND levels.level_id < :level_id
ORDER BY levels.level_id DESC
LIMIT 1';
-
+
$st = $this->app->db->prepare($sql);
$st->execute(array(':level_id'=>$level->level_id, ':group'=>$group, ':uid'=>$this->app->user->uid));
$previous = $st->fetch();
@@ -132,71 +128,143 @@ public function getLevel($group, $name, $noSkip=false) {
}
$this->levelView($level->level_id);
- } else
- return false;
- //Build level data
- $sql = 'SELECT `key`, `value`, users.username
- FROM levels_data
- LEFT JOIN users
- ON levels_data.value = users.user_id AND levels_data.key = "author"
- WHERE level_id = :lid';
- $st = $this->app->db->prepare($sql);
- $st->execute(array(':lid'=>$level->level_id));
- $data = $st->fetchAll();
+ return $level;
- $level->data = array();
+ else:
+ $before_after_sql = 'SELECT `level_id`, `name`, LOWER(CONCAT("/levels/", CONCAT_WS("/", levels_groups.title, levels.name))) as `uri`
+ FROM levels
+ INNER JOIN levels_groups
+ ON levels_groups.title = levels.group
+ WHERE `group` = :group
+ ORDER BY level_id';
- foreach($data as $d) {
- //Find all non-value entries
- foreach($d as $k=>$v) {
- if ($v && $k !== 'key' && $k !== 'value')
- $d->value = $v;
+ $sql = "SELECT levels.level_id, levels.name, levels_groups.title AS `group`, CONCAT(`group`, ' Level ', levels.name) as `title`,
+ IF(users_levels.completed > 0, 1, 0) as `completed`, users_levels.completed as `completed_time`, `started`,
+ IFNULL(users_levels.attempts, 0) as `attempts`,
+ levels_before.uri AS `level_before_uri`, levels_after.uri AS `level_after_uri`
+ FROM levels
+ INNER JOIN levels_groups
+ ON levels_groups.title = levels.group
+ LEFT JOIN ({$before_after_sql} DESC) levels_before
+ ON levels_before.level_id < levels.level_id
+ LEFT JOIN ({$before_after_sql} ASC) levels_after
+ ON levels_after.level_id > levels.level_id
+ LEFT JOIN users_levels
+ ON users_levels.user_id = :uid AND users_levels.level_id = levels.level_id
+ WHERE levels.name = :level AND levels.group = :group";
+
+ $st = $this->app->db->prepare($sql);
+ $st->execute(array(':level'=>$name, ':group'=>$group, ':uid'=>$this->app->user->uid));
+ $level = $st->fetch();
+
+ if ($level) {
+ //Check if user has access
+ if (isset($level->level_before_uri) && strtolower($level->group) == 'main') {
+ $sql = 'SELECT IF(users_levels.completed > 0, 1, 0) as `completed` FROM levels
+ LEFT JOIN users_levels
+ ON users_levels.user_id = :uid AND users_levels.level_id = levels.level_id
+ WHERE `group` = :group AND levels.level_id < :level_id
+ ORDER BY levels.level_id DESC
+ LIMIT 1';
+
+ $st = $this->app->db->prepare($sql);
+ $st->execute(array(':level_id'=>$level->level_id, ':group'=>$group, ':uid'=>$this->app->user->uid));
+ $previous = $st->fetch();
+
+ if (!$noSkip && (!$previous || !$previous->completed)) {
+ header("Location: $level->level_before_uri?skipped");
+ die();
+ }
+ }
+
+ $this->levelView($level->level_id);
+ } else {
+ return false;
}
- $level->data[$d->key] = $d->value;
- }
+ // Build level data
+ $sql = 'SELECT `key`, `value`, users.username
+ FROM levels_data
+ LEFT JOIN users
+ ON levels_data.value = users.user_id AND levels_data.key = "author"
+ WHERE level_id = :lid';
- if (isset($level->data['code'])) {
- $level->data['code'] = json_decode($level->data['code']);
- }
+ $st = $this->app->db->prepare($sql);
+ $st->execute(array(':lid'=>$level->level_id));
+ $data = $st->fetchAll();
- // Set page details
- $this->app->page->title = ucwords($level->title);
+ $level->data = array();
- // Get stats
- $sql = "SELECT COUNT(user_id) AS `completed` FROM users_levels WHERE completed > 0 AND level_id = :lid AND user_id != 69";
- $st = $this->app->db->prepare($sql);
- $st->execute(array(':lid'=>$level->level_id));
- $result = $st->fetch();
- $level->count = $result->completed;
-
- // // // Get latest
- // $sql = "SELECT username, completed FROM users_levels INNER JOIN users ON users.user_id = users_levels.user_id WHERE completed > 0 AND level_id = :lid AND users.user_id != 69 ORDER BY completed DESC LIMIT 1";
- $sql = "SELECT username, completed FROM users INNER JOIN (SELECT `user_id`, `completed` FROM users_levels WHERE completed > 0 AND level_id = :lid AND user_id != 69 ORDER BY `completed` DESC LIMIT 1) `a` ON `a`.user_id = users.user_id;";
- $st = $this->app->db->prepare($sql);
- $st->execute(array(':lid'=>$level->level_id));
- $result = $st->fetch();
- $level->last_completed = $result->completed;
- $level->last_user = $result->username;
-
- // // // Get first
- // $sql = "SELECT username, completed FROM users_levels INNER JOIN users ON users.user_id = users_levels.user_id WHERE completed > 0 AND level_id = :lid AND users.user_id != 69 ORDER BY completed ASC LIMIT 1";
- $sql = "SELECT username, completed FROM users INNER JOIN (SELECT `user_id`, `completed` FROM users_levels WHERE completed > 0 AND level_id = :lid AND user_id != 69 ORDER BY `completed` ASC LIMIT 1) `a` ON `a`.user_id = users.user_id;";
- $st = $this->app->db->prepare($sql);
- $st->execute(array(':lid'=>$level->level_id));
- $result = $st->fetch();
- $level->first_completed = $result->completed;
- $level->first_user = $result->username;
-
- return $level;
+ foreach($data as $d) {
+ //Find all non-value entries
+ foreach($d as $k=>$v) {
+ if ($v && $k !== 'key' && $k !== 'value')
+ $d->value = $v;
+ }
+
+ $level->data[$d->key] = $d->value;
+ }
+
+ if (isset($level->data['code']) && $level->data['code']) {
+ $level->data['code'] = json_decode($level->data['code']);
+ }
+
+ // Set page details
+ $this->app->page->title = ucwords($level->title);
+
+ // Get stats
+ $sql = "SELECT COUNT(user_id) AS `completed` FROM users_levels WHERE completed > 0 AND level_id = :lid AND user_id != 69";
+ $st = $this->app->db->prepare($sql);
+ $st->execute(array(':lid'=>$level->level_id));
+ $result = $st->fetch();
+ $level->count = $result->completed;
+
+ // If level has uptime code, check level status
+ if (isset($level->data['uptime']) && $level->data['uptime']) {
+ // Second cache used for high completion levels, limits the online check from happening every time someone completes the level
+ $level->online = $this->app->cache->get('level_uptime_' . $level->data['uptime'], 5);
+
+ if (!$level->online) {
+ $status = file_get_contents("https://api.uptimerobot.com/getMonitors?apiKey=".$level->data['uptime']."&format=json&noJsonCallback=1");
+ $status = json_decode($status);
+ $level->online = $status->monitors->monitor[0]->status == 2 ? 'online' : 'offline';
+
+ $this->app->cache->set('level_uptime_' . $level->data['uptime'], $level->online);
+ }
+ }
+
+ // Get last user to complete level
+ $sql = "SELECT username, completed FROM users INNER JOIN (SELECT `user_id`, `completed` FROM users_levels WHERE completed > 0 AND level_id = :lid AND user_id != 69 ORDER BY `completed` DESC LIMIT 1) `a` ON `a`.user_id = users.user_id;";
+ $st = $this->app->db->prepare($sql);
+ $st->execute(array(':lid'=>$level->level_id));
+ $result = $st->fetch();
+ $level->last_completed = $result->completed;
+ $level->last_user = $result->username;
+
+ // Get first user to complete level
+ $sql = "SELECT username, completed FROM users INNER JOIN (SELECT `user_id`, `completed` FROM users_levels WHERE completed > 0 AND level_id = :lid AND user_id != 69 ORDER BY `completed` ASC LIMIT 1) `a` ON `a`.user_id = users.user_id;";
+ $st = $this->app->db->prepare($sql);
+ $st->execute(array(':lid'=>$level->level_id));
+ $result = $st->fetch();
+ $level->first_completed = $result->completed;
+ $level->first_user = $result->username;
+
+ // Cache level data
+ $this->app->cache->set($cacheKey, serialize($level));
+
+ return $level;
+
+ endif; // End cache check
}
+ // Mark that the user has viewed a level
function levelView($level_id) {
$st = $this->app->db->prepare('INSERT IGNORE INTO users_levels (`user_id`, `level_id`) VALUES (:uid, :lid)');
$st->execute(array(':lid'=> $level_id, ':uid' => $this->app->user->uid));
}
+ // Check if supplied answer is correct
function check($level) {
if (!isset($level->data['answer']))
return false;
@@ -205,53 +273,77 @@ function check($level) {
$attempted = false;
$correct = false;
+ $incorrect = 0;
+
foreach($answers AS $answer) {
+ $valid = false;
+
if (strtolower($answer->method) == 'post') {
if (isset($_POST[$answer->name])) {
$attempted = true;
- if ($answer->type && $answer->type == 'regex') {
+ if (isset($answer->type) && $answer->type == 'regex') {
if (preg_match($answer->value, $_POST[$answer->name])) {
- $correct = true;
+ $valid = true;
+ if ($incorrect === 0) {
+ $correct = true;
+ }
} else {
$correct = false;
- break;
}
- } else if ($_POST[$answer->name] === $answer->value)
+ } else if ($_POST[$answer->name] === $answer->value) {
+ $valid = true;
+ if ($incorrect === 0) {
$correct = true;
- else {
- $correct = false;
- break;
}
+ } else {
+ $correct = false;
+ }
+ } else {
+ $correct = false;
}
} else if (strtolower($answer->method) == 'get') {
if (isset($_GET[$answer->name])) {
$attempted = true;
- if ($answer->type && $answer->type == 'regex') {
+ if (isset($answer->type) && $answer->type == 'regex') {
if (preg_match($answer->value, $_GET[$answer->name])) {
- $correct = true;
+ $valid = true;
+ if ($incorrect === 0) {
+ $correct = true;
+ }
} else {
$correct = false;
- break;
}
- } else if ($_GET[$answer->name] === $answer->value)
+ } else if ($_GET[$answer->name] === $answer->value) {
+ $valid = true;
+ if ($incorrect === 0) {
$correct = true;
- else {
- $correct = false;
- break;
}
+ } else {
+ $correct = false;
+ }
+ } else {
+ $correct = false;
}
}
+
+ if (!$valid) {
+ $incorrect++;
+ }
}
if ($attempted) {
$level->attempt = $correct;
$this->attempt($level, $correct);
+
+ if ($level->level_id == 53) {
+ $level->errorMsg = (3 - $incorrect) . ' out of 3 answers correct';
+ }
}
return $correct;
}
-
+ // Record attempt to complete level
function attempt($level, $correct=false) {
if (!$level->completed) {
$level->attempts = $level->attempts + 1;
@@ -261,6 +353,7 @@ function attempt($level, $correct=false) {
$level->count++;
$level->last_user = $this->app->user->username;
$level->last_completed = "now";
+
//Update user score (temporary)
$this->app->user->score = $this->app->user->score + $level->data['reward'];
$st = $this->app->db->prepare('UPDATE users_levels SET completed = NOW(), attempts=attempts+1 WHERE level_id = :lid AND user_id = :uid');
@@ -295,20 +388,20 @@ function user_data($level_id, $data=null) {
}
-
// ADMIN FUNCTIONS
function addCategory($title) {
- if (!$this->app->user->admin_site_priv)
+ if (!$this->app->user->admin_site_priv) {
return false;
+ }
$st = $this->app->db->prepare('INSERT INTO levels_groups (`title`) VALUES (:title)');
return $st->execute(array(':title'=> $title));
}
function editLevel($id, $new = false) {
- if (!$this->app->user->admin_site_priv)
+ if (!$this->app->user->admin_site_priv) {
return false;
-
+ }
$changes = array();
@@ -327,6 +420,9 @@ function editLevel($id, $new = false) {
if (isset($_POST['reward']) && is_numeric($_POST['reward'])) {
$changes['reward'] = $_POST['reward'];
}
+ if (isset($_POST['uptime']) && strlen($_POST['uptime'])) {
+ $changes['uptime'] = $_POST['uptime'];
+ }
if (isset($_POST['description']) && strlen($_POST['description'])) {
$changes['description'] = $_POST['description'];
}
@@ -375,11 +471,13 @@ function newLevel() {
}
function editLevelForm($id) {
- if (!$this->app->user->admin_site_priv)
+ if (!$this->app->user->admin_site_priv) {
return false;
+ }
- if (!$this->app->checkCSRFKey("level-editor", $_POST['token']))
+ if (!$this->app->checkCSRFKey("level-editor", $_POST['token'])) {
return false;
+ }
$form = null;
@@ -405,11 +503,13 @@ function editLevelForm($id) {
}
}
- if (count($form['fields']))
+ if (count($form['fields'])) {
$form = json_encode($form);
+ }
} else {
- if (isset($_POST['form']))
+ if (isset($_POST['form'])) {
$form = $_POST['form'];
+ }
}
if ($form) {
diff --git a/files/class.loader.php b/files/class.loader.php
index 1650da8..25abfa5 100644
--- a/files/class.loader.php
+++ b/files/class.loader.php
@@ -82,7 +82,7 @@ public function load($type) {
$path = "{$this->css_base}min/{$this->theme}/main.css";
if ($this->generate($path, $this->default_css, 'css')) {
$buster = filemtime($this->php_base.$path);
- $css_includes = "\n";
+ $css_includes = "\n";
}
//Build custom CSS file, if required
@@ -94,7 +94,7 @@ public function load($type) {
if ($this->generate($path, $this->custom_css, 'css')) {
$buster = filemtime($this->php_base.$path);
- $css_includes .= " \n";
+ $css_includes .= " \n";
}
}
@@ -104,7 +104,7 @@ public function load($type) {
$path = "{$this->js_base}min/main.js";
if ($this->generate($path, $this->default_js, 'js')) {
$buster = filemtime($this->php_base.$path);
- $js_includes = "\n";
+ $js_includes = "\n";
}
//Build custom JS, if required
@@ -116,7 +116,7 @@ public function load($type) {
if ($this->generate($path, $this->custom_js, 'js')) {
$buster = filemtime($this->php_base.$path);
- $js_includes .= " \n";
+ $js_includes .= " \n";
}
}
diff --git a/files/class.messages.php b/files/class.messages.php
index bdc115c..99fe747 100644
--- a/files/class.messages.php
+++ b/files/class.messages.php
@@ -171,6 +171,13 @@ public function deleteConvo($id) {
return $st->execute(array(':uid' => $this->app->user->uid, ':pm_id' => $id));
}
+ public function markMessagesRead()
+ {
+ // Mark thread as seen
+ $st = $this->app->db->prepare("UPDATE pm_users SET `seen` = NOW() WHERE user_id = :uid");
+ $st->execute(array(':uid' => $this->app->user->uid));
+ }
+
public function newMessage($to, $body, $pm_id=null) {
$body = trim($body);
diff --git a/files/class.profile.php b/files/class.profile.php
index f462683..0f5d576 100644
--- a/files/class.profile.php
+++ b/files/class.profile.php
@@ -58,7 +58,7 @@ public function __construct($username, $public=false) {
}
unset($this->gravatar);
- if (!$this->app->user->admin_site_priv) {
+ if (!$this->app->admin) {
unset($this->site_priv);
unset($this->pm_priv);
unset($this->forum_priv);
@@ -73,6 +73,7 @@ public function __construct($username, $public=false) {
activity.last_active, friends.status AS friends, friends.user_id AS friend, profile.gravatar,
IF (profile.gravatar = 1, u.email , profile.img) as `image`,
IF(priv.site_priv = 2, true, false) AS admin, IF(priv.forum_priv = 2, true, false) AS moderator,
+ priv.*,
forum_posts.posts, articles.articles, (donated.user_id IS NOT NULL) AS donator, (users_blocks.user_id IS NOT NULL) AS blocked, (users_blocks_me.user_id IS NOT NULL) AS blockedMe, karma.karma
FROM users u
LEFT JOIN users_profile profile
@@ -107,12 +108,9 @@ public function __construct($username, $public=false) {
if (isset($this->image)) {
$gravatar = isset($this->gravatar) && $this->gravatar == 1;
$this->image = profile::getImg($this->image, 198, $gravatar);
- } else
+ } else {
$this->image = profile::getImg(null, 198);
-
-
- if ($public)
- return true;
+ }
$st = $this->app->db->prepare('SELECT users_medals.medal_id, medals.label, medals.description, medals_colours.colour
@@ -125,6 +123,17 @@ public function __construct($username, $public=false) {
$st->execute(array(':uid' => $this->uid));
$this->medals = $st->fetchAll();
+ if (!$this->app->user->admin) {
+ unset($this->site_priv);
+ unset($this->pm_priv);
+ unset($this->forum_priv);
+ unset($this->pub_priv);
+ }
+
+ // Limit the amount of information public users can see
+ if ($public)
+ return true;
+
$st = $this->app->db->prepare('SELECT u.user_id as uid, u.username, users_friends.status, u.score, profile.gravatar, IF (profile.gravatar = 1, u.email , profile.img) as `image`
FROM users_friends as friends
INNER JOIN users u
@@ -138,8 +147,11 @@ public function __construct($username, $public=false) {
$st->execute(array(':uid' => $this->uid, ':user' => $this->app->user->uid));
$this->friendsList = $st->fetchAll();
+ // Parse content
+ $this->name = $this->app->parse($this->name, false, false);
+
if (isset($this->about)) {
- $this->about_plain = $this->about;
+ $this->about_plain = $this->app->parse($this->about, false, false);
$this->about = $this->app->parse($this->about);
}
@@ -453,7 +465,7 @@ public static function getMusic($id) {
}
public static function getImg($img, $size, $gravatar=false) {
- $default = "/users/images/{$size}/1:1/no_pic.jpg";
+ $default = "https://hackthis-10af.kxcdn.com/users/images/{$size}/1:1/no_pic.jpg";
if (!$img)
return $default;
@@ -461,7 +473,7 @@ public static function getImg($img, $size, $gravatar=false) {
if ($gravatar) {
return "https://www.gravatar.com/avatar/" . md5(strtolower(trim($img))) . "?d=identicon&s=" . $size;
} else {
- return "/users/images/{$size}/1:1/{$img}";
+ return "https://hackthis-10af.kxcdn.com/users/images/{$size}/1:1/{$img}";
}
}
}
diff --git a/files/class.user.php b/files/class.user.php
index d926b12..980348c 100644
--- a/files/class.user.php
+++ b/files/class.user.php
@@ -40,7 +40,10 @@ public function __construct($app) {
//Check if user is registering in
if (isset($_GET['register'])) {
- $this->reg_error = $this->register();
+ $reg_error = $this->register();
+ if (!is_numeric($reg_error)) {
+ $this->reg_error = $reg_error;
+ }
}
// Check if user is logged in
@@ -65,6 +68,16 @@ public function __construct($app) {
}
}
+ // Check if user is using Google Auth code
+ if (isset($_GET['g_auth']) && isset($_POST['g_code'])) {
+ if(is_numeric($_POST['g_code'])) {
+ $this->googleAuth($_POST['g_code']);
+ } else {
+ $this->login_error = 'Google Auth code must be numeric';
+ unset($_SESSION['g_auth']);
+ }
+ }
+
//Login, register or connect via facebook
if (isset($_GET['facebook'])) {
if (isset($_GET['code'])) {
@@ -83,7 +96,7 @@ public function __construct($app) {
*
* @todo Split functionality into separate functions e.g. medal checks
*/
- private function get_details() {
+ public function get_details() {
$this->app->stats->users_activity($this);
$st = $this->app->db->prepare('SELECT username, score, email, (oauth_id IS NOT NULL) as connected,
@@ -222,41 +235,61 @@ private function salt() {
}
public function login($user, $pass) {
- $st = $this->app->db->prepare('SELECT u.user_id, u.password, u.old_password, IFNULL(priv.site_priv, 1) as site_priv
+ $ldapID = $this->app->ldap->checkLogin($user, $pass);
+ if ($ldapID) {
+ // Check everything matches MySQL
+ $st = $this->app->db->prepare('SELECT u.user_id, u.g_auth, IFNULL(priv.site_priv, 1) as site_priv
FROM users u
LEFT JOIN users_priv priv
ON u.user_id = priv.user_id
- WHERE username = :u');
- $st->execute(array(':u' => $user));
- $row = $st->fetch();
-
- // Check if users details exist
- $this->login_error = 'Invalid login details';
- if ($row) {
- if ($row->old_password == 1) {
- $user = strtolower($user);
- $userhash = md5($user[0]."h97".md5(md5($pass))."t77Ds");
-
- if ($row->password === $userhash) {
- // Store new password
- $hash = crypt($pass, $this->salt());
- $st = $this->app->db->prepare('UPDATE users SET password = :hash, old_password = 0 WHERE user_id = :uid LIMIT 1');
- $status = $st->execute(array(':uid' => $row->user_id, ':hash' => $hash));
+ WHERE u.user_id = :uid AND u.username = :u');
+ $st->execute(array(':uid' => $ldapID, ':u' => $user));
+ $row = $st->fetch();
+
+ if ($row) {
+ if (!$row->site_priv) {
+ $this->login_error = 'Account has been banned';
+ return false;
+ }
- if (!$row->site_priv) {
- $this->login_error = 'Account has been banned';
- return false;
- }
+ $this->uid = $row->user_id;
+ // Check if Google Auth is enabled
+ if($row->g_auth == 1) {
+ // set Google Auth session and don't log in
+ $_SESSION['g_auth'] = $this->uid;
+ $this->g_auth = $this->uid;
+ } else {
$this->loggedIn = true;
- $this->uid = $row->user_id;
// Setup GA event
- $this->app->ssga->set_event('user', 'login', 'default', $this->uid);
+ $this->app->ssga->set_event('user', 'login', 'ldap', $this->uid);
$this->app->ssga->send();
$this->createSession();
}
+
+ return $this->loggedIn;
+ }
+ }
+
+ // User isn't valid in LDAP, double check with MySQL backup
+ $st = $this->app->db->prepare('SELECT u.user_id, u.password, u.old_password, g_auth, IFNULL(priv.site_priv, 1) as site_priv
+ FROM users u
+ LEFT JOIN users_priv priv
+ ON u.user_id = priv.user_id
+ WHERE username = :u');
+ $st->execute(array(':u' => $user));
+ $row = $st->fetch();
+
+ // Check if users details exist
+ $this->login_error = 'Invalid login details';
+ if ($row) {
+ if ($row->old_password == 1) {
+ $this->app->ssga->set_event('user', 'login', 'old_password', $this->uid);
+ $this->app->ssga->send();
+ $this->login_error = 'Your password has expired, click here to generate a new one.';
+ return false;
} else {
if ($row->password == crypt($pass, $row->password)) {
@@ -277,6 +310,12 @@ public function login($user, $pass) {
}
}
+
+ // Add none LDAP user to LDAP
+ if ($this->loggedIn) {
+ $this->app->ldap->createUser($this->uid, $user, $pass);
+ }
+
return $this->loggedIn;
}
@@ -428,6 +467,45 @@ public function oauth($provider, $id) {
}
}
+ public function googleAuth($authCode, $uid=null) {
+ if (!$uid) {
+ $uid = $_SESSION['g_auth'];
+ }
+
+ // setup Google Auth class
+ require('vendor/gauth.php');
+ $ga = new gauth();
+ $st = $this->app->db->prepare('SELECT g_secret FROM users WHERE user_id = :uid');
+ $st->execute(array(':uid' => $uid));
+ $secret = $st->fetch();
+
+ // verify Google code
+ $checkResult = $ga->verifyCode($secret->g_secret, $authCode, 2); // 2 = 2*30sec clock tolerance
+
+ if ($checkResult) {
+ $this->uid = $uid;
+
+ // if ok unset the session and log in
+ unset($_SESSION['g_auth']);
+ $this->loggedIn = true;
+
+ // Setup GA event
+ $this->app->ssga->set_event('user', 'login', 'GAuth', $this->uid);
+ $this->app->ssga->send();
+ $this->createSession();
+
+ return true;
+ } else {
+ unset($_SESSION['g_auth']);
+
+ $app->user->loggedIn = false;
+ $app->user->g_auth = false;
+ $this->login_error = 'Incorrect Authenticator code';
+
+ return false;
+ }
+ }
+
private function createSession() {
if ($this->loggedIn && isset($this->uid)) {
//session_regenerate_id();
@@ -490,9 +568,21 @@ private function regenerateRememberToken() {
setcookie('autologin', $token, time()+60*60*24*7, '/', 'hackthis.co.uk', true, true);
}
- public function register() {
+ public function register($username=null, $password=null, $email=null) {
+ if (!$username) {
+ $username = $_POST['reg_username'];
+ }
+ if (!$password) {
+ $password = $_POST['reg_password'];
+ $password2 = $_POST['reg_password_2'];
+ } else {
+ $password2 = $password;
+ }
+ if (!$email) {
+ $email = $_POST['reg_email'];
+ }
+
//Input check
- $username = $_POST['reg_username'];
if (!$this->app->utils->check_user($username))
return "Invalid username";
@@ -502,15 +592,13 @@ public function register() {
if ($st->fetch(PDO::FETCH_ASSOC))
return "Username already in use";
- $pass = $_POST['reg_password'];
- if (!isset($pass) || strlen($pass) < 5)
+ if (!isset($password) || strlen($password) < 5)
return "Invalid password";
- if ($pass !== $_POST['reg_password_2'])
+ if ($password !== $password2)
return "Passwords don't match";
- $hash = crypt($pass, $this->salt());
+ $hash = crypt($password, $this->salt());
- $email = $_POST['reg_email'];
if (!$this->app->utils->check_email($email))
return "Invalid email address";
@@ -522,7 +610,8 @@ public function register() {
// Check if IP has created more than 10 accounts
$st = $this->app->db->prepare('SELECT count(*) AS `count` FROM users_registration WHERE ip=?');
- $st->bindParam(1, ip2long($_SERVER['REMOTE_ADDR']));
+ $ip = ip2long($_SERVER['REMOTE_ADDR']);
+ $st->bindParam(1, $ip);
$st->execute();
$res = $st->fetch();
if ($res && $res->count >= 10) {
@@ -540,6 +629,9 @@ public function register() {
$uid = $this->app->db->lastInsertId();
+ // Add to LDAP
+ $this->app->ldap->createUser($uid, $username, $password);
+
// Login user
$this->loggedIn = true;
$this->uid = $uid;
@@ -562,6 +654,8 @@ public function register() {
$result = $st->execute(array(':u' => $uid, ':i' => ip2long($_SERVER['REMOTE_ADDR'])));
$this->createSession();
+
+ return $uid;
}
public function logout() {
@@ -620,6 +714,10 @@ public function delete($password, $token) {
$this->app->log->add('users', 'Deleted [' . $this->username . ']');
$this->app->db->commit();
+
+ // Remove session
+ $this->logout();
+
return true;
} catch (PDOException $e) {
$this->app->db->rollback();
@@ -809,7 +907,7 @@ public function getData($type, $uid=0, $interval=false) {
if ($uid) {
$sql = 'SELECT `value`
FROM users_data
- WHERE `type` = :type AND users.user_id = :uid';
+ WHERE `type` = :type AND user_id = :uid';
if ($interval)
$sql .= ' AND `time` > date_sub(now(), interval 10 minute)';
$sql .= ' LIMIT 1';
@@ -960,6 +1058,9 @@ public function changePassword($pass, $pass2, $uid = null) {
$status = $st->execute(array(':uid' => $uid, ':hash' => $hash));
if ($status) {
+ // Update LDAP
+ $this->app->ldap->changePassword($this->username, $pass);
+
$this->removeData('reset', $uid);
return true;
} else {
diff --git a/files/elements/emails/forum.html b/files/elements/emails/forum.html
index e4a633f..c14c6d3 100644
--- a/files/elements/emails/forum.html
+++ b/files/elements/emails/forum.html
@@ -14,7 +14,7 @@
Want to learn about hacking and network security? Discover how hacks, dumps and defacements are performed and secure your website against hackers with HackThis!!
- We offer a safe and legal environment to test your hacking skills with our 40+ challenging levels. Also on offer is a wide range of articles that cover all aspects of security, from network hacking to lock picking tutorials. We offer a safe and legal environment to test your hacking skills with our 40+ challenging levels. Also on offer is a wide range of articles that cover all aspects of security, from network hacking, securing passwords and sensitive data to lock picking tutorials.
+ Help is always on hand if you need it, or just discuss the latest news and trends with our thriving community. To get started register and login or just browser our articles and forum.
- You are recieving this notification as you are watching this thread. To stop these notifications please visit the thread and click the unwatch button.
+ You are receiving this notification as you are watching this thread. To stop these notifications please visit the thread and click the unwatch button.
-
\ No newline at end of file
+
diff --git a/files/elements/home_articles.php b/files/elements/home_articles.php
index 0373bbc..27531b4 100644
--- a/files/elements/home_articles.php
+++ b/files/elements/home_articles.php
@@ -14,9 +14,9 @@
?>
thumbnail) && $article->thumbnail): ?>
-
+
video)): ?>
-
+
=$article->title;?>
@@ -30,28 +30,4 @@
?>
Welcome to the hackers playground
- Discover how hacks and other security exploits are performed and secure your website against hackers.
+
+
+
+
+online) && $level->online): ?>
+
'>Level =$level->online;?>
+
data['description']) && (!isset($level->attempt) || $level->attempt !== true)):
@@ -43,4 +46,4 @@
$app->utils->message('Invalid details');
}
?>
-
| Author | +Article | +Category | +Submitted | +
|---|---|---|---|
| {{ article.username }} | +{{ article.title }} | +{{ article.comment }} | ++ |
| Moderator | +Action | +Time | +
|---|---|---|
| {{ log.username }} | +{{ log.subject }} | ++ |

- The easiest way to contact us is to use the form provided below. We will respond to you as quickly as we can. Feel free to say hi or send us any comments or suggestions you have...good or bad. + The easiest way to contact us is to use the form provided below. We will respond to you as quickly as we can. Feel free to say hi, suggest changes, or share your thoughts with us (good or bad).
\ No newline at end of file +?> diff --git a/html/donator.php b/html/donator.php index 2faf870..27926b9 100644 --- a/html/donator.php +++ b/html/donator.php @@ -26,7 +26,6 @@ } ?>
- A new thread can be only started under a sub-section. If you go on the forum, or even under a section, you will see that the button “New thread” is not active and if you try to click on it, a message will appear saying that you can only start a new thread after you chose a sub-section. + A new thread can only be started under a sub-section. If you go to the forum, or even under a section, you will see that the button “New thread” is not active and if you try to click on it, a message will appear saying that you can only start a new thread after you chose a sub-section.
@@ -32,11 +32,11 @@
- Karma is a way of rating users posts within the forum. The karma rating is displayed as a number in the top of left of each forum post. Users with the bronze Karma medal are allowed to upvote posts and only users with the silver Karma medal can down vote. You earn the medals by completing levels and being active within the forum. Posts with karma of -3 or less are automatically hidden although this isn't the main role of the karma system, see reporting posts below. + Karma is a way to rate users posts within the forum. The karma rating is displayed as a number in the top of left of each forum post. Users with the bronze Karma medal are allowed to upvote posts and only users with the silver Karma medal can down vote. You earn the medals by completing levels and being active within the forum. Posts with karma of -3 or less are automatically hidden although this isn't the main role of the karma system, see reporting posts below.
- You have the ability to watch and unwatch any thread in the forum. At the top of each thread your current preference is displayed and can be changed simply by clicking the button. By posting in a thread you automatically start watching that thread. + You have the ability to watch and unwatch any thread in the forum. At the top of each thread your current preference is displayed and can be simply changed by clicking the button. By posting in a thread you automatically start watching that thread.
@@ -46,4 +46,4 @@ \ No newline at end of file +?> diff --git a/html/files/css/forum.scss b/html/files/css/forum.scss index a9af1b1..eedb7ce 100644 --- a/html/files/css/forum.scss +++ b/html/files/css/forum.scss @@ -64,6 +64,12 @@ } } } + + .forum-stats-label { + color: white; + width: 80px; + display: inline-block; + } } diff --git a/html/files/css/guest_landing.scss b/html/files/css/guest_landing.scss index ccb6ebf..acbfb73 100644 --- a/html/files/css/guest_landing.scss +++ b/html/files/css/guest_landing.scss @@ -1,5 +1,5 @@ body { - background: url('/files/images/bg_landing_2.png') no-repeat center top; + background: url('https://hackthis-10af.kxcdn.com/files/images/bg_landing_2.png') no-repeat center top; } body.theme-light { @@ -150,4 +150,4 @@ section.features { opacity: 1; transform: scale(1); } -} \ No newline at end of file +} diff --git a/html/files/css/icomoon.css b/html/files/css/icomoon.css index 1f71b63..dbc7742 100644 --- a/html/files/css/icomoon.css +++ b/html/files/css/icomoon.css @@ -1,10 +1,10 @@ @font-face { font-family: 'icomoon'; - src:url('/files/fonts/icomoon.eot'); - src:url('/files/fonts/icomoon.eot?#iefix') format('embedded-opentype'), - url('/files/fonts/icomoon.woff') format('woff'), - url('/files/fonts/icomoon.ttf') format('truetype'), - url('/files/fonts/icomoon.svg#icomoon') format('svg'); + src:url('https://hackthis-10af.kxcdn.com/files/fonts/icomoon.eot'); + src:url('https://hackthis-10af.kxcdn.com/files/fonts/icomoon.eot?#iefix') format('embedded-opentype'), + url('https://hackthis-10af.kxcdn.com/files/fonts/icomoon.woff') format('woff'), + url('https://hackthis-10af.kxcdn.com/files/fonts/icomoon.ttf') format('truetype'), + url('https://hackthis-10af.kxcdn.com/files/fonts/icomoon.svg#icomoon') format('svg'); font-weight: normal; font-style: normal; } diff --git a/html/files/css/interaction.scss b/html/files/css/interaction.scss index c4cd718..5f8d835 100644 --- a/html/files/css/interaction.scss +++ b/html/files/css/interaction.scss @@ -943,11 +943,12 @@ div.lite-pagination { } @media (max-width: 768px) { + .modal-overlay, .modal-overlay .modal { + position: absolute; top: 0; left: 0; right: 0; - bottom: 0; width: auto; } } diff --git a/html/files/css/main.scss b/html/files/css/main.scss index ec94c75..a01c6af 100644 --- a/html/files/css/main.scss +++ b/html/files/css/main.scss @@ -14,7 +14,7 @@ body { color: $black; repeat: no-repeat; - image: url("https://d3t63m1rxnixd2.cloudfront.net/files/images/bg2.png"); + image: url("https://hackthis-10af.kxcdn.com/files/images/bg2.png"); position: center -32px; } @@ -373,7 +373,8 @@ blockquote { } } &.medal-error { - background: darken($red, 10%); + background: $red; + color: white; } } diff --git a/html/files/images/ad_banner_nullsec.png b/html/files/images/ad_banner_nullsec.png deleted file mode 100644 index 925632d..0000000 Binary files a/html/files/images/ad_banner_nullsec.png and /dev/null differ diff --git a/html/files/images/ad_banner_walker.png b/html/files/images/ad_banner_walker.png deleted file mode 100644 index 20381bd..0000000 Binary files a/html/files/images/ad_banner_walker.png and /dev/null differ diff --git a/html/files/images/ads/ad_donate.png b/html/files/images/ads/ad_donate.png deleted file mode 100644 index 65a6e70..0000000 Binary files a/html/files/images/ads/ad_donate.png and /dev/null differ diff --git a/html/files/images/ads/ad_facebook.png b/html/files/images/ads/ad_facebook.png deleted file mode 100644 index 4c0bc2b..0000000 Binary files a/html/files/images/ads/ad_facebook.png and /dev/null differ diff --git a/html/files/images/ads/ad_twitter.png b/html/files/images/ads/ad_twitter.png deleted file mode 100644 index fb09307..0000000 Binary files a/html/files/images/ads/ad_twitter.png and /dev/null differ diff --git a/html/files/js/admin.js b/html/files/js/admin.js index 073a586..c485342 100644 --- a/html/files/js/admin.js +++ b/html/files/js/admin.js @@ -6,23 +6,44 @@ $(function() {
+
2-Step Authentication adds an extra layer of security to your HackThis Account, drastically reducing the chances of having your account stolen. To break into an account with 2-Step Authentication, bad guys would not only have to know your username and password, they'd also have to get a hold of your phone.
+ +Google Authenticator is a product developed by Google which allows the user to make use of TOTP.
When enabled you will be asked for a code from your Google Authenicator app on your mobile device when logging into HackThis. It is available for Apple and Android devices
Your Google Authenticator QR Code
+Please scan the below QR code using your Google Authenticator App to add the account
+Google Authenticator Disabled
It is now ok for you to remove your HackThis account from your Google Authenticator app.
The HackThis!! ticker is a list of the most popular user submitted links. - Anyone can submit a link to the list but currently all submissions need to approved by a moderator.
+ Anyone can submit a link to the list but currently all submissions need to be approved by a moderator. @@ -123,4 +123,4 @@ \ No newline at end of file +?> diff --git a/install_hackthis_ubuntu.sh b/install_hackthis_ubuntu.sh index 770ce3b..68fb49f 100755 --- a/install_hackthis_ubuntu.sh +++ b/install_hackthis_ubuntu.sh @@ -17,7 +17,7 @@ Caption function isPackageInstalled { packagePolicyOutput=`apt-cache policy $1` - echo $packagePolicyOutput | grep Installed > /dev/null + echo $packagePolicyOutput | grep -v none | grep Installed > /dev/null return $? } @@ -92,7 +92,7 @@ ls README.md > /dev/null 2>&1 || { git_root_dir=`pwd` # Package installation -required_packages="apache2 php5 libapache2-mod-php5 mysql-server php5-mysql" +required_packages="apache2 php5 libapache2-mod-php5 mysql-server php5-mysql php5-ldap" echo Checking installed packages for package in $required_packages; do installMissingPackages $package @@ -177,7 +177,6 @@ mysql -u $mysql_user $pass_clause < setup.sql echo -e "\t HackThis database was initialized" # Setting up directories and permissions - echo Setting up directories and permissions mkdir -p html/files/css/min{,/light,/dark} chmod 777 html/files/css/min{,/light,/dark} @@ -190,4 +189,9 @@ chmod 777 files/cache{,/twig} mkdir -p files/logs chmod 777 files/logs +# Enabeling ModRewrite to solve RewriteEngine issue +echo Enabeling Mod_Rewrite +a2enmod rewrite +service apache2 restart + echo Done! diff --git a/install_hackthis_windows.sh b/install_hackthis_windows.sh index 7b1ac8c..3301531 100755 --- a/install_hackthis_windows.sh +++ b/install_hackthis_windows.sh @@ -118,16 +118,16 @@ ls /c/wamp/wampmanager.exe > /dev/null 2>&1 || { } echo -e "\t WAMP installation found in C:\wamp" -# Make sure that sh.exe is found in C:\Program Files (x86)\Git\bin\sh.exe -ls /c/Program\ Files\ \(x86\)/Git/bin/sh.exe > /dev/null 2>&1 || { +# Make sure that sh.exe is found in C:\Program Files\Git\bin\sh.exe +ls /c/Program\ Files/Git/bin/sh.exe > /dev/null 2>&1 || { echo echo -e "\t Git Bash was not installed in the default location." - echo -e '\t Installation requires sh.exe to be found at C:\Program Files (x86)\Git\\bin\sh.exe.' + echo -e '\t Installation requires sh.exe to be found at C:\Program Files\Git\\bin\sh.exe.' echo echo -e "\e[0;31mAborting...\e[m" waitForEnterAndExit } -echo -e "\t Git Bash installation found in C:\Program Files (x86)/Git/bin/sh.exe" +echo -e "\t Git Bash installation found in C:\Program Files/Git/bin/sh.exe" # Make sure mysql client is installed in C:\wamp\bin\mysql\[mysql version\bin\mysql.exe mysql_client=`ls /c/wamp/bin/mysql/*/bin/mysql.exe 2>/dev/null`; diff --git a/node/server.js b/node/server.js deleted file mode 100644 index 7ba5905..0000000 --- a/node/server.js +++ /dev/null @@ -1,231 +0,0 @@ -var http = require('http'); -var app = require('http').createServer(handler) -, io = require('socket.io').listen(app, { log: false }) -//, io = require('socket.io').listen(app) -, url = require('url') -, qs = require('querystring'); - -var feed_log = []; - -var _irc = require('irc'); -var connections = []; -var irc_log = []; -var irc_clients = []; -var global_irc; - -var irc_topic = "Global chat"; -var irc_names = {}; - -app.listen(8080); - - -var api_key = 'PML758e0UW4oqT8js9vAg5SZY3w6JgkJ'; - -io.sockets.on('connection', function (socket) { - //Send feed history - socket.emit('feed', feed_log.slice(-10)); - - socket.on('chat_register', function (data) { - if (data.nick && data.key) - connectIRC(socket, data.nick, data.key); - }); - - socket.on('disconnect', function() { - disconnectSocket(socket); - }); -}); - -function handler(req, res) { - if (req.method == 'POST') { - var query = url.parse(req.url, true).query; - if (query.api == api_key) { - var body = ''; - req.on('data', function (data) { - body += data; - }); - req.on('end',function(){ - var POST = qs.parse(body); - console.log(POST); - feed(POST); - res.writeHead(200); - }); - } - } - - res.end(); -} - - -function feed(data) { - feed_log.push(data); - io.sockets.emit('feed', data); -} - - - -global_irc = new _irc.Client('irc.hackthis.co.uk', 'ChatBot', { - userName: 'ChatBot', - realName: 'ChatBot', - port: 6697, - secure: true, - selfSigned: true, - certExpired: true, - channels: ['#nukeland'] -}); - -global_irc.setMaxListeners(0); -global_irc.addListener('message', function (nick, chan, message) { - irc_log.push({nick: nick, chan: chan, msg: message}); -}).addListener('join', function (chan, nick, message) { - irc_log.push({nick: nick, chan: chan, info: 'join'}); -}).addListener('part', function (chan, nick, reason, message) { - irc_log.push({nick: nick, info: 'part'}); -}).addListener('quit', function (nick, reason, channels, message) { - irc_log.push({nick: nick, info: 'part'}); -}).addListener('topic', function (chan, topic, nick) { - irc_topic = topic; -}).addListener('names', function (chan, names) { - irc_names = names; -}); - - -function connectIRC(socket, nick, key) { - console.log(key + ' - New connection'); - socket.nick = nick; - socket.key = key; - - //lookup existing connection - if (socket.key in irc_clients) { - irc_clients[socket.key].connections++; - socket.irc = irc_clients[socket.key].client; - } else { - console.log(key + ' - Creating IRC connection'); - socket.irc = new _irc.Client('irc.hackthis.co.uk', nick, { - userName: nick, - realName: nick, - channels: ['#nukeland'] - }); - - irc_clients[socket.key] = {connections: 1, client: socket.irc}; - } - - // for (var i = 0; i < connections.length; i++) { - // if (connections[i].key == socket.key && connections[i].nick == socket.nick) { - // console.log('IRC user already active'); - // socket.irc = connections[i].irc; - // break; - // } - // } - - // if (!socket.irc) { - // console.log('Creating new IRC user'); - // socket.irc = new _irc.Client('irc.hackthis.co.uk', nick, { - // userName: nick, - // realName: nick, - // channels: ['#nukeland'] - // }); - // } - - //redefine handler - socket.irc.setMaxListeners(0); - socket.irc.addListener('message', function (nick, chan, message) { - // Check for ctcp - var action = message.match(/^\u0001ACTION (.*)\u0001$/); - if (action) { - socket.emit('chat', {nick: nick, chan: chan, msg: action[1], info: 'action'}); - } else { - socket.emit('chat', {nick: nick, chan: chan, msg: message}); - } - }).addListener('join', function (chan, nick, message) { - socket.emit('chat', {nick: nick, chan: chan, msg: message, info: 'join'}); - }).addListener('part', function (chan, nick, reason, message) { - socket.emit('chat', {nick: nick, chan: chan, msg: message, info: 'part'}); - }).addListener('quit', function (nick, reason, channels, message) { - socket.emit('chat', {nick: nick, info: 'part'}); - }).addListener('registered', function (message) { - socket.emit('chat', {info: 'registered'}); - }).addListener('topic', function (chan, topic, nick) { - socket.emit('chat', {nick: nick, topic: topic, info: 'topic'}); - }); - - socket.on('chat', function (data) { - if (data.msg.substring(0, 4) == "/me ") - socket.irc.action('#nukeland', data.msg.substring(4)); - else - socket.irc.say('#nukeland', data.msg); - }); - - connections.push(socket); - - //Send history - socket.emit('chat', irc_log.slice(-25)); - socket.emit('chat', {topic: irc_topic, info: 'topic'}); - socket.emit('chat', {names: irc_names, info: 'names'}); -} - -function disconnectSocket(socket) { - key = socket.key; - if (key in irc_clients) { - console.log(key + ' - Client disconnected: ' + irc_clients[key].connections + ' connections'); - } else { - console.log(key + ' - Client disconnected'); - } - - (function(key) { - setTimeout(function(){disconnectIRC(key)}, 15000); - })(key); - /*setTimeout(function() { - // console.log('Deleting connection...'); - - // irc = socket.irc; - // connections.splice(connections.indexOf(socket), 1); - - // if (!irc) - // return; - - // if (connections.length > 0) { - // var n = 0; - // connections.forEach(function(item) { - // if (item.irc === irc) - // n++; - // }); - - // if (n === 0) { - // console.log('Disconnecting from IRC'); - // irc.disconnect(); - // } else { - // console.log('IRC connection in use'); - // } - // } else { - // console.log('No other connections'); - // irc.disconnect(); - // } - - console.log(key + ' - Timeout'); - if (key in irc_clients) { - irc_clients[key].connections--; - console.log(key + ' - ' + irc_clients[key].connections + ' connections'); - - if (irc_clients[key].connections == 0) { - irc_clients[key].client.disconnect(); - delete irc_clients[key]; - } - } - }, 15000, key);*/ - - connections.splice(connections.indexOf(socket), 1); -} - -function disconnectIRC(key) { - if (key in irc_clients) { - irc_clients[key].connections--; - console.log(key + ' - Timeout: ' + irc_clients[key].connections + ' connections'); - - if (irc_clients[key].connections == 0) { - irc_clients[key].client.disconnect(); - delete irc_clients[key]; - } - } else { - console.log(key + ' - Timeout'); - } -} \ No newline at end of file diff --git a/node/test.html b/node/test.html deleted file mode 100644 index dc96e9f..0000000 --- a/node/test.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - -