diff --git a/composer.json b/composer.json index 5f574aa6..e9ceac19 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,8 @@ "inc/queue.php", "inc/polyfill.php", "inc/error.php", - "inc/functions.php" + "inc/functions.php", + "inc/functions/net.php" ] }, "license": "Tinyboard + vichan", diff --git a/inc/config.php b/inc/config.php index 10d7a1e3..d8ae7584 100644 --- a/inc/config.php +++ b/inc/config.php @@ -172,7 +172,7 @@ // How long should the cookies last (in seconds). Defines how long should moderators should remain logged // in (0 = browser session). - $config['cookies']['expire'] = 60 * 60 * 24 * 30 * 6; // ~6 months + $config['cookies']['expire'] = 60 * 60 * 24 * 7; // 1 week. // Make this something long and random for security. $config['cookies']['salt'] = 'abcdefghijklmnopqrstuvwxyz09123456789!@#$%^&*()'; @@ -180,6 +180,10 @@ // Whether or not you can access the mod cookie in JavaScript. Most users should not need to change this. $config['cookies']['httponly'] = true; + // Do not allow logins via unencrypted HTTP. Should only be changed in testing environments or if you connect to a + // load-balancer without encryption. + $config['cookies']['secure_login_only'] = true; + // Used to salt secure tripcodes ("##trip") and poster IDs (if enabled). $config['secure_trip_salt'] = ')(*&^%$#@!98765432190zyxwvutsrqponmlkjihgfedcba'; @@ -1216,6 +1220,7 @@ // Moderator errors $config['error']['toomanyunban'] = _('You are only allowed to unban %s users at a time. You tried to unban %u users.'); $config['error']['invalid'] = _('Invalid username and/or password.'); + $config['error']['insecure'] = _('Login on insecure connections is disabled.'); $config['error']['notamod'] = _('You are not a mod…'); $config['error']['invalidafter'] = _('Invalid username and/or password. Your user may have been deleted or changed.'); $config['error']['malformed'] = _('Invalid/malformed cookies.'); diff --git a/inc/functions/net.php b/inc/functions/net.php new file mode 100644 index 00000000..ab08c3cb --- /dev/null +++ b/inc/functions/net.php @@ -0,0 +1,10 @@ +bindValue(':username', $username); $query->execute() or error(db_error($query)); - + if ($user = $query->fetch(PDO::FETCH_ASSOC)) { list($version, $ok) = test_password($user['password'], $user['version'], $password); @@ -108,7 +98,7 @@ function login($username, $password) { ); } } - + return false; } @@ -116,20 +106,23 @@ function setCookies() { global $mod, $config; if (!$mod) error('setCookies() was called for a non-moderator!'); - + + $is_https = Net\is_connection_secure(); + setcookie($config['cookies']['mod'], $mod['username'] . // username - ':' . + ':' . $mod['hash'][0] . // password ':' . $mod['hash'][1], // salt - time() + $config['cookies']['expire'], $config['cookies']['jail'] ? $config['cookies']['path'] : '/', null, !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off', $config['cookies']['httponly']); + time() + $config['cookies']['expire'], $config['cookies']['jail'] ? $config['cookies']['path'] : '/', null, $is_https, $config['cookies']['httponly']); } function destroyCookies() { global $config; + $is_https = Net\is_connection_secure(); // Delete the cookies - setcookie($config['cookies']['mod'], 'deleted', time() - $config['cookies']['expire'], $config['cookies']['jail']?$config['cookies']['path'] : '/', null, !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off', true); + setcookie($config['cookies']['mod'], 'deleted', time() - $config['cookies']['expire'], $config['cookies']['jail']?$config['cookies']['path'] : '/', null, $is_https, true); } function modLog($action, $_board=null) { @@ -146,36 +139,36 @@ function modLog($action, $_board=null) { else $query->bindValue(':board', null, PDO::PARAM_NULL); $query->execute() or error(db_error($query)); - + if ($config['syslog']) _syslog(LOG_INFO, '[mod/' . $mod['username'] . ']: ' . $action); } function create_pm_header() { global $mod, $config; - + if ($config['cache']['enabled'] && ($header = cache::get('pm_unread_' . $mod['id'])) != false) { if ($header === true) return false; - + return $header; } - + $query = prepare("SELECT `id` FROM ``pms`` WHERE `to` = :id AND `unread` = 1"); $query->bindValue(':id', $mod['id'], PDO::PARAM_INT); $query->execute() or error(db_error($query)); - + if ($pm = $query->fetch(PDO::FETCH_ASSOC)) $header = array('id' => $pm['id'], 'waiting' => $query->rowCount() - 1); else $header = true; - + if ($config['cache']['enabled']) cache::set('pm_unread_' . $mod['id'], $header); - + if ($header === true) return false; - + return $header; } @@ -186,6 +179,7 @@ function make_secure_link_token($uri) { function check_login($prompt = false) { global $config, $mod; + // Validate session if (isset($_COOKIE[$config['cookies']['mod']])) { // Should be username:hash:salt @@ -196,12 +190,12 @@ function check_login($prompt = false) { if ($prompt) mod_login(); exit; } - + $query = prepare("SELECT `id`, `type`, `boards`, `password` FROM ``mods`` WHERE `username` = :username"); $query->bindValue(':username', $cookie[0]); $query->execute() or error(db_error($query)); $user = $query->fetch(PDO::FETCH_ASSOC); - + // validate password hash if ($cookie[1] !== mkhash($cookie[0], $user['password'], $cookie[2])) { // Malformed cookies @@ -209,7 +203,7 @@ function check_login($prompt = false) { if ($prompt) mod_login(); exit; } - + $mod = array( 'id' => (int)$user['id'], 'type' => (int)$user['type'], diff --git a/inc/mod/pages.php b/inc/mod/pages.php index 31f76133..066afa51 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -4,8 +4,11 @@ * Copyright (c) 2010-2013 Tinyboard Development Group */ +use Vichan\Functions\Net; + defined('TINYBOARD') or exit; + function mod_page($title, $template, $args, $subtitle = false) { global $config, $mod; @@ -35,9 +38,11 @@ function clone_wrapped_with_exist_check($clonefn, $src, $dest) { function mod_login($redirect = false) { global $config; - $args = array(); + $args = []; - if (isset($_POST['login'])) { + if ($config['cookies']['secure_login_only'] && !Net\is_connection_secure()) { + $args['error'] = $config['error']['insecure']; + } elseif (isset($_POST['login'])) { // Check if inputs are set and not empty if (!isset($_POST['username'], $_POST['password']) || $_POST['username'] == '' || $_POST['password'] == '') { $args['error'] = $config['error']['invalid'];