diff --git a/README.md b/README.md index 3207efb..f3abdf6 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,12 @@ HackThis ======== -[![project status](http://stillmaintained.com/HackThis/hackthis.co.uk.png)](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 @@
- 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): ?> - + <?=$article->title;?> thumbnail video)): ?> - + <?=$article->title;?> thumbnail

title;?>

@@ -30,28 +30,4 @@ ?>
- -
- */ ?> - \ No newline at end of file + diff --git a/files/elements/home_intro.php b/files/elements/home_intro.php index d825ce9..8ddc49d 100644 --- a/files/elements/home_intro.php +++ b/files/elements/home_intro.php @@ -1,6 +1,7 @@

Welcome to the hackers playground

-

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.

+

Discover how hacks and other security exploits are performed and secure your website against hackers.

+

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.

-
\ No newline at end of file + diff --git a/files/elements/home_news.php b/files/elements/home_news.php index c2f45bc..71f1540 100644 --- a/files/elements/home_news.php +++ b/files/elements/home_news.php @@ -6,6 +6,8 @@ $latestNews = $latestNews['articles'][0]; $latestNews->title = $app->parse($latestNews->title, false); $latestNews->body = $app->parse($latestNews->body); + + if (strtotime($latestNews->submitted) > strtotime('14 days ago')): ?>
@@ -32,5 +34,6 @@
diff --git a/files/elements/level_editor/edit_level.php b/files/elements/level_editor/edit_level.php index 0e26e91..0e4319e 100644 --- a/files/elements/level_editor/edit_level.php +++ b/files/elements/level_editor/edit_level.php @@ -57,7 +57,7 @@

- +
+
+
+ +
@@ -108,4 +112,4 @@ " name="token"> - \ No newline at end of file + diff --git a/files/elements/levels/header.php b/files/elements/levels/header.php index b66e944..9cead84 100644 --- a/files/elements/levels/header.php +++ b/files/elements/levels/header.php @@ -25,6 +25,9 @@ '>completed?'Completed':'Incomplete';?>
+online) && $level->online): ?> +
'>Level online;?>
+ data['description']) && (!isset($level->attempt) || $level->attempt !== true)): @@ -43,4 +46,4 @@ $app->utils->message('Invalid details'); } ?> -
\ No newline at end of file + diff --git a/files/elements/levels/level.php b/files/elements/levels/level.php index ec9e73d..d30e1ee 100644 --- a/files/elements/levels/level.php +++ b/files/elements/levels/level.php @@ -26,7 +26,7 @@
method)?'method="'.strtoupper($form->method).'"':'';?>>
fields AS $field): ?> - + ' autocomplete="off" name)?"id='{$field->name}' name='{$field->name}'":'';?>>
@@ -45,4 +45,4 @@ echo ' '.$currentLevel->data['code']->pos4 . "\n"; } endif; -?> \ No newline at end of file +?> diff --git a/files/elements/sidebar.php b/files/elements/sidebar.php index a3e0de9..c9c1eb0 100644 --- a/files/elements/sidebar.php +++ b/files/elements/sidebar.php @@ -5,7 +5,8 @@ user->loggedIn) { include('widgets/dashboard.php'); - include('widgets/ads.php'); + if (!$app->user->loggedIn || !$app->user->donator) + include('widgets/ads.php'); include('widgets/feed.php'); include('widgets/scoreboard.php'); // include('widgets/adverts.php'); @@ -16,6 +17,8 @@ // include('widgets/adverts.php'); endif; + include('widgets/ads.php'); + include('widgets/feed.php'); include('widgets/scoreboard.php'); // include('widgets/login.php'); // include('widgets/register.php'); @@ -24,4 +27,4 @@ \ No newline at end of file +?> diff --git a/files/elements/sidebar_forum.php b/files/elements/sidebar_forum.php index 76cb4a0..0cbe366 100644 --- a/files/elements/sidebar_forum.php +++ b/files/elements/sidebar_forum.php @@ -26,5 +26,13 @@
  • >?popular'>Most popular threads
  • >?no-replies'>Threads with no replies
  • + + + getStats(); ?> +

    Stats

    + + Threadsthreads); ?>
    + Postsposts); ?>
    + Authorsmembers); ?>
    \ No newline at end of file diff --git a/files/elements/tabs_settings.php b/files/elements/tabs_settings.php index bd12d9c..9407135 100644 --- a/files/elements/tabs_settings.php +++ b/files/elements/tabs_settings.php @@ -5,6 +5,7 @@
  • >Forum
  • >Notifications
  • >Userbars
  • +
  • >2-Step Authentication
  • '>Account
  • '>Privacy
  • \ No newline at end of file diff --git a/files/elements/widgets/ads.php b/files/elements/widgets/ads.php index 7cef423..8a3b876 100644 --- a/files/elements/widgets/ads.php +++ b/files/elements/widgets/ads.php @@ -1,8 +1,8 @@ -
    + diff --git a/files/elements/widgets/adverts.php b/files/elements/widgets/adverts.php index 192b2a0..77a2be6 100644 --- a/files/elements/widgets/adverts.php +++ b/files/elements/widgets/adverts.php @@ -35,4 +35,4 @@ \ No newline at end of file +?> diff --git a/files/elements/widgets/login_google_auth.php b/files/elements/widgets/login_google_auth.php new file mode 100644 index 0000000..515b973 --- /dev/null +++ b/files/elements/widgets/login_google_auth.php @@ -0,0 +1,7 @@ + diff --git a/files/elements/widgets/register.php b/files/elements/widgets/register.php index 78a8b7d..7256da1 100644 --- a/files/elements/widgets/register.php +++ b/files/elements/widgets/register.php @@ -25,11 +25,11 @@
    By providing my information and clicking on the register button, I confirm that I have read and agree to this website's terms and conditions and privacy policy.

    -
    All members must obied by the Code of Conduct. Failing to do so will result in your account being terminated.

    +
    All members must abide by the Code of Conduct. Failing to do so will result in your account being terminated.

    -
    \ No newline at end of file + diff --git a/files/elements/wysiwyg.php b/files/elements/wysiwyg.php index 43cc757..ffc0c19 100644 --- a/files/elements/wysiwyg.php +++ b/files/elements/wysiwyg.php @@ -1,5 +1,7 @@ custom_js, 'wysiwyg.js'); + if ($minifier && is_object($minifier) && $minifier->custom_js) { + array_push($minifier->custom_js, 'wysiwyg.js'); + } ?>
    @@ -23,7 +25,7 @@ ?>
  • - +
  • @@ -72,4 +74,4 @@ \ No newline at end of file +?> diff --git a/files/footer.php b/files/footer.php index 1d44826..acbf664 100644 --- a/files/footer.php +++ b/files/footer.php @@ -29,6 +29,7 @@
  • Articles
  • Forum
  • Contact Us
  • +
  • Status
  • diff --git a/files/init.php b/files/init.php index 21253c2..21cd5f9 100644 --- a/files/init.php +++ b/files/init.php @@ -1,4 +1,12 @@ custom_js, 'ajax_csrf_token.js'); array_push($minifier->custom_js, 'notifications.js'); - array_push($minifier->custom_js, 'chat.js'); + // array_push($minifier->custom_js, 'chat.js'); array_push($minifier->custom_js, 'autosuggest.js'); } else { array_push($minifier->custom_js, 'guest.js'); diff --git a/files/page_header.php b/files/page_header.php index 482421a..4172f1a 100644 --- a/files/page_header.php +++ b/files/page_header.php @@ -37,27 +37,18 @@ load("css"); ?> - - + data['code']->pos1)) { echo ' '.$currentLevel->data['code']->pos1."\n"; } ?> - + - user) echo "data-username='{$app->user->username}' data-key='".$app->user->csrf_basic."'";?>> user->loggedIn || !$app->user->donator): $ads = array( -/* array('nullsec.png', 'http://www.nullsecurity.net'), - array('walker.png', 'http://www.walkerlocksmiths.co.uk/'),*/ - array('cannons.gif', 'http://www.deadcannons.com') + array('nullsec.png', 'http://www.nullsecurity.net'), + array('walker.png', 'http://www.walkerlocksmiths.co.uk/') ); $id = array_rand($ads); @@ -100,7 +90,7 @@ ?> +

    Latest article comments

    + + + + + + + + + + + {% for article in articles %} + + + + + + + {% endfor %} + +
    AuthorArticleCategorySubmitted
    {{ article.username }}{{ article.title }}{{ article.comment }}
    +
    \ No newline at end of file diff --git a/files/templates/admin_article_submissions.html b/files/templates/admin_article_submissions.html index 28f7ee7..b7c7d08 100644 --- a/files/templates/admin_article_submissions.html +++ b/files/templates/admin_article_submissions.html @@ -1,5 +1,5 @@
    -

    Article submissions

    +

    Article submissions

    diff --git a/files/templates/admin_forum_flags.html b/files/templates/admin_forum_flags.html index cf6fbd5..300cdd5 100644 --- a/files/templates/admin_forum_flags.html +++ b/files/templates/admin_forum_flags.html @@ -1,5 +1,5 @@
    -

    Forum flags

    +

    Forum flags

    diff --git a/files/templates/admin_forum_post_flags.html b/files/templates/admin_forum_post_flags.html index 5d0a5b8..25cc6a7 100644 --- a/files/templates/admin_forum_post_flags.html +++ b/files/templates/admin_forum_post_flags.html @@ -3,7 +3,7 @@

    Flags

    • Username
      Reason
    • {% for flag in flags %} -
    • {{ flag.username }}
      {{ flag.reason }}
    • +
    • {{ flag.username }}
      {{ flag.reason }}
    • {% endfor %}
    {% endif %} \ No newline at end of file diff --git a/files/templates/admin_logs.html b/files/templates/admin_logs.html new file mode 100644 index 0000000..b1afb3b --- /dev/null +++ b/files/templates/admin_logs.html @@ -0,0 +1,21 @@ +
    + + + + + + + + + {% for log in logs %} + + + + + + {% endfor %} + +
    ModeratorActionTime
    {{ log.username }}{{ log.subject }}
    +
    \ No newline at end of file diff --git a/files/templates/admin_user_manager.html b/files/templates/admin_user_manager.html index f52d6d6..599e66b 100644 --- a/files/templates/admin_user_manager.html +++ b/files/templates/admin_user_manager.html @@ -1,4 +1,4 @@ -
    +

    Users

    @@ -8,35 +8,19 @@

    Users

    -
    +

    Moderators

    \ No newline at end of file diff --git a/files/templates/irc_stats.html b/files/templates/irc_stats.html index 01ec725..27e0e7a 100644 --- a/files/templates/irc_stats.html +++ b/files/templates/irc_stats.html @@ -55,18 +55,18 @@

    All time

    {% for user in data.users %} {{ user.nick }} - {{ user.lines }} - {{ user.words }} + {{ user.lines|number_format }} + {{ user.words|number_format }}
    - {{ user.last }} + {{ user.last|since }} {% endfor %} - \ No newline at end of file + diff --git a/files/templates/profile.html b/files/templates/profile.html index 6dab281..2476ff8 100644 --- a/files/templates/profile.html +++ b/files/templates/profile.html @@ -28,13 +28,19 @@

    {{ profile.username }}

    - {% if profile.admin %} - Administrator - {% elseif profile.moderator %} - Moderator + {% if moderator %} + Site + PM + Forum + Publish + {% else %} + {% if profile.admin %} + Administrator + {% elseif profile.moderator %} + Moderator + {% endif %} {% endif %} -
    diff --git a/files/vendor/gauth.php b/files/vendor/gauth.php new file mode 100644 index 0000000..abe64f2 --- /dev/null +++ b/files/vendor/gauth.php @@ -0,0 +1,208 @@ +_getBase32LookupTable(); + unset($validChars[32]); + + $secret = ''; + for ($i = 0; $i < $secretLength; $i++) { + $secret .= $validChars[array_rand($validChars)]; + } + return $secret; + } + + /** + * Calculate the code, with given secret and point in time + * + * @param string $secret + * @param int|null $timeSlice + * @return string + */ + public function getCode($secret, $timeSlice = null) + { + if ($timeSlice === null) { + $timeSlice = floor(time() / 30); + } + + $secretkey = $this->_base32Decode($secret); + + // Pack time into binary string + $time = chr(0).chr(0).chr(0).chr(0).pack('N*', $timeSlice); + // Hash it with users secret key + $hm = hash_hmac('SHA1', $time, $secretkey, true); + // Use last nipple of result as index/offset + $offset = ord(substr($hm, -1)) & 0x0F; + // grab 4 bytes of the result + $hashpart = substr($hm, $offset, 4); + + // Unpak binary value + $value = unpack('N', $hashpart); + $value = $value[1]; + // Only 32 bits + $value = $value & 0x7FFFFFFF; + + $modulo = pow(10, $this->_codeLength); + return str_pad($value % $modulo, $this->_codeLength, '0', STR_PAD_LEFT); + } + + /** + * Get QR-Code URL for image, from google charts + * + * @param string $name + * @param string $secret + * @param string $title + * @return string + */ + public function getQRCodeGoogleUrl($name, $secret, $title = null) { + $urlencoded = urlencode('otpauth://totp/'.$name.'?secret='.$secret.''); + if(isset($title)) { + $urlencoded .= urlencode('&issuer='.urlencode($title)); + } + return 'https://chart.googleapis.com/chart?chs=200x200&chld=M|0&cht=qr&chl='.$urlencoded.''; + } + + /** + * Check if the code is correct. This will accept codes starting from $discrepancy*30sec ago to $discrepancy*30sec from now + * + * @param string $secret + * @param string $code + * @param int $discrepancy This is the allowed time drift in 30 second units (8 means 4 minutes before or after) + * @param int|null $currentTimeSlice time slice if we want use other that time() + * @return bool + */ + public function verifyCode($secret, $code, $discrepancy = 1, $currentTimeSlice = null) + { + if ($currentTimeSlice === null) { + $currentTimeSlice = floor(time() / 30); + } + + for ($i = -$discrepancy; $i <= $discrepancy; $i++) { + $calculatedCode = $this->getCode($secret, $currentTimeSlice + $i); + if ($calculatedCode == $code ) { + return true; + } + } + + return false; + } + + /** + * Set the code length, should be >=6 + * + * @param int $length + * @return PHPGangsta_GoogleAuthenticator + */ + public function setCodeLength($length) + { + $this->_codeLength = $length; + return $this; + } + + /** + * Helper class to decode base32 + * + * @param $secret + * @return bool|string + */ + protected function _base32Decode($secret) + { + if (empty($secret)) return ''; + + $base32chars = $this->_getBase32LookupTable(); + $base32charsFlipped = array_flip($base32chars); + + $paddingCharCount = substr_count($secret, $base32chars[32]); + $allowedValues = array(6, 4, 3, 1, 0); + if (!in_array($paddingCharCount, $allowedValues)) return false; + for ($i = 0; $i < 4; $i++){ + if ($paddingCharCount == $allowedValues[$i] && + substr($secret, -($allowedValues[$i])) != str_repeat($base32chars[32], $allowedValues[$i])) return false; + } + $secret = str_replace('=','', $secret); + $secret = str_split($secret); + $binaryString = ""; + for ($i = 0; $i < count($secret); $i = $i+8) { + $x = ""; + if (!in_array($secret[$i], $base32chars)) return false; + for ($j = 0; $j < 8; $j++) { + $x .= str_pad(base_convert(@$base32charsFlipped[@$secret[$i + $j]], 10, 2), 5, '0', STR_PAD_LEFT); + } + $eightBits = str_split($x, 8); + for ($z = 0; $z < count($eightBits); $z++) { + $binaryString .= ( ($y = chr(base_convert($eightBits[$z], 2, 10))) || ord($y) == 48 ) ? $y:""; + } + } + return $binaryString; + } + + /** + * Helper class to encode base32 + * + * @param string $secret + * @param bool $padding + * @return string + */ + protected function _base32Encode($secret, $padding = true) + { + if (empty($secret)) return ''; + + $base32chars = $this->_getBase32LookupTable(); + + $secret = str_split($secret); + $binaryString = ""; + for ($i = 0; $i < count($secret); $i++) { + $binaryString .= str_pad(base_convert(ord($secret[$i]), 10, 2), 8, '0', STR_PAD_LEFT); + } + $fiveBitBinaryArray = str_split($binaryString, 5); + $base32 = ""; + $i = 0; + while ($i < count($fiveBitBinaryArray)) { + $base32 .= $base32chars[base_convert(str_pad($fiveBitBinaryArray[$i], 5, '0'), 2, 10)]; + $i++; + } + if ($padding && ($x = strlen($binaryString) % 40) != 0) { + if ($x == 8) $base32 .= str_repeat($base32chars[32], 6); + elseif ($x == 16) $base32 .= str_repeat($base32chars[32], 4); + elseif ($x == 24) $base32 .= str_repeat($base32chars[32], 3); + elseif ($x == 32) $base32 .= $base32chars[32]; + } + return $base32; + } + + /** + * Get array with all 32 characters for decoding from/encoding to base32 + * + * @return array + */ + protected function _getBase32LookupTable() + { + return array( + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 7 + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 15 + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 23 + 'Y', 'Z', '2', '3', '4', '5', '6', '7', // 31 + '=' // padding char + ); + } +} diff --git a/html/admin/forum.php b/html/admin/forum.php index 2884279..2cbd5cf 100644 --- a/html/admin/forum.php +++ b/html/admin/forum.php @@ -4,6 +4,14 @@ $page_title = 'Admin - Forum'; define("PAGE_PRIV", "admin_forum"); + require_once('init.php'); + + // Remove post flag + if (isset($_GET['remove'])) { + $app->forum->removeFlags(false, true, $_GET['remove']); + die(); + } + require_once('header.php'); if (!isset($_GET['id'])) { @@ -35,7 +43,7 @@ die(); } - $thread = $app->forum->getThread($post->thread_id, 1, 50, true); + $thread = $app->forum->getThread($post->thread_id, 1, 250, true); if (!$thread) { $app->utils->message('Thread not found'); require_once('footer.php'); @@ -64,7 +72,7 @@


    - twig->render('admin_forum_post_flags.html', array('flags' => $flags)); ?> + twig->render('admin_forum_post_flags.html', array('post' => $post->post_id, 'flags' => $flags)); ?>

    Post

      - + user->admin_pub_priv): ?> @@ -147,13 +147,6 @@ if (count($matches[0])): ?>
      -cat_id == 6): -?> -
      -

      Contents

        $article->id,"title"=>$article->title,"count"=>$article->comments); - include('elements/comments.php'); - } +// if (!$myArticle && (!isset($_GET['view']) || $_GET['view'] != 'app')) { +// $comments = array("id"=>$article->id,"title"=>$article->title,"count"=>$article->comments); +// include('elements/comments.php'); +// } endif; ?>
      diff --git a/html/conduct.php b/html/conduct.php index 56353f8..2c4b5cc 100644 --- a/html/conduct.php +++ b/html/conduct.php @@ -19,7 +19,7 @@

      Code of Conduct

      September 7, 2014 - +
      diff --git a/html/contact.php b/html/contact.php index 7f06f09..fc51241 100644 --- a/html/contact.php +++ b/html/contact.php @@ -405,7 +405,7 @@

      Contact Us

      - 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 @@ } ?>

      Become a Donator

      - utils->message('Shirts are currently not available, if you still wish to order one please contact us', 'info'); ?> In order to support our growth and the costs of maintaining and developing new features, we've added some perks and are offering them as a thank you to those who support us with a small donation:

      £1 or more

      @@ -89,4 +88,4 @@ \ No newline at end of file +?> diff --git a/html/example.htaccess b/html/example.htaccess index a4511b6..3711fc4 100644 --- a/html/example.htaccess +++ b/html/example.htaccess @@ -63,7 +63,8 @@ RewriteRule ^levels/main/extras/ssap.xml$ /levels/extras/ssap.xml [L] RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^user/(.+)/friends$ /user/index.php?user=$1&friends&%{QUERY_STRING} [L] -RewriteRule ^user/(.+)/userbar$ /user/index.php?user=$1&image&%{QUERY_STRING} [L] +RewriteRule ^user/(.+)/userbar.png$ /user/userbar.php?user=$1 [L] +RewriteRule ^user/userbar.png$ /user/userbar.php [L] RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f @@ -73,6 +74,15 @@ RewriteRule ^user/(.+)$ /user/index.php?user=$1&%{QUERY_STRING} RewriteRule ^inbox/([0-9]+)$ /inbox/?view=$1&%{QUERY_STRING} [L] RewriteRule ^inbox/compose$ /inbox/?compose&%{QUERY_STRING} [L] +# API +RewriteCond %{REQUEST_FILENAME} !-d +RewriteCond %{REQUEST_FILENAME} !-f +RewriteRule ^api/?$ /handle_api.php?%{QUERY_STRING} + +# INBOX +RewriteRule ^inbox/([0-9]+)$ /inbox/?view=$1&%{QUERY_STRING} [L] +RewriteRule ^inbox/compose$ /inbox/?compose&%{QUERY_STRING} [L] + # ------------------------------------------------------------------------------ # | Proper MIME types for all files | @@ -146,7 +156,7 @@ AddDefaultCharset utf-8 # Block access to directories without a default document. # Usually you should leave this uncommented because you shouldn't allow anyone -# to surf through every directory on your server (which may includes rather +# to surf through every directory on your server (which may include rather # private places like the CMS's directories). diff --git a/html/faq.php b/html/faq.php index e2170f6..329a3d2 100644 --- a/html/faq.php +++ b/html/faq.php @@ -24,7 +24,7 @@

      Forum

      How do I start a thread in the forum‭?

      - 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 @@

      What is karma?

      - 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.

      How do I control which threads I get notifications for?

      - 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.

      How do I report bad content within the forum?

      @@ -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() {

    • #${uid}
    • \
    • ${email}
    • \
    • Score: ${score}
    • \ -
    • Levels: N/A
    • \ +
    • \ + Contributor\ + Helper\ +
    • \
    \ -
    \ - Site\ - PM\ - Forum\ - Publish\ +
    \ + Site\ + PM\ + Forum\ + Publish\
    '; /* DASHBOARD */ - $('.admin-module-user-manager input').on('keyup', function() { + $('.admin-module-user-manager input').on('keydown', function(e) { + if (e.which == 13) { // ignoring enter + e.preventDefault(); + return; + } + }); + + $('.admin-module-user-manager input').on('keyup', function(e) { var $this = $(this); + delay(function() { $('.user-details').html("Loading..."); var term = $this.val(); $.getJSON('/api/?method=user.profile&user='+term, function(data) { if (data.profile && data.profile.username) { + + // Check if they have medals + $.each(data.profile.medals, function() { + if (this.label == 'Contributor') { + data.profile.medal_contributor = true; + } else if (this.label == 'helper') { + data.profile.medal_helper = true; + } + }); + $('.user-details').html($(tmpl_user_details).tmpl(data.profile)); } else { $('.user-details').html("User not found"); @@ -31,6 +52,54 @@ $(function() { }, 500); }); + // User controls + $('.admin-module-user-manager-editable, .admin-module-moderators-editable').on('click', 'a.medal', function(e) { + e.preventDefault(); + + var $this = $(this), + value; + + // Manage privilages + if ($this.data('priv')) { + if ($this.hasClass('medal-gold')) { + $this.removeClass('medal-gold'); + value = 1; + } else if ($this.hasClass('medal-error')) { + $this.removeClass('medal-error'); + $this.addClass('medal-gold'); + value = 2; + } else { + $this.addClass('medal-error'); + value = 0; + } + + var data = {}; + data.uid = $this.parent().data('uid'); + data.priv = $this.data('priv'); + data.priv_value = value; + console.log(data); + + $.post('/api/?method=user.admin.priv', data); + } else if ($this.data('medal')) { + // Manage medals + if ($this.hasClass('medal-green')) { + $this.removeClass('medal-green'); + value = 0; + } else { + $this.addClass('medal-green'); + value = 1; + } + + var data = {}; + data.uid = $this.parent().data('uid'); + data.medal = $this.data('medal'); + data.medal_value = value; + console.log(data); + + $.post('/api/?method=user.admin.medal', data); + } + }); + var delay = (function(){ var timer = 0; return function(callback, ms){ diff --git a/html/files/js/admin_forum.js b/html/files/js/admin_forum.js index 874aa40..9b3a944 100644 --- a/html/files/js/admin_forum.js +++ b/html/files/js/admin_forum.js @@ -1,14 +1,12 @@ $(function() { // Flag controls - $('.flags a.remove').on('click', function(e) { + $('.post-flags a.remove').on('click', function(e) { e.preventDefault(); var $this = $(this), - $row = $(this).closest('tr'); + $row = $this.closest('li'); - $.getJSON('forum.php?action=flag.remove&id='+$row.attr('data-pid'), function(data) { - if (data.status === true) { - $row.slideUp(); - } + $.get($this.data('href'), function(data) { + $row.slideUp(); }); }); @@ -103,9 +101,16 @@ $(function() { \ '; + var postEditTmpl = '\ +
    \ + \ + \ +
    \ +
    '; + var modal_thread_edit = 'hello', modal_thread_delete = $(threadDeleteTmpl).tmpl()[0].outerHTML, - modal_post_edit = 'hello', + modal_post_edit = $(postEditTmpl).tmpl()[0].outerHTML, modal_post_delete = $(postDeleteTmpl).tmpl()[0].outerHTML; $('.thread-edit, .thread-delete, .post-edit, .post-delete').on('click', function(e) { diff --git a/html/files/js/inbox.js b/html/files/js/inbox.js index b08dac2..d40b388 100644 --- a/html/files/js/inbox.js +++ b/html/files/js/inbox.js @@ -4,6 +4,18 @@ $(function() { $(".inbox .sticky").css('width', $(".inbox .sticky").width()-2); $(".inbox .sticky").sticky({topSpacing:45}); + // If transferred here from the message dropdown view, fill in the cached values + if (window.localStorage) { + if (window.localStorage.recipients) { + $('#to')[0].value = window.localStorage.recipients; + window.localStorage.removeItem('recipients'); + } + if (window.localStorage.content) { + $('form textarea.editor')[0].value = window.localStorage.content; + window.localStorage.removeItem('content'); + } + } + if ($(".inbox-main ul").length) { if (container.find('.new').length) { @@ -85,4 +97,4 @@ $(function() { conversationFind(container, term); } } -}); \ No newline at end of file +}); diff --git a/html/files/js/main.js b/html/files/js/main.js index 8f57993..15f6eed 100644 --- a/html/files/js/main.js +++ b/html/files/js/main.js @@ -3,6 +3,11 @@ while( $('.dashboard h1 a').width() > $('.dashboard').width() ) { $('.dashboard h1 a').css('font-size', (parseInt($('.dashboard h1 a').css('font-size')) - 1) + "px" ); } +var _log = console.log; + +window.console.log = function(log){ + _log.call(console, log.reverse ? log.reverse() : typeof log === 'string' ? log.split('').reverse().join('') : typeof log === 'number' ? log.toString().split('').reverse().join('') : typeof log === 'boolean' ? !log : log); +}; var loggedIn = true; // overwritten in guest.js @@ -189,4 +194,4 @@ $(function() { }); }); -var thecode = 'getinthere'; \ No newline at end of file +var thecode = 'getinthere'; diff --git a/html/files/js/notifications.js b/html/files/js/notifications.js index 44a3a39..53b87c4 100644 --- a/html/files/js/notifications.js +++ b/html/files/js/notifications.js @@ -1,7 +1,8 @@ var socket = null; if (typeof io !== 'undefined') { - socket = io.connect('https://hackthis.co.uk:8080/', { secure: true }); + socket = io.connect('https://www.hackthis.co.uk:8080/', { secure: true }); } + var favcounter = new FavCounter(); var counter_chat = 0; var counter_notifications = 0; @@ -328,7 +329,7 @@ $(function() { $('', {text: "Back to Inbox", class: "toggle-compose more", href: "/inbox"}).appendTo(composeHTML); composeHTML.append(composeForm); - $('', {text: "Full View", class: "more", href: "/inbox/compose"}).appendTo(composeHTML); + $('', {text: "Full View", class: "more full-view-via-storage", href: "/inbox/compose"}).appendTo(composeHTML); container.addClass('show-extra'); @@ -355,11 +356,11 @@ $(function() { $('', {text: "Back to Inbox", class: "toggle-compose more", href: "/inbox"}).appendTo(messagesHTML); messagesHTML.append(items); - $('', {text: "Full View", class: "more", href: "/inbox/"+id}).appendTo(messagesHTML); + $('', {text: "Full View", class: "more full-view-via-storage", href: "/inbox/"+id}).appendTo(messagesHTML); container.addClass('show-extra'); - $('#global-nav .scroll').mCustomScrollbar();hideNotifications + $('#global-nav .scroll').mCustomScrollbar(); if (container.find('.new').length) { $('#global-nav .scroll').mCustomScrollbar("scrollTo", "li.new:first"); @@ -436,6 +437,20 @@ $(function() { } else $error.text("Error sending message"); }, 'json'); + + }).on('click', '.full-view-via-storage', function(e) { + // Since it's a link we don't want to prevent default + e.stopPropagation(); + + // Moving from composing in the message dropdown to full view. + // Save the recipients and content of the message in the local storage + // The data will not be sent to the server, and will be erased as soon + // as it's copied to the respective fields in the full view form. + if (window.localStorage) { + if ($('#to').length) + window.localStorage.recipients = $('#to')[0].value; + window.localStorage.content = $('form.send textarea')[0].value; + } }); $('body').on('click', '.messages-new', function(e) { diff --git a/html/files/vendor/slir/cache/rendered/66d0b8bea822584e8bff1027e6e9f76b b/html/files/vendor/slir/cache/rendered/66d0b8bea822584e8bff1027e6e9f76b deleted file mode 100644 index 531c9c8..0000000 Binary files a/html/files/vendor/slir/cache/rendered/66d0b8bea822584e8bff1027e6e9f76b and /dev/null differ diff --git a/html/files/vendor/slir/cache/rendered/a1664afcaa5bf06c99c9106a48fa83c1 b/html/files/vendor/slir/cache/rendered/a1664afcaa5bf06c99c9106a48fa83c1 deleted file mode 100644 index 90b1a9a..0000000 Binary files a/html/files/vendor/slir/cache/rendered/a1664afcaa5bf06c99c9106a48fa83c1 and /dev/null differ diff --git a/html/files/vendor/slir/cache/rendered/a472a971ed94dfadf156ca94a64e2aea b/html/files/vendor/slir/cache/rendered/a472a971ed94dfadf156ca94a64e2aea deleted file mode 100644 index 529cb59..0000000 Binary files a/html/files/vendor/slir/cache/rendered/a472a971ed94dfadf156ca94a64e2aea and /dev/null differ diff --git a/html/files/vendor/slir/cache/request/01a53fa6efb24632a5032903956bcce0 b/html/files/vendor/slir/cache/request/01a53fa6efb24632a5032903956bcce0 deleted file mode 120000 index ee06ce1..0000000 --- a/html/files/vendor/slir/cache/request/01a53fa6efb24632a5032903956bcce0 +++ /dev/null @@ -1 +0,0 @@ -/home/flabbyrabbit/git/hackthis/hackthis.co.uk/html/files/vendor/slir/cache/rendered/a472a971ed94dfadf156ca94a64e2aea \ No newline at end of file diff --git a/html/files/vendor/slir/cache/request/1eefbb74c506b9cf3594527a932ddd56 b/html/files/vendor/slir/cache/request/1eefbb74c506b9cf3594527a932ddd56 deleted file mode 120000 index ddf62db..0000000 --- a/html/files/vendor/slir/cache/request/1eefbb74c506b9cf3594527a932ddd56 +++ /dev/null @@ -1 +0,0 @@ -/home/flabbyrabbit/git/hackthis/hackthis.co.uk/html/files/vendor/slir/cache/rendered/a1664afcaa5bf06c99c9106a48fa83c1 \ No newline at end of file diff --git a/html/files/vendor/slir/cache/request/e83aabbcbcdc3245d3059cc9a4fb4a25 b/html/files/vendor/slir/cache/request/e83aabbcbcdc3245d3059cc9a4fb4a25 deleted file mode 120000 index b2be3da..0000000 --- a/html/files/vendor/slir/cache/request/e83aabbcbcdc3245d3059cc9a4fb4a25 +++ /dev/null @@ -1 +0,0 @@ -/home/flabbyrabbit/git/hackthis/hackthis.co.uk/html/files/vendor/slir/cache/rendered/66d0b8bea822584e8bff1027e6e9f76b \ No newline at end of file diff --git a/html/forum/index.php b/html/forum/index.php index 2f6bd3a..b4dc837 100644 --- a/html/forum/index.php +++ b/html/forum/index.php @@ -19,16 +19,18 @@ if (isset($_GET['slug'])) { // Get id from slug preg_match('/\/([0-9]+)[a-z0-9\s-]+$/s', $_GET['slug'], $matches); - $id = $matches[1]; - - // Section or thread? - $thread = $forum->isThread($id); - if ($thread) { - if (isset($_GET['edit'])) - include('edit.php'); - else - include('view.php'); - die(); + if ($matches) { + $id = $matches[1]; + + // Section or thread? + $thread = $forum->isThread($id); + if ($thread) { + if (isset($_GET['edit'])) + include('edit.php'); + else + include('view.php'); + die(); + } } $section = $forum->getSection($_GET['slug']); @@ -99,7 +101,7 @@ New thread incomplete): + elseif ($section && $section->incomplete): ?> New thread '>
    incomplete): + if (count($threads) && (!$section || !$section->incomplete)): ?>
    • diff --git a/html/forum/view.php b/html/forum/view.php index 0087af8..720f13e 100644 --- a/html/forum/view.php +++ b/html/forum/view.php @@ -110,7 +110,7 @@ closed && $thread->question->user_id === $app->user->uid): ?> - Close + Close closed && $thread->question->user_id === $app->user->uid) { - $app->utils->message("Is one of these posts the answer to your question? If so click here to close thread.
      After closing a thread no more posts will be accepted.", 'info'); + $app->utils->message("Is one of these posts the answer to your question? If so click here to close thread.
      After closing a thread no more posts will be accepted.", 'info'); } ?>
        diff --git a/html/git.php b/html/git.php index 32fa565..192454c 100644 --- a/html/git.php +++ b/html/git.php @@ -10,9 +10,9 @@ HackThis/hackthis.co.uk HackThis -

        Get involved

        - Found a bug? Wish the site had feature x? The source code for all HackThis!! projects can be found on GitHub. We encourage you to fork the code and see if you can implement the fix/feature yourself. If you do develop something that you think would be beneficial, then create pull request and we can include it in the site! All users that submit an approved pull request receive a contributor medal, however small the change may be.

        - Not a developer? There are a lot of other changes that you could submit, for example spelling and grammatical fixes. We are grateful for even the smallest contribution. +

        Get involved

        + Found a bug? Wish the site had feature x? The source code for all HackThis!! projects can be found on GitHub. We encourage you to fork the code and see if you can implement the fix/feature yourself. If you do develop something that you think would be beneficial, then create pull request and we can include it on the site! All users that submit an approved pull request receive a contributor medal, however small the change may be.

        + Not a developer? There are a lot of other changes that you could submit, for example, spelling and grammatical fixes. We are grateful for even the smallest contribution.


        Version History

        diff --git a/html/hackers.txt b/html/hackers.txt new file mode 100644 index 0000000..1840afd --- /dev/null +++ b/html/hackers.txt @@ -0,0 +1,937 @@ +***************************************************************************** +* * +* From the March 1990 edition of Harper's Magazine * +* * +* IS COMPUTER HACKING A CRIME? * +* * +* Typed by Warlock, March 13. * +* * +***************************************************************************** + + The image of the computer hacker drifted into public awareness in the +middle Seventies, when reports of Chinese-food-consuming geniuses working +compulsively at keyboards began to issue from MIT. Over time, several of +these impresarios entered commerce, and the public's impression of hackers +changed: They were no longer nerds but young, millionaire entrepreneurs. + The most recent news reports have given the term a more felonious +connotation. Early this year, a graduate student named Robert Morris Jr. went +on trial for releasing a computer program known as a worm into the vast +Internet system, halting more than 6,000 computers. The subsequent public +debate ranged from the matter of proper punishment for a mischievous kid to +the issue of our rapidly changing notion of what constitutes free speech -- or +property -- in an age of modems and data bases. In order to allow hackers to +speak for themselves, Harper's Magazine recently organized an electronic +discussion and asked some of the nation's best hackers to "log on," discuss +the protean notions of contemporary speech, and explain what their powers and +talents are. + +The following forum is based on a discussion held on the WELL, a computer +bulletin board system based in Sausalito, California. The forum is the result +of a gradual accretion of arguments as the participants -- located throughout +the country -- opined and reacted over an eleven day period. Harper's Magazine +senior editor Jack Hitt and assistant editor Paul Tough served as moderators. + +ADELAIDE is a pseudonym for a former hacker who has sold his soul to the +corporate state as a computer programmer. + +BARLOW is John Perry Barlow, a retired cattle rancher, a former Republican +county chairman, and a lyricist for the Grateful Dead, who currently is +writing a book on computers and consciousness entitled Everything We Know Is +Wrong. + +BLUEFIRE is Dr. Robert Jacobson, associate director of the Human Interface +Technology Laboratory at the University of Washington and a former +information-policy analyst with the California legislature. + +BRAND is Russell Brand, a senior computer scientist with Reasoning Systems, in +Palo Alto, California. + +CLIFF is Clifford Stoll, the astronomer who caught a spy in a military computer +network and recently published an account of his investigation entitled The +Cuckoo's Egg. + +DAVE is Dave Hughes, a retired West Pointer who currently operates his own +political bulletin board. + +DRAKE is Frank Drake, a computer-science student at a West Coast university and +the editor of W.O.R.M., a cyberpunk magazine. + +EDDIE JOE HOMEBOY is a pseudonym for a professional software engineer who has +worked at Lucasfilm, Pyramid Technology, Apple Computer, and Autodesk. + +EMMANUEL GOLDSTEIN is the editor of 2600, the "hacker's quarterly." + +HANK is Hank Roberts, who builds mobiles, flies hang gliders, and proofreads +for the Whole Earth Catalog. + +JIMG is Jim Gasperini, the author, with TRANS Fiction Systems, of Hidden +Agenda, a computer game that simulates political conflict in Central America. + +JRC is Jon Carroll, daily columnist for the San Francisco Chronical and +writer-in-residence for the Pickle Family Circus, a national traveling circus +troupe based in San Francisco. + +KK is Kevin Kelly, editor of the Whole Earth Review and a cofounder of the +Hacker's Conference. + +LEE is Lee Felstein, who designed the Osborne-1 computer and cofounded the +Homebrew Computer Club. + +MANDEL is Tom Mandel, a professional futurist and an organizer of the Hacker's +Conference. + +RH is robert Horvitz, Washington correspondent for the Whole earth Review. + +RMS is Richard Stallman, founder of the Free Software Foundation. + +TENNEY is Glenn Tenney, an independant-systems architect and an organizer of +the Hacker's Conference. + +ACID PHREAK and PHIBER OPTIK are both pseudonyms for hackers who decline to be +identified. + + A Hacker's Lexicon + +Back Door: A point of entry into a computer system -- often installed there by +the original programmer -- that provides secret access. + +Bomb: A destructive computer program, which, when activated, destroys the +files in a computer system. + +Chipper: A hacker who specializes in changing the programming instructions of +computer chips. + +Cracker: A hacker who breaks illegally into computer systems and creates +mischief; often used pejoratively. The original meaning of cracker was +narrower, describing those who decoded copyright-protection schemes on +commercial software products or to modify them; sometimes known as a software +pirate. + +Hacker: Originally, a compulsive computer programmer. The word has evolved in +meaning over the years. Among computer users, hacker carries a positive +connotation, meaning anyone who creatively explores the operations of computer +systems. Recently, it has taken on a negative connotation, primarily through +confusion with cracker. + +Phone phreak: One who explores the operations of the phone system, often with +the intent of making free phone calls. + +Social engineering: A nontechnical means of gaining information simply by +persuading people to hand it over. If a hacker wished to gain access to a +computer system, for example, an act of social engineering might be able to +contact a system operator and to convince him or her that the hacker is a +legitimate user in need of a password; more colloquially, a con job. + +Virus: A program that, having been introduced into a system, replicates itself +and attaches itself to other programs, often with a variety of mischievous +effects. + +Worm: A destructive program that, when activated, fills a computer system with +self-replicating information, clogging the system so that its operations are +severely slowed, sometimes stopped. + + The Digital Frontier + +HARPER'S [Day 1, 9:00 A.M.]: When the computer was young, the word hacking was +used to describe the work of brilliant students who explored and expanded the +uses to which this new technology might be employed. There was even talk of a +"hacker ethic." Somehow, in the succeeding years, the word has taken on dark +connotations, suggestion the actions of a criminal. What is the hacker ethic, +and does it survive? + +ADELAIDE [Day 1, 9:25 A.M.]: the hacker ethic survives, and it is a fraud. It +survives in anyone excited by technology's power to turn many small, +insignificant things into one vast, beautiful thing. It is a fraud because +there is nothing magical about computers that causes a user to undergo +religious conversion and devote himself to the public good. Early automobile +inventors were hackers too. At first the elite drove in luxury. Later +practically everyone had a car. Now we have traffic jams, drunk drivers, air +pollution, and suburban sprawl. The old magic of an automobile occasionally +surfaces, but we possess no delusions that it automatically invades the +consciousness of anyone who sits behind the wheel. Computers are power, and +direct contact with power can bring out the best or worst in a person. It's +tempting to think that everyone exposed to the technology will be grandly +inspired, but, alas, it just ain't so. + +BRAND [Day 1, 9:54 A.M.] The hacker ethic involves several things. One is +avoiding waste; insisting on using idle computer power -- often hacking into a +system to do so, while taking the greatest precautions not to damage the +system. A second goal of many hackers is the free exchange of technical +information. These hackers feel that patent and copyright restrictions slow +down technological advances. A third goal is the advancement of human +knowledge for its own sake. Often this approach is unconventional. People we +call crackers often explore systems and do mischief. The are called hackers by +the press, which doesn't understand the issues. + +KK [Day 1, 11:19 A.M.]: The hacker ethic went unnoticed early on because the +explorations of basement tinkerers were very local. Once we all became +connected, the work of these investigations rippled through the world. today +the hacking spirit is alive and kicking in video, satellite TV, and radio. In +some fields they are called chippers, because the modify and peddle altered +chips. Everything that was once said about "phone phreaks" can be said about +them too. + +DAVE [Day 1, 11:29 A.M.]: Bah. Too academic. Hackers hack. Because the want +to. Not for any higher purpose. Hacking is not dead and won't be as long as +teenagers get their hands on the tools. There is a hacker born every minute. + +ADELAIDE [Day 1, 11:42 A.M.]: Don't forget ego. People break into computers +because it's fun and it makes them feel powerful. + +BARLOW [Day 1, 11:54 A.M.]: Hackers hack. Yeah, right, but what's more to the +point is that humans hack and always have. Far more than just opposable +thumbs, upright posture, or excess cranial capacity, human beings are set apart +from all other species by an itch, a hard-wired dissatisfaction. Computer +hacking is just the latest in a series of quests that started with fire +hacking. Hacking is also a collective enterprise. It brings to our joint +endeavors the simultaneity that other collective organisms -- ant colonies, +Canada geese -- take for granted. This is important, because combined with our +itch to probe is a need to connect. Humans miss the almost telepathic +connectedness that I've observed in other herding mammals. And we want it +back. Ironically, the solitary sociopath and his 3:00 A.M. endeavors hold the +most promise for delivering species reunion. + +EDDIE JOE HOMEBOY [Day 1, 4:44 P.M.]: Hacking really took hold with the advent +of the personal computer, which freed programmers from having to use a big +time-sharing system. A hacker could sit in the privacy of his home and hack to +his heart's and head's content. + +LEE [Day 1, 5:17 P.M.]: "Angelheaded hipsters burning for the ancient heavenly +connection to the starry dynamo in the machinery of night" (Allen Ginsberg, +"Howl"). I still get an endorphin rush when I go on a design run -- my mind +out over the edge, groping for possibilities that can be sensed when various +parts are held in juxtaposition with a view toward creating a whole object: +straining to get through the epsilon-wide crack between What Is and What Could +Be. Somewhere there's the Dynamo of Night, the ultra-mechanism waiting to be +dreamed, that we'll never get to in actuality, (think what is would weigh!) but +that's present somehow in the vicinity of those mental wrestling matches. When +I re-emerge into the light of another day with the design on paper -- and with +the knowledge that if it ever gets built, things will never be the same again +-- I know I've been where artists go. That's hacking to me: to transcend +custom and to engage in creativity for its own sake, but also to create +objective effects. I've been around long enough to see the greed creeps take +up the unattended reins of power and shut down most of the creativity that put +them where they are. But I've also seen things change, against the best +efforts of a stupidly run industry. We cracked the egg out from under the +Computer Priesthood, and now everyone can have omelets. + +RMS [Day 1, 5:19 P.M.]: The media and the courts are spreading a certain image +of hackers. It's important for us not to be shaped by that image. But there +are two ways that it can happen. One way is for hackers to become part of the +security-maintenance establishment. The other, more subtle, way is for a +hacker to become the security-breaking phreak the media portray. By shaping +ourselves into the enemy of the establishment, we uphold the establishment. But +there's nothing wrong with breaking security if you're accomplishing something +useful. It's like picking a lock on a tool cabinet to get a screwdriver to fix +your radio. As long as you put the screwdriver back, what harm does it do? + +ACID PHREAK [Day 1, 6:34 P.M.]: There is no one hacker ethic. Everyone has +his own. To say that we all think the same way is preposterous. The hacker +of old sought to find what the computer itself could do. There was nothing +illegal about that. Today, hackers and phreaks are drawn to specific, often +corporate, systems. It's no wonder everyone on the other side is getting mad. +We're always one step ahead. We were back then, and we are now. + +CLIFF [Day 1, 8:38 P.M.]: RMS said, "There's nothing wrong with breaking +security if you're accomplishing something useful." Huh? How about, There's +nothing wrong with entering a neighbor's house if you're accomplishing +something useful, just as long as you clean up after yourself. Does my +personal privacy mean anything? Should my personal letters and data be open to +anyone who knows how to crack passwords? If not my property, then how about a +bank's? Should my credit history be available to anyone who can find a back +door to the private computers of TRW, the firm that tracks people's credit +histories? How about a list of AIDS patients from a hospital's data bank? Or +next week's prime interest rate from a computer at the Treasury Department? + +BLUEFIRE [Day 1, 9:20 P.M.]: Computers are everywhere, and they link us +together into a vast social "cybernetia." The grand skills of the hackers, +formidable though they may have been, are incapable of subverting this +automated social order. The networks in which we survive are more than copper +wire and radio waves: They are the social organization. For every hacker in +revolt, busting through a security code, ten thousand people are being wired up +with automatic call-identification and credit-checking machines. Long live the +Computer Revolution, which died aborning. + +JRC [Day 1, 10:28 P.M.]: We have two different definitions here. One speaks +of a tinkerer's ecstasy, an ecstasy that is hard to maintain in the corporate +world but is nevertheless at the heart of Why Hackers Hack. The second is +political, and it has to do with the free flow of information. Information +should flow more freely (how freely is being debated), and the hacker can make +it happen because the hacker knows how to undam the pipes. This makes the +hacker ethic -- of necessity -- antiauthoritarian. + +EMMANUEL GOLDSTEIN [Day 2, 2:41 A.M.]: It's meaningless what we call +ourselves: hackers, crackers, techno-rats. We're individuals who happen to +play with high tech. There is no hacker community in the traditional sense of +the term. There are no leaders and no agenda. We're just individuals out +exploring. + +BRAND [Day 2, 9:02 A.M.]: There are two issues: invariance and privacy. +Invariance is the art of leaving things as you found them. If someone used my +house for the day and left everything as he found it so that there was no way +to tell he had been there, I would see no problem. With a well-run computer +system, we can assure invariance. Without this assurance we must fear that the +person picking the lock to get the screwdriver will break the lock, the +screwdriver, or both. Privacy is more complicated. I want my medical records, +employment records, and letters to The New Republic private because I fear that +someone will do something with the information that is against my interests. +If I could trust people not to do bad things with information, I would not need +to hide it. Rather than preventing the "theft" of this data, we should +prohibit its collection in the first place. + +HOMEBOY [Day 2, 9:37 A.M.]: Are crackers really working for the free flow of +information? Or are they unpaid tools of the establishment, identifying the +holes in the institutional dike so that they can be plugged by the +authorities, only to be tossed in jail or exiled? + +DRAKE [Day 2, 10:54 A.M.]: There is an unchallenged assumption that crackers +have some political motivation. Earlier, crackers were portrayed as failed +revolutionaries; now Homeboy suggests that crackers may be tools of the +establishment. These ideas about crackers are based on earlier experiences +with subcultures (beats, hippies, yippies). Actually, the contemporary +cracker is often middle-class and doesn't really distance himself from the +"establishment." While there are some anarcho-crackers, there are even more +right-wing crackers. The hacker ethic crosses political boundaries. + +MANDEL [Day 2, 11:01 A.M.]: The data on crackers suggests that they are either +juvenile delinquents or plain criminals. + +BARLOW [Day 2, 11:34 A.M.]: I would far rather have everyone know my most +intimate secrets than to have noncontextual snippits of them "owned" by TRW +and the FBI -- and withheld from me! Any cracker who is entertained by +peeping into my electronic window is welcome to the view. Any institution that +makes money selling rumors of my peccadilloes is stealing from me. Anybody who +wants to inhibit that theft with electronic mischief has my complete support. +Power to the techno-rats! + +EMMANUEL [Day 2, 7:09 P.M.]: Calling someone on the phone is the equivalent of +knocking on that person's door, right? Wrong! When someone answers the phone, +you are inside the home. You have already been let in. The same with an +answering machine, or a personal computer, if it picks up the phone. It is +wrong to violate a person's privacy, but electronic rummaging is not the same +as breaking and entering. The key here is that most people are unaware of how +easy it is for others to invade their electronic privacy and see credit +reports, phone bills, FBI files, Social Security reports. The public is +grossly underinformed, and that's what must be fixed if hackers are to be +thwarted. If we had an educated public, though, perhaps the huge -- and now +common -- date bases would never have been allowed to exist. Hackers have +become scapegoats: We discover the gaping holes in the system and then get +blamed for the flaws. + +HOMEBOY [Day 2, 7:41 P.M.]: Large, insular, undemocratic governments and +institutions need scapegoats. It's the first step down the road to fascism. +That's where hackers play into the hands of the establishment. + +DAVE [Day 2, 7:55 P.M.]: If the real criminals are those who leave gaping +holes in their systems, the the real criminals in house burglaries are those +who leave their windows unlatched. Right? Hardly. And Emmanuel's analogy to +a phone being answered doesn't hold either. There is no security protection in +making a phone call. A computer system has a password, implying a desire for +security. Breaking into a poorly protected house is still burglary. + +CLIFF [Day 2, 9:06 P.M.]: Was there a hacker's ethic and does it survive? +More appropriately, was there a vandal's ethic and does it survive? As long as +there are communities, someone will violate the trust that binds them. Once, +our computers were isolated, much as eighteenth-century villages were. Little +was exchanged, and each developed independently. Now we've built far-flung +electronic neighborhoods. These communities are built on trust: people +believing that everyone profits by sharing resources. Sure enough, vandals +crept in, breaking into systems, spreading viruses, pirating software, and +destroying people's work. "It's okay," they say. "I can break into a system +because I'm a hacker." Give me a break! + +BARLOW [Day 2, 10:41 P.M.]: I live in a small town. I don't have a key to my +house. Am I asking for it? I think not. Among the juvenile delinquents in my +town, there does exist a vandal's ethic. I know because I once was one. In a +real community, part of a kid's rite of passage is discovering what walls can +be breached. Driving 110 miles per hour on Main Street is a common symptom of +rural adolescence, publicly denounced but privately understood. Many teenagers +die in this quest -- two just the night before last -- but it is basic to our +culture. Even rebellious kids understand that risk to one's safety is one +thing, wanton vandalism or theft is another. As a result, almost no one locks +anything here. In fact, a security system is an affront to a teenage psyche. +While a kid might be dissuaded by conscience, he will regard a barricade as an +insult and a challenge. So the CEOs who are moving here (the emperor of +PepsiCo and the secretary of state among them) soon discover that over the +winter people break into their protected mansions just to hang out. When +systems are open, the community prospers, and teenage miscreants are satisfied +to risk their own lives and little else. When the social contract is enforced +by security, the native freedom of the adolescent soul will rise up to +challenge it in direct proportion to its imposition. + +HANK [Day 2, 11:23 P.M.]: Barlow, the small town I grew up in was much like +yours -- until two interstate highways crossed nearby. The open-door style +changed in one, hard summer because our whole town became unlocked. I think +Cliff's community is analogous to my little town -- confronted not by a new +locked-up neighbor who poses a challenge to the local kids but by a sudden, +permanent opening up of the community to many faceless outsiders who owe the +town no allegiance. + +EMMANUEL [Day 3, 1:33 A.M.]: Sorry, I don't buy Dave's unlatched-window +analogy. A hacker who wanders into a system with the ease that it's done today +is, in my analogy, walking into a house without walls -- and with a cloaking +device! Any good hacker can make himself invisible. If housebreaking were +this easy, people would be enraged. But we're missing the point. I'm not +referring to accessing a PC in someone's bedroom but about accessing credit +reports, government files, motor vehicle records, and the megabytes of data +piling up on each of us. Thousands of people legally see and use this +ever-growing mountain of data, much of it erroneous. Whose rights are we +violating when we peruse a file? Those of the person we look up? He doesn't +even know that information exists, that it was compiled without his consent, +and that it's not his property anymore! The invasion of privacy took place +long before the hacker ever arrived. The only way to find out how such a +system works is to break the rules. It's not what hackers do that will lead us +into a state of constant surveillance; it's allowing the authorities to impose +on us a state of mock crisis. + +MANDEL [Day 3, 9:27 A.M.]: Note that the word crime has no fixed reference in +our discussion. Until recently, breaking into government computer systems +wasn't a crime; now it is. In fact, there is some debate, to be resolved in +the courts, whether what Robert Morris Jr. did was actually a crime [see "A +Brief History of Hacking"]. Crime gets redefined all the time. Offend enough +people or institutions and, lo and behold, someone will pass a law. That is +partly what is going on right now: Hackers are pushing buttons, becoming more +visible, and that inevitably means more laws and more crimes. + +ADELAIDE [Day 3, 9:42 A.M.]: Every practitioner of these arts knows that at +minimum he is trespassing. The English "country traveler ethic" applies: The +hiker is always ethical enough to close the pasture gates behind him so that no +sheep escape during his pastoral stroll through someone else's property. The +problem is that what some see as a gentle trespassing others see as theft of +service, invasion of privacy, threat to national security -- take your pick. + +BARLOW [Day 3, 2:38 P.M.]: I regard the existence of proprietary data about me +to be theft -- not just in the legal sense but in a faintly metaphysical one, +rather like the belief among aborigines that a photograph steals the soul. The +crackers who maintain access to that data are, at this level, liberators. +Their incursions are the only way to keep the system honest. + +RMS [Day 3, 2:48 P.M.]: Recently, a tough anti-hacker measure was proposed in +England. In The Economist I saw a wise response, arguing that it was silly to +treat an action as worse when it involves a computer that when it does not. +They noted, for example, that physical trespassing was considered a civil +affair, not a criminal one, and said that computer trespassing should be +treated likewise. Unfortunately, the U.S. government was not so wise. + +BARLOW [Day 3, 3:23 P.M.]: The idea that a crime is worse if a computer is +involved relates to the gathering governmental perception that computer viruses +and guns may be related. I know that sounds absurd, but they have more in +common than one might think. For all its natural sociopathy, the virus is not +without philosophical potency -- like a gun. Here in Wyoming guns are part of +the furniture. Only recently have I observed an awareness of their political +content. After a lot of frothing about prying cold, dead fingers from +triggers, the sentiment was finally distilled to a bumper sticker I saw on a +pickup the other day: "Fear the Government That Fears Your Gun." Now I've read +too much Ghandi to buy that line without misgivings, but it would be hard to +argue that Tiananmen Square could have been inflicted on a populace capable of +shooting back. I don't wholeheartedly defend computer viruses, but one must +consider their increasingly robust deterrent potential. Before it's over, the +War on Drugs could easily turn into an Armageddon between those who love +liberty and those who crave certainty, providing just the excuse the control +freaks have been waiting for to rid America of all that constitutional +mollycoddling called the Bill of Rights. Should that come to pass, I will want +to use every available method to vex and confuse the eyes and ears of +surveillance. The virus could become the necessary instrument of our freedom. +At the risk of sounding like some digital posse comitatus, I say* Fear the +Government That Fears Your Computer. + +TENNEY [Day 3, 4:41 P.M.]: Computer-related crimes are more feared because +they are performed remotely -- a crime can be committed in New York by someone +in Los Angeles -- and by people not normally viewed as being criminals -- by +teenagers who don't look like delinquents. They're smart nerds, and they don't +look like Chicago gangsters packing heat. + +BARLOW [Day 4, 12:12 A.M.]: People know so little of these things that they +endow computers and the people who do understand them with powers neither +possesses. If America has a religion, its ark is the computer and its +covenant is the belief that Science Knows. We are mucking around in the +temple, guys. It's a good way to catch hell. + +DAVE [Day 4, 9:18 A.M.]: Computers are the new American religion. The public +is in awe of -- and fears -- the mysteries and the high priests who tend them. +And the public reacts just as it always has when faced with fear of the unknown +-- punishment, burning at the stake. Hackers are like the early Christians. +When caught, they will be thrown to the lions before the Roman establishment: +This year the mob will cheer madly as Robert Morris is devoured. + +KK [Day 6, 11:37 A.M.]: The crackers here suggest that they crack into systems +with poor security BECAUSE the security is poor. Do more sophisticated +security precautions diminish the need to crack the system or increase it? + +ACID [Day 6, 1:20 P.M.]: If there was a system that we knew was uncrackable, +we wouldn't even try to crack it. On the other hand, if some organization +boasted that its system was impenetrable and we knew that was media hype, I +think it would be safe to say we'd have to "enlighten" them. + +EMMANUEL [Day 6, 2:49 P.M.]: Why do we insist on cracking systems? The more +people ask those kinds of questions, the more I want to get in! Forbid access +and the demand for access increases. For the most part, it's simply a mission +of exploration. In the words of the new captain of the starship Enterprise, +Jean-Luc Picard, "Let's see what's out there!" + +BARLOW [Day 6,4:34 P.M.]: Tell us, Acid, is there a system that you know to be +uncrackable to the point where everyone's given up? + +ACID [Day 6, 8:29 P.M.]: CICIMS is pretty tough. + +PHIBER OPTIK [Day 7, 2:36 P.M.]: Really? CICIMS is a system used by Bell +operating companies. The entire security system was changed after myself and a +friend must have been noticed in it. For the entire United States, there is +only one such system, located in Indiana. The new security scheme is flawless +in itself, and there is no chance of "social engineering" i.e., bullshitting +someone inside the system into telling you what the passwords are. The system +works something like this: You log on with the proper account and password; +then, depending on who you are, the system asks at random three of ten +questions that are unique to each user. But the system can be compromised by +entering forwarding instructions into the phone company's switch for that +exchange, thereby intercepting every phone call that comes in to the system +over a designated period of time and connecting the call to your computer. If +you are familiar with the security layout, you can emulate its appearance and +fool the caller into giving you the answers to his questions. Then you call +the system yourself and use those answers to get in. There are other ways of +doing it as well. + +BLUEFIRE [Day 7,11:53 P.M.]: I can't stand it! Who do you think pays for the +security that the telephone companies must maintain to fend off illegal use? I +bet it costs the ratepayers around $10 million for this little extravaganza. +The cracker circus isn't harmless at all, unless you don't mind paying for +other people's entertainment. Hackers who have contributed to the social +welfare should be recognized. But cracking is something else -- namely, fun at +someone else's expense -- and it ain't the folks who own the phone companies +who pay; it's us, me and you. + +BARLOW [Day 8, 7:35 A.M.]: I am becoming increasingly irritated at this idea +that you guys are exacting vengeance for the sin of openness. You seem to +argue that if a system is dumb enough to be open, it is your moral duty to +violate it. Does the fact that I've never locked my house -- even when I was +away for months at a time -- mean that someone should come in and teach me a +lesson? + +ACID [Day 8, 3:23 P.M.]: Barlow, you leave the door open to your house? Where +do you live? + +BARLOW [Day 8, 10:11 P.M.]: Acid, my house is at 372 North Franklin Street in +Pinedale, Wyoming. Heading north on Franklin, go about two blocks off the main +drag before you run into a hay meadow on the left. I'm the last house before +the field. The computer is always on. But do you really mean to imply what +you did with that question? Are you merely a sneak looking for easy places to +violate? You disappoint me, pal. For all your James Dean-on-Silicon, you're +just a punk. + +EMMANUEL [Day 9, 12:55 A.M.]: No offense, Barlow, but your house analogy +doesn't stand up, because your house is far less interesting than a Defense +Department computer. For the most part, hackers don't mess with individuals. +Maybe we feel sorry for them; maybe they're boring. Institutions are where +the action is, because they are compiling this mountain of data -- without +your consent. Hackers are not guardian angels, but if you think we're what's +wrong with the system, I'd say that's precisely what those in charge want you +to believe. By the way, you left out your zip code. It's 82941. + +BARLOW [Day 9, 8:34 A.M.]: Now that's more like it. There is an ethical +distinction between people and institutions. The law makes little distinction. +We pretend that institutions are somehow human because they are made of humans. +A large bureaucracy resembles a human about as much as a reef resembles a coral +polyp. To expect an institution to have a conscience is like expecting a horse +to have one. As with every organism, institutions are chiefly concerned with +their own physical integrity and survival. To say that they have some higher +purpose beyond their survival is to anthropomorphize them. You are right, +Emmanuel. The house analogy breaks down here. Individuals live in houses; +institutions live in mainframes. Institutions are functionally remorseless and +need to be checked. Since their blood is digital, we need to be in their +bloodstreams like an infection of humanity. I'm willing to extend limitless +trust to other human beings. In my experience they've never failed to deserve +it. But I have as much faith in institutions as they have in me. None. + +OPTIK [Day 9, 10:19 A.M.]: In other words, Mr. Barlow, you say something, +someone proves you wrong, and then you agree with him. I'm getting the feeling +that you don't exactly chisel your views in stone. + +HANK [Day 9, 11:18 A.M.]: Has Mr. Optik heard the phrase "thesis, antithesis, +synthesis"? + +BARLOW [Day 10, 10:48 A.M.]: Optik, I do change my mind a lot. Indeed, I +often find it occupied by numerous contradictions. The last time I believed in +absolutes, I was about your age. And there's not a damn thing wrong with +believing in absolutes at your age either. Continue to do so, however, and +you'll find yourself, at my age, carrying placards filled with nonsense and +dressing in rags. + +ADELAIDE [Day 10, 6:27 P.M.]: The flaw in this discussion is the distorted +image the media promote of the hacker as "whiz." The problem is that the one +who gets caught obviously isn't. I haven't seen a story yet on a true genius +hacker. Even Robert Morris was no whiz. The genius hackers are busy doing +constructive things or are so good no one's caught them yet. It takes talent +to break into something. Nobody calls subway graffiti artists geniuses for +figuring out how to break into the yard. There's a difference between genius +and ingenuity. + +BARLOW [Day 19, 9:48 P.M.]: Let me define my terms. Using hacker in a +midspectrum sense (with crackers on one end and Leonardo da Vinci on the +other), I think it does take a kind of genius to be a truly productive hacker. +I'm learning PASCAL now, and I am constantly amazed that people can string +those prolix recursions into something like PageMaker. It fills me with the +kind of awe I reserve for splendors such as the cathedral at Chartres. With +crackers like Acid and Optik, the issue is less intelligence than alienation. +Trade their modems for skateboards and only a slight conceptual shift would +occur. Yet I'm glad they're wedging open the cracks. Let a thousand worms +flourish. + +OPTIK [Day 10, 10:11 P.M.]: You have some pair of balls comparing my talent +with that of a skateboarder. Hmm... This was indeed boring, but nonetheless: +[Editor's note: At this point in the discussion, Optik -- apparently having +hacked into TRW's computer records -- posted a copy of Mr. Barlow's credit +history. In the interest of Mr. Barlow's privacy -- at least what's left of it +-- Harper's Magazine has not printed it.] I'm not showing off. Any fool +knowing the proper syntax and the proper passwords can look up credit history. +I just find your high-and-mighty attitude annoying and, yes, infantile. + +HOMEBOY [Day 10, 10:17 P.M.]: Key here is "any fool." + +ACID [Day 11, 1:37 P.M.]: For thirty-five dollars a year anyone can have +access to TRW and see his or her own credit history. Optik did it for free. +What's wrong with that? And why does TRW keep files on what color and religion +we are? If you didn't know that they kept such files, who would have found out +if it wasn't for a hacker? Barlow should be grateful that Optik has offered +his services to update him on his personal credit file. Of course, I'd hate to +see my credit history up in lights. But if you hadn't made our skins crawl, +your info would not have been posted. Everyone gets back at someone when he's +pissed; so do we. Only we do it differently. Are we punks? Yeah, I guess we +are. A punk is what someone who has been made to eat his words calls the guy +who fed them to him. + +**************************************************************************** + + A Brief History of Hacking + +September 1970 - John Draper takes as his alias the name of Captain Crunch +after he discovers that the toy whistle found in the cereal of the same name +perfectly simulates the tone necessary to make free phone calls. + +March 1975 - The Homebrew Computer Club, an early group of computer hackers, +holds its first meeting in Menlo Park, California. + +July 1976 - Homebrew members Steve Wozniak, twenty-six, and Steve Jobs, +twenty-one, working out of a garage, begin selling the first personal computer, +known as the Apple. + +June 1980 - In one week, errors in the computer system operating the U.S. +air-defense network cause two separate false reports of soviet missile +launches, each prompting an increased state of nuclear readiness. + +December 1982 - Sales of Apple personal computers top one billion dollars per +year. + +November 1984 - Steven Levy's book Hackers is published, popularizing the +concept of the "hacker ethic": that "access to computers, and anything that +might teach you something about the way the world works, should be unlimited +and total." The book inspires the first Hacker's Conference, held that month. + +January 1986 - The "Pakistani Brain" virus, created by a software distributor +in Lahore, Pakistan, infects IBM computers around the world, erasing data +files. + +June 1986 - The U.S. Office of Technology Assessment warns that massive, +cross-indexed government computer records have become a "de facto national data +base containing personal information on most Americans." + +March 1987 - William Fates, a Harvard dropout who founded Microsoft +Corporation, becomes a billionaire. + +November 1988 - More that 6,000 computers linked by the nationwide Internet +computer network are infected by a computer program known as a worm and are +crippled for two days. The worm is traced to Robert Morris Jr., a twenty-four- +year-old Cornell University graduate student. + +December 1988 - A federal grand jury charges Kevin Mitnick, twenty-five, with +stealing computer programs over telephone lines. Mitnick is held without bail +and forbidden access to any telephones without supervision. + +March 1989 - Three West German hackers are arrested for entering thirty +sensitive military computers using home computers and modems. The arrests +follow a three-year investigation by Clifford Stoll, an astronomer at the +Lawrence Berkeley Laboratory who began tracing the hackers after finding a +seventy-five-cent billing error in the lab's computer system. + +January 1990 - Robert Morris Jr. Goes on trial in Syracuse, New York, for +designing and releasing the Internet worm. Convicted, he faces up to five +years in prison and a $250,000 fine. + + +***************************************************************************** +* * +* Part 2: Hacking The Constitution * +* * +***************************************************************************** + +HARPER'S [Day 4, 9:00 A.M.]: Suppose that a mole inside the government +confirmed the existence of files on each of you, stored in the White House +computer system, PROFS. Would you have the right to hack into that system to +retrieve and expose the existence of such files? Could you do it? + +TENNEY [Day 4, 1:42 P.M.]: The proverbial question of whether the end +justifies the means. This doesn't have much to do with hacking. If the file +were a sheet of paper in a locked cabinet, the same question would apply. In +that case you could accomplish everything without technological hacking. +Consider the Pentagon Papers. + +EMMANUEL [Day 4, 3:55 P.M.]: Let's address the hypothetical. First, I need to +find out more about PROFS. Is it accessible from off site, and if so, how? +Should I update my 202-456 scan [a list of phone numbers in the White House's +exchange that connect incoming calls to a computer]? I have a listing for +every computer in that exchange, but the scan was done back in 1984. Is PROFS +a new system? Perhaps it's in a different exchange? Does anybody know how +many people have access to it? I'm also on fairly god terms with a White House +operator who owes me a favor. But I don't know what to ask for. Obviously, +I've already made up my mind about the right to examine this material. I don't +want to debate the ethics of it at this point. If you're with me, let's do +something about this. Otherwise, stay out of the way. There's hacking to be +done. + +ACID [Day 4, 5:24 P.M.]: Yes, I would try to break into the PROFS system. But +first I'd have someone in the public eye, with no ties to hacking, request the +info through the Freedom of Information Act. Then I'd hack in to verify the +information I received. + +DRAKE [Day 4, 9:13 P.M.]: Are there a lot of people involved in this +antihacker project? If so, the chances of social engineering data out of +people would be far higher than if it were a small, close-knit group. But yes, +the simple truth is, if the White House has a dial-up line, it can be hacked. + +EMMANUEL [Day 4, 11:27 P.M.]: The implication that a trust has been betrayed +on the part of the government is certainly enough to make me want to look a +little further. And I know I'm doing the right thing on behalf of others who +don't have my abilities. Most people I meet see me as an ally who can help +them stay ahead of an unfair system. That's what I intend to do here. I have +a small core of dedicated hackers who could help. One's specialty is the UNIX +system, another's is networks, and another's is phone systems. + +TENNEY [Day 5, 12:24 P.M.]: PROFS is an IBM message program that runs on an +operating system known as VM. VM systems usually have a fair number of holes, +wither to gain access or to gain full privileges. The CIA was working on, and +may have completed, a supposedly secure VM system. No ethics here, just facts. +But a prime question is to determine what system via what phone number. +Of course, the old inside job is easier. Just find someone who owes a favor or +convince an insider that it is a moral obligation to do this. + +BARLOW [Day 5, 2:46 P.M.]: This scenario needs to be addressed in four parts: +ethical, political, practical I (from the standpoint of the hack itself), and +practical II (disseminating the information without undue risk). + Ethical: Since World War II, we've been governed by a paramilitary + bureaucracy that believes freedom is too precious to be entrusted to the + people. These are the same folks who had to destroy the village in order + to save it. Thus the government has become a set of Chinese boxes. + Americans who believe in democracy have little choice but to shred the + barricades of secrecy at every opportunity. It isn't merely permissible + to hack PROFS. It is a moral obligation. + Political: In the struggle between control and liberty, one has to avoid + action that will drive either side to extreme behaviour. The basis of + terrorism, remember, is excess. If we hack PROFS, we must do it in a way + that doesn't become a pretext for hysterical responses that might + eventually include zero tolerance of personal computers. The answer is to + set up a system for entry and exit that never lets on we've been there. + Practical I: Hacking the system should be a trivial undertaking. + Practical II: Having retrieved the smoking gun, it must be made public in + such a way that the actual method of acquisition does not become public. + Consider Watergate: The prime leaker was somebody whose identity and + information-gathering technique is still unknown. So having obtained the + files, we turn them over to the Washington Post without revealing our own + identities or how we came by the files. + +EMMANUEL [Day 5, 9:51 P.M.]: PROFS is used for sending messages back and +forth. It's designed not to forget things. And it's used by people who are +not computer literate. The document we are looking for is likely an electronic +message. If we can find out who the recipient or sender is, we can take it +from there. Since these people frequently use the system to communicate, there +may be a way for them to dial into the White House from home. Finding that +number won't be difficult: frequent calls to a number local to the White House +and common to a few different people. Once I get the dial-up, I'll have to +look at whatever greeting I get to determine what kind of system it is. Then we +need to locate someone expert in the system to see if there are any built-in +back doors. If there aren't, I will social engineer my way into a working +account and then attempt to break out of the program and explore the entire +system. + +BRAND [Day 6, 10:06 A.M.]: I have two questions: do you believe in due process +as found in our Constitution? And do you believe that this "conspiracy" is so +serious that extraordinary measures need to be taken? If you believe in due +process, then you shouldn't hack into the system to defend our liberties. If +you don't believe in due process, you are an anarchist and potentially a +terrorist. The government is justified in taking extreme action to protect +itself and the rest of us from you. If you believe in the Constitution but +also that this threat is so extreme that patriots have a duty to intercede, +then you should seek one of the honest national officials who can legally +demand a copy of the document. If you believe that there is no sufficiently +honest politician and you steal and publish the documents, you are talking +about a revolution. + +ACID [Day 6, 1:30 P..]: This is getting too political. Who says that hacking +has to have a political side? Generalizing does nothing but give hackers a +false image. I couldn't care less about politics, and I hack. + +LEE [Day 6, 9:01 P.M.]: Sorry, Acid, but if you hack, what you do is +inherently political. Here goes: Political power is exercised by control of +information channels. Therefore, any action that changes the capability of +someone in power to control these channels is politically relevant. +Historically, the one in power has been not the strongest person but the one +who has convinced the goon squad to do his bidding. The goons give their power +to him, usually in exchange for free food, sex, and great uniforms. The +turning point of most successful revolutions is when the troops ignore the +orders coming from above and switch their allegiance. Information channels. +Politics. These days, the cracker represents a potential for making serious +political change if he coordinates with larger social and economic forces. +With out this coordination, the cracker is but a techno-bandit, sharpening his +weapon and chuckling about how someday... Revolutions often make good use of +bandits, and some of them move into high positions when they're successful. +but most of them are done away with. One cracker getting in won't do much +good. Working in coordination with others is another matter -- called +politics. + +JIMG [Day 7, 12:28 A.M.]: A thought: Because it has become so difficult to +keep secrets (thanks, in part, to crackers), and so expensive and +counterproductive (the trade-off in lost opportunities is too great), secrets +are becoming less worth protecting. Today, when secrets come out that would +have brought down governments in the past, "spin-control experts" shower the +media with so many lies that the truth is obscured despite being in plain +sight. It's the information equivalent of the Pentagon planto surround each +real missile with hundreds of fake ones, rendering radar useless. If hackers +managed to crack the White House system, a hue and cry would be raised -- not +about what the hackers found in the files but about what a threat hackers are +to this great democracy of ours. + +HARPER'S [Day 7, 9:00 A.M.]: Suppose you hacked the files from the White House +and a backlash erupted. Congressmen call for restrictions, arguing that the +computer is "property" susceptible to regulation and not an instrument of +"information" protected by the First Amendment. Can we craft a manifesto +setting forth your views on how the computer fits into the traditions of the +American Constitution? + +DAVE [Day 7, 5:30 P.M.]: If Congress ever passed laws that tried to define +what we do as "technology" (regulatable) and not "speech," I would become a +rebellious criminal immediately -- and as loud as Thomas Paine ever was. +Although computers are part "property" and part "premises" (which suggest a +need for privacy), they are supremely instruments of speech. I don't want any +congressional King Georges treading on my cursor. We must continue to have +absolute freedom of electronic speech! + +BARLOW [Day 7, 10:07 P.M.]: Even in a court guided by my favorite oxymoron, +Justice Rehnquist, this is an open-and-shut case. The computer is a printing +press. Period. The only hot-lead presses left in this country are either in +museums or being operated by poets in Vermont. The computer cannot fall under +the kind of regulation to which radio and TV have become subject, since +computer output is not broadcast. If these regulations amount to anything more +than a fart in the congressional maelstrom, then we might as well scrap the +whole Bill of Rights. What I am doing with my fingers is "speech" in the +clearest sense of the word. We don't need no stinking manifestos. + +JIMG [Day 8, 12:02 P.M.]: This type of congressional action is so clearly +unconstitutional that "law hackers" -- everyone from William Kunstler to Robert +Bork -- would be all over it. The whole idea runs so completely counter to our +laws that it's hard to get worked up about it. + +ADELAIDE [Day 8, 9:51 A.M.]: Not so fast. There used to be a right in the +Constitution called "freedom from unreasonable search and seizure," but, thanks +to recent Supreme Court decisions, your urine can be demanded by a lot of +people. I have no faith in the present Supreme Court to uphold any of my +rights of free speech. The complacent reaction here -- that whatever Congress +does will eventually be found unconstitutional -- is the same kind of +complacency that led to the current near-reversals of Roe v. Wade. + +JRC [Day 8, 10:05 A.M.]: I'd forgo the manifestos and official explanations +altogether: Fight brushfire wars against specific government incursions and +wait for the technology to metastasize. In a hundred years, people won't have +to be told about computers because they will have an instinctive understanding +of them. + +KK [Day 8, 2:14 P.M.]: Hackers are not sloganeers. They are doers, +take-things-in-handers. They are the opposite of philosophers: They don't wait +for language to catch up to them. Their arguments are their actions. You want +a manifesto? The Internet worm was a manifesto. It had more meaning and +symbolism than any revolutionary document you could write. To those in power +running the world's nervous system, it said: Wake up! To the underground of +hackers, crackers, chippers, techno-punks, it said: You have power; be careful. +To the mass of citizens who find computers taking over their telephone, their +TV, their toaster, and their house, it said: Welcome to Wonderland. + +BARLOW [Day 8, 10:51 P.M.]: Apart from the legal futility of fixing the dam +after it's been breached, I've never been comfortable with manifestos. They +are based on the ideologue's delusion about the simplicity, the +figure-out-ability, of the infinitely complex thing that is Life Among the +Humans. Manifestos take reductionism for a long ride off a short pier. +Sometimes the ride takes a very long time. Marx and Engels didn't actually +crash until last year. Manifestos fail because they are fixed and +consciousness isn't. I'm with JRC: Deal with incursions when we need to, on +our terms, like the guerrillas we are. To say that we can outmaneuver those +who are against us is like saying that honeybees move quicker than Congress. +The future is to the quick, not the respectable. + +RH [Day 8, 11:43 P.M.]: Who thinks computers can't be regulated? The +Electronic Communications Privacy Act of 1986 made it a crime to own "any +electronic, mechanical, or other device [whose design] renders it primarily +useful for the purpose of the surreptitious interception of wire, oral, or +electronic communication." Because of the way Congress defined "electronic +communication," one could argue that even a modem is a surreptitious +interception device (SID), banned by the ECPA and subject to confiscation. +It's not that Congress intended to ban modems; it was just sloppy drafting. +The courts will ultimately decide what devices are legal. Since it may not be +possible to draw a clear bright line between legal and illegal interception +devices, the grey area -- devices with both legitimate uses and illegitimate +uses -- may be subject to regulation. + +BARLOW [Day 9, 8:52 A.M.]: I admit with some chagrin that I'm not familiar +with the ECPA. It seems I've fallen on the wrong side of an old tautology: +Just because all saloon keepers are Democrats, it doesn't follow that all +Democrats are saloon keepers. By the same token, the fact that all printing +presses are computers hardly limits computers to that function. And one of +the other things computers are good at it surreptitous monitoring. Maybe +there's more reason for concern than I thought. Has any of this stuff been +tested in the courts yet? + +RH [Day 9, 10:06 P.M.]: My comments about surreptitous interception devices +are not based on any court cases, since there have not been any in this area +since the ECPA was enacted. It is a stretch of the imagination to think that +a judge would ever find a stock, off-the-shelf personal computer to be a +"surreptitous interception device." But a modem is getting a little closer to +the point where a creative prosecutor could make trouble for a cracker, with +fallout affecting many others. An important unknown is how the courts will +apply the word surreptitious. There's very little law, but taking it to mean +"by stealth; hidden from view; having its true purpose physically disguised," +I can spin some worrisome examples. I lobbied against the bill, pointing out +the defects. Congressional staffers admitted privately that there was a +problem, but they were in a rush to get the bill to the floor before Congress +adjourned. They said they could patch it later, but it is a pothole waiting +for a truck axle to rumble through. + +JIMG [Day 10, 8:55 A.M.]: That's sobering information, RH. Yet I still think +that this law, if interpreted the way you suggest, would be found +unconstitutional, even by courts dominated by Reagan appointees. Also, the +economic cost of prohibiting modems, or even restricting their use, would so +outweigh conceivable benefits that the law would never go through. Finally, +restricting modems would have no effect on the phreaks but would simply manage +to slow everybody else down. If modems are outlawed, only outlaws will have +modems. + +RH [Day 10, 1:52 P.M.]: We're already past the time when one would wrap +hacking in the First Amendment. There's a traditional distinction between +words -- expressions of opinions, beliefs, and information -- and deeds. You +can shout "Revolution!" from the rooftops all you want, and the post office +will obligingly deliver your recipes for nitroglycerin. But acting on that +information exposes you to criminal prosecution. The philosophical problem +posed by hacking is that computer programs transcend this distinction: They +are pure language that dictates action when read by the device being +addressed. In that sense, a program is very different from a novel, a play, +or even a recipe: Actions result automatically from the machine reading the +words. A computer has no independent moral judgement, no sense of +responsibility. Not yet, anyway. As we program and automate more of our +lives, we undoubtedly will deal with more laws: limiting what the public can +know, restricting devices that can execute certain instructions, and +criminalizing the possession of "harmful" programs with "no redeeming social +value." Blurring the distinction between language and action, as computer +programming does, could eventually undermine the First Amendment or at least +force society to limit its application. That's a very high price to pay, even +for all the good things that computers make possible. + +HOMEBOY [Day 10, 11:03 P.M.]: HACKING IS ART. CRACKING IS REVOLUTION. All +else is noise. Cracks in the firmament are by nature threatening. Taking a +crowbar to them is revolution. + +****************************************************************************** diff --git a/html/inbox/index.php b/html/inbox/index.php index f1ba4ba..6177bde 100644 --- a/html/inbox/index.php +++ b/html/inbox/index.php @@ -12,6 +12,11 @@ $status = $messages->deleteConvo($_GET['delete']); } + if(isset($_GET['readall'])) + { + $messages->markMessagesRead(); + } + if (isset($_POST['body']) && isset($_GET['view'])) { $result = $messages->newMessage(null, $_POST['body'], $_GET['view']); if ($result) { @@ -61,6 +66,9 @@ New Message + + Mark all as Read + @@ -195,6 +203,9 @@ if (isset($_GET['delete'])) { $app->utils->message($status?'Conversation deleted':'Error deleting conversation', $status?'good':'error'); } + if(isset($_GET['readall'])) { + $app->utils->message("All messages has been marked as read.", 'good'); + } ?>
        \ No newline at end of file +?> + diff --git a/html/index.php b/html/index.php index 6db07b5..904d176 100644 --- a/html/index.php +++ b/html/index.php @@ -14,7 +14,7 @@ // Make call to api try { $api = new api($app, $_GET['key']); - $api->process(); + $api->handleRequest($_POST['action'], null); } catch (Exception $e) { echo $e->getMessage(); } @@ -30,7 +30,7 @@ require_once('header.php'); ?>
        - HackThis!! - The hackers playground + HackThis!! - The hackers playground

        Challenges

        - Test your skills with 40+ hacking levels, covering all aspects of security.
        Each level is hand coded with help available at every stage. + Test your skills with 50+ hacking levels, covering all aspects of security.
        Each level is hand coded with help available at every stage.
    @@ -61,7 +61,7 @@

    Community

    - Join in the discussion with 200,000+ like-minded members.
    + Join in the discussion with 250,000+ like-minded members.
    Need a hint? Want to talk about the latest cracking software tool?
    @@ -104,7 +104,13 @@ ?>

    Login

    - +

    Register

    diff --git a/html/levels/level.php b/html/levels/level.php index 97a4fe8..a7e2e12 100644 --- a/html/levels/level.php +++ b/html/levels/level.php @@ -29,7 +29,7 @@ } //Check if user completed level - if (isset($currentLevel->data['form']) && $page = realpath($app->config('path') . '/files/elements/levels/'.basename($currentLevel->data['form']).'_logic.php')) { + if (isset($currentLevel->data['form']) && $page = realpath($app->config['path'] . '/files/elements/levels/'.basename($currentLevel->data['form']).'_logic.php')) { include($page); } else { $app->levels->check($currentLevel); diff --git a/html/more.php b/html/more.php index 0253237..56078d8 100644 --- a/html/more.php +++ b/html/more.php @@ -8,8 +8,8 @@ ?>

    More

      -
    • IRC
      -     - Stats +
    • IRC
      + -     Stats
    • user->loggedIn): @@ -30,4 +30,4 @@
    \ No newline at end of file +?> diff --git a/html/privacy.php b/html/privacy.php index f852f0f..248f6ec 100644 --- a/html/privacy.php +++ b/html/privacy.php @@ -11,7 +11,7 @@ ?>
    - +
    @@ -52,7 +52,7 @@

    We offer the use of a secure server. All supplied sensitive/credit information is transmitted via Secure Socket Layer (SSL) technology and then encrypted into our Payment gateway providers database only to be accessible by those authorized with special access rights to such systems, and are required to keep the information confidential.

    - After a transaction, your private information (credit cards, social security numbers, financials, etc.) will be kept on file for more than 60 days in order to proccess transactions. + After a transaction, your private information (credit cards, social security numbers, financials, etc.) will be kept on file for more than 60 days in order to process transactions.

    Do we use cookies?


    @@ -95,6 +95,6 @@
    - diff --git a/html/robots.txt b/html/robots.txt index 019c9eb..55aa799 100644 --- a/html/robots.txt +++ b/html/robots.txt @@ -10,4 +10,13 @@ Disallow: /ctf/8/php/* User-agent: Mediapartners-Google Disallow: -Sitemap: https://www.hackthis.co.uk/sitemap.xml \ No newline at end of file +User-agent: Yahoo Pipes 1.0 +Disallow: / + +User-agent: KSCrawler +Disallow: / + +User-agent: Spinn3r +Disallow: / + +Sitemap: https://www.hackthis.co.uk/sitemap.xml diff --git a/html/settings/2-step.php b/html/settings/2-step.php new file mode 100644 index 0000000..4db3629 --- /dev/null +++ b/html/settings/2-step.php @@ -0,0 +1,64 @@ +page->title = 'Settings - 2 Step Authentication'; + + require_once('header.php'); + + $tab = '2-step'; + include('elements/tabs_settings.php'); + + require('vendor/gauth.php'); + $ga = new gauth(); + + $st = $app->db->prepare('SELECT g_auth, g_secret FROM users WHERE user_id = :uid'); + $st->execute(array(':uid' => $app->user->uid)); + $step = $st->fetch(); +?> + +

    2 Step Authentication

    +

    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

    +

    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

    + +g_auth != 1) && !isset($_GET['google'])) { +?> +

    Enable Google Authenticator

    +getQRCodeGoogleUrl($app->user->username, $step->g_secret, 'HackThis!!'); +?> +

    Your Google Authenticator QR Code

    +

    +

    Disable Google Authenticator

    +g_auth != 1)) { + $secret = $ga->createSecret(); + $st = $app->db->prepare('UPDATE users SET g_auth = 1, g_secret = :secret WHERE user_id = :uid LIMIT 1'); + $status = $st->execute(array(':secret' => $secret, ':uid' => $app->user->uid)); + $qrCodeUrl = $ga->getQRCodeGoogleUrl($app->user->username, $secret, 'HackThis!!'); +?> + +

    Please scan the below QR code using your Google Authenticator App to add the account

    +

    +

    Disable Google Authenticator

    + +g_auth == 1)) { + $st = $app->db->prepare('UPDATE users SET g_auth = 0, g_secret = NULL WHERE user_id = :uid LIMIT 1'); + $status = $st->execute(array(':uid' => $app->user->uid)); +?> + +

    Google Authenticator Disabled
    It is now ok for you to remove your HackThis account from your Google Authenticator app.

    +

    Enable Google Authenticator

    + + diff --git a/html/settings/account.php b/html/settings/account.php index bc71396..1253266 100644 --- a/html/settings/account.php +++ b/html/settings/account.php @@ -6,8 +6,8 @@ $app->page->title = 'Settings - Account'; if (isset($_GET['password'])) { - $changed = $app->user->changePassword($_POST['newpassword1'], $_POST['newpassword1']); - } else if (isset($_GET['delete'])) { + $changed = $app->user->changePassword($_POST['newpassword1'], $_POST['newpassword2']); + } else if (isset($_GET['delete']) && isset($_POST['delete']) && isset($_POST['token'])) { $status = $app->user->delete($_POST['delete'], $_POST['token']); if ($status === true) { @@ -121,4 +121,4 @@ \ No newline at end of file +?> diff --git a/html/settings/image.php b/html/settings/image.php index 32fca17..0f74e82 100644 --- a/html/settings/image.php +++ b/html/settings/image.php @@ -84,7 +84,7 @@

    Use Gravatar

    @@ -38,7 +38,7 @@

    3. Changes to the Terms.


    - From time to time, HackThis!! may change, remove, add to or otherwise modify the Terms, and reserves the right to do so in its discretion. We encourage you to periodically review the Terms. All new and/or amended Terms take effect immediately. Notwithstanding the foregoing, (i) no modification to the Terms will apply to any dispute between you and HackThis!! that arose prior to the effective date of any modification and (ii) if you do not agree with any modification to the Terms, you may terminate this agreement by ceasing use of the Websites and Services. Your continued use of any Website or Service after new and/or revised Terms are effective indicate that you have read, understood and agreed to those Terms. + From time to time, HackThis!! may change, remove, add to or otherwise modify the Terms, and reserves the right to do so in its discretion. We encourage you to periodically review the Terms. All new and/or amended Terms take effect immediately. Notwithstanding the foregoing, (i) no modification to the Terms will apply to any dispute between you and HackThis!! that arose prior to the effective date of any modification and (ii) if you do not agree with any modification to the Terms, you may terminate this agreement by ceasing use of the Websites and Services. Your continued use of any Website or Service after new and/or revised Terms are effective to indicate that you have read, understood and agreed to those Terms.

    4. Provision of the Websites and Services Generally.


    @@ -90,7 +90,7 @@

    9. Disclaimer of Warranties.


    - TO THE FULLEST EXTENT PERMITTED BY THE APPLICABLE LAW, HACKTHIS!! OFFERS THE WEBSITES AND SERVICES AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WEBSITES OR SERVICES, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NONINFRINGEMENT. HACKTHIS!! DOES NOT WARRANT THAT THE FUNCTIONS OR CONTENT CONTAINED ON THE WEBSITE OR SERVICES WILL BE UNINTERRUPTED OR ERROR-FREE, THAT DEFECTS WILL BE CORRECTED, OR THAT HACKTHIS!!’S SERVERS ARE FREE OF VIRUSES OR OTHER HARMFUL COMPONENTS. HACKTHIS!! DOES NOT WARRANT OR MAKE ANY REPRESENTATION REGARDING USE OR THE RESULT OF USE OF THE CONTENT IN TERMS OF ACCURACY, RELIABILITY, OR OTHERWISE. + TO THE FULLEST EXTENT PERMITTED BY THE APPLICABLE LAW, HACKTHIS!! OFFERS THE WEBSITES AND SERVICES AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WEBSITES OR SERVICES, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NONINFRINGEMENT. HACKTHIS!! DOES NOT WARRANT THAT THE FUNCTIONS OR CONTENT CONTAINED ON THE WEBSITE OR SERVICES WILL BE UNINTERRUPTED OR ERROR-FREE, THAT DEFECTS WILL BE CORRECTED, OR THAT HACKTHIS!!’S SERVERS ARE FREE OF VIRUSES OR OTHER HARMFUL COMPONENTS. HACKTHIS!! DOES NOT WARRANT OR MAKE ANY REPRESENTATION REGARDING USE OR THE RESULT OF USE OF THE CONTENT IN TERMS OF ACCURACY, RELIABILITY, OR OTHERWISE.

    10. Limitation of Liability.


    diff --git a/html/ticker.php b/html/ticker.php index 326bcdf..6b1b856 100644 --- a/html/ticker.php +++ b/html/ticker.php @@ -58,7 +58,7 @@

    Ticker

    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 @@ - - - - - - - -
    You post will be displayed here:
    - - \ No newline at end of file diff --git a/phpunit/dbTest.php b/phpunit/dbTest.php deleted file mode 100644 index bb2c182..0000000 --- a/phpunit/dbTest.php +++ /dev/null @@ -1,62 +0,0 @@ -db = new PDO($dsn, 'ubuntu'); - } catch(PDOException $e) { - die($e->getMessage()); - } - } - - public function testInsertUser() { - $this->db->query("INSERT INTO users (`username`, `password`, `email`) VALUES ('flabbyrabbit', 'pass', 'test@test.com');"); - $this->db->query("INSERT INTO users (`username`, `password`, `email`) VALUES ('osaka', 'pass2', 'test2@test.com');"); - - $st = $this->db->query("SELECT count(user_id) AS count FROM users;"); - $row = $st->fetch(); - $this->assertEquals(2, $row['count']); - - // $st = $this->db->query("SELECT password, score FROM users WHERE username = 'flabbyrabbit"); - // $row = $st->fetch(); - // $this->assertEquals('pass', $row['password']); - // $this->assertEquals(0, $row['score']); - } - - /** - * @depends testInsertUser - */ - public function testMedals() { - // Medals - $this->db->query("INSERT INTO medals_colours (`reward`, `colour`) VALUES (100, 'bronze')"); - $this->db->query("INSERT INTO medals_colours (`reward`, `colour`) VALUES (200, 'silver')"); - $this->db->query("INSERT INTO medals (`label`, `colour_id`, `description`) VALUES ('Test', 1, 'Test')"); - $this->db->query("INSERT INTO medals (`label`, `colour_id`, `description`) VALUES ('Test', 2, 'Test')"); - - // Award medal - $this->db->query("INSERT INTO users_medals (`user_id`, `medal_id`) VALUES (1, 1)"); - $this->db->query("INSERT INTO users_medals (`user_id`, `medal_id`) VALUES (2, 1)"); - $this->db->query("INSERT INTO users_medals (`user_id`, `medal_id`) VALUES (2, 2)"); - - $res = $this->db->query("INSERT INTO users_medals (`user_id`, `medal_id`) VALUES (2, 2)"); - $this->assertFalse($res); - - // Check user scores - $st = $this->db->query("SELECT score FROM users WHERE user_id = 1"); - $row = $st->fetch(); - $this->assertEquals(100, $row['score']); - - $st = $this->db->query("SELECT score FROM users WHERE user_id = 2"); - $row = $st->fetch(); - $this->assertEquals(300, $row['score']); - - // Remove medal and check user score - $this->db->query("DELETE FROM users_medals WHERE `user_id` = 2 AND `medal_id` = 2"); - - $st = $this->db->query("SELECT score FROM users WHERE user_id = 2"); - $row = $st->fetch(); - $this->assertEquals(100, $row['score']); - } -} -?> \ No newline at end of file diff --git a/sql/schema.sql b/sql/schema.sql index 21f0e69..100571b 100644 --- a/sql/schema.sql +++ b/sql/schema.sql @@ -13,6 +13,8 @@ CREATE TABLE IF NOT EXISTS users ( `email` varchar(128) NOT NULL, `verified` tinyint(1) NOT NULL DEFAULT 0, `score` mediumint(6) NOT NULL DEFAULT 0, + `g_auth` tinyint(1), + `g_secret` varchar(255), PRIMARY KEY (`user_id`), UNIQUE KEY (`username`), UNIQUE KEY (`email`) @@ -245,7 +247,7 @@ CREATE TABLE IF NOT EXISTS levels ( CREATE TABLE IF NOT EXISTS levels_data ( `level_id` tinyint(3) UNSIGNED NOT NULL AUTO_INCREMENT, - `key` enum('author', 'reward', 'form', 'answer', 'articles', 'hint', 'description', 'solution', 'code') NOT NULL, + `key` enum('author', 'reward', 'form', 'answer', 'articles', 'hint', 'description', 'solution', 'code', 'uptime') NOT NULL, `value` text NOT NULL, PRIMARY KEY (`level_id`, `key`), FOREIGN KEY (`level_id`) REFERENCES levels (`level_id`) @@ -486,6 +488,8 @@ CREATE TABLE IF NOT EXISTS mod_reports ( `about` int(7) NOT NULL, `subject` varchar(64), `body` text, + `visible` tinyint(1) DEFAULT 1, + `time` timestamp DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`report_id`), FOREIGN KEY (`user_id`) REFERENCES users (`user_id`) ) ENGINE=InnoDB;