4chan/reports/changepass.php
2025-04-17 18:12:08 -05:00

310 lines
8.2 KiB
PHP

<?php
require_once 'lib/db.php';
require_once 'lib/auth.php';
require_once 'csp.php';
/*
$mysql_suppress_err = false;
ini_set('display_errors', 1);
error_reporting(E_ALL);
*/
define('IN_APP', true);
class ChangePass {
protected
$error = null,
// Routes
$actions = array(
'index',
'change'/*,
'force'*/
);
const
MODS = '/www/global/htpasswd/moderators',
JANITORS = '/www/global/htpasswd/janitors',
JANITORS_NGINX = '/www/global/htpasswd/janitors_nginx',
ADMINS = '/www/global/htpasswd/admins',
MANAGERS = '/www/global/htpasswd/managers',
DEVS = '/www/global/htpasswd/developers',
DEVS_NGINX = '/www/global/htpasswd/developers_nginx'
;
const
S_BADAUTH = 'Username or old password incorrect.',
S_MISMATCH = 'Your new passwords did not match.',
S_TOOWEAK = 'You password must be at least 8 characters long and contain one letter and one digit.',
S_TOOLONG = 'Your password is too long.',
S_SAMEPASS = 'Your new password may not match your old one.',
S_ERROR = 'Internal Server Error',
S_OK = 'Password changed successfully.',
S_BADOTP = 'Invalid or expired OTP.'
;
/**
* Renders HTML template
*/
private function renderHTML($view) {
include('views/' . $view . '.tpl.php');
}
private function error($msg) {
$this->error = $msg;
if ($msg === self::S_BADAUTH) {
header('HTTP/1.0 403 Forbidden');
}
$this->renderHTML('changepass');
die();
}
private function get_csrf_token() {
return bin2hex(openssl_random_pseudo_bytes(16));
}
private function updateHtpasswd($file, $username, $password, $nginx = false) {
if ($nginx) {
$args = '-b';
}
else {
$args = '-Bb -C 10';
}
system("/usr/local/www/bin/htpasswd $args "
. escapeshellarg($file) . " "
. escapeshellarg($username) . " "
. escapeshellarg($password),
$ret_status
);
if ($ret_status != 0) {
$this->error(self::S_ERROR . ' (pwd' . (int)$ret_status . ')');
}
}
/**
* Login
*/
public function change() {
if (!isset($_POST['userlogin'])
|| !isset($_POST['passlogin'])
|| !isset($_POST['new_password'])
|| !isset($_POST['new_password2'])) {
$this->error(self::S_BADAUTH);
}
if (!isset($_COOKIE['csrf']) || !isset($_POST['csrf'])
|| $_COOKIE['csrf'] === '' || $_POST['csrf'] === '') {
$this->error(self::S_BADAUTH);
}
if ($_COOKIE['csrf'] !== $_POST['csrf']) {
$this->error(self::S_BADAUTH);
}
$username = $_POST['userlogin'];
$password = $_POST['passlogin'];
$new_password = $_POST['new_password'];
$new_password2 = $_POST['new_password2'];
if ($username === ''
|| $password === ''
|| $new_password === ''
|| $new_password2 === '') {
$this->error(self::S_BADAUTH);
}
if (strlen($new_password) > 100) {
$this->error(self::S_TOOLONG);
}
if ($new_password !== $new_password2) {
$this->error(self::S_MISMATCH);
}
/*
if ($new_password === $password) {
$this->error(self::S_SAMEPASS);
}
*/
if (strlen($new_password) < 8
|| !preg_match('/[0-9]/', $new_password)
|| !preg_match('/[a-z]/i', $new_password)) {
$this->error(self::S_TOOWEAK);
}
mysql_global_connect();
$query = "SELECT * FROM `mod_users` WHERE `username` = '%s' LIMIT 1";
$res = mysql_global_call($query, $username);
if (!mysql_num_rows($res)) {
$this->error(self::S_BADAUTH);
}
$user = mysql_fetch_assoc($res);
if ($user['auth_secret']) {
if (!isset($_POST['otp']) || !preg_match('/^[0-9]+$/', $_POST['otp'])) {
$this->error(self::S_BADOTP);
}
require_once 'lib/GoogleAuthenticator.php';
$ga = new PHPGangsta_GoogleAuthenticator();
$dec_secret = auth_decrypt($user['auth_secret']);
if ($dec_secret === false) {
$this->error(self::S_ERROR);
}
if (!$ga->verifyCode($dec_secret, $_POST['otp'], 1)) {
$this->error(self::S_BADOTP);
}
}
if (!$user) {
$this->error(self::S_BADAUTH);
}
if (!password_verify($password, $user['password'])) {
$this->error(self::S_BADAUTH);
}
// Update table
$query = "UPDATE `mod_users` SET password = '%s', password_expired = 0 WHERE username = '%s' LIMIT 1";
$res = mysql_global_call($query, password_hash($new_password, PASSWORD_DEFAULT), $username);
if (mysql_affected_rows() !== 1) {
$this->error(self::S_ERROR . ' (4)');
}
// htpasswd
$isAdmin = $user['level'] === 'admin';
$isManager = ($user['level'] === 'manager') || ($user['username'] === 'desuwa');
$isMod = $user['level'] === 'mod';
$isJanitor = $user['level'] === 'janitor';
$isDev = strpos($user['flags'], 'developer') !== false;
$this->updateHtpasswd(self::JANITORS, $username, $new_password);
$this->updateHtpasswd(self::JANITORS_NGINX, $username, $new_password, true);
if ($isAdmin || $isManager || $isMod) {
$this->updateHtpasswd(self::MODS, $username, $new_password);
}
if ($isAdmin || $isManager) {
$this->updateHtpasswd(self::MANAGERS, $username, $new_password);
}
if ($isAdmin/* || ($isDev && $username === 'desuwa')*/) {
$this->updateHtpasswd(self::ADMINS, $username, $new_password);
}
if ($isDev) {
$this->updateHtpasswd(self::DEVS, $username, $new_password);
$this->updateHtpasswd(self::DEVS_NGINX, $username, $new_password, true);
}
// Delete cookies
$cookie_ttl = $_SERVER['REQUEST_TIME'] - 3600;
setcookie('4chan_auser', '', $cookie_ttl, '/', '.4chan.org', true, true);
setcookie('4chan_apass', '', $cookie_ttl, '/', '.4chan.org', true, true);
setcookie('apass', '', $cookie_ttl, '/', '.4chan.org', true, true);
setcookie('csrf', '', $cookie_ttl, '/changepass', '.4chan.org', true, true);
// Remove after migrating from 4chan_asession -> apass
setcookie('4chan_asession', '', $cookie_ttl, '/', '.4chan.org', true, true);
$this->mode = 'success';
$this->renderHTML('changepass');
}
public function force() {
die();
$mysql_suppress_err = false;
ini_set('display_errors', 1);
error_reporting(E_ALL);
require_once 'lib/admin.php';
require_once 'lib/auth.php';
auth_user();
if (!has_flag('developer') || !isset($_GET['password']) || !isset($_GET['username'])) {
$this->error('Bad request');
}
$username = $_GET['username'];
$password = $_GET['password'];
if (!$username || !$password) {
$this->error('Bad request');
}
$query = "UPDATE `mod_users` SET password = '%s', password_expired = 0 WHERE username = '%s' LIMIT 1";
$res = mysql_global_call($query, password_hash($password, PASSWORD_DEFAULT), $username);
if (mysql_affected_rows() !== 1) {
$this->error(self::S_ERROR . ' (4)');
}
$this->updateHtpasswd(self::JANITORS, $username, $password);
$this->updateHtpasswd(self::JANITORS_NGINX, $username, $password, true);
//$this->updateHtpasswd(self::MODS, $username, $password);
}
/**
* Default page
*/
public function index() {
$this->mode = 'prompt';
$this->csrf = $this->get_csrf_token();
if (isset($_COOKIE['4chan_auser'])) {
$this->username = htmlspecialchars($_COOKIE['4chan_auser'], ENT_QUOTES);
}
else {
$this->username = false;
}
setcookie('csrf', $this->csrf, 0, '/changepass', '.4chan.org', true, true);
$this->renderHTML('changepass');
}
/**
* Main
*/
public function run() {
if ($_SERVER['HTTP_HOST'] !== 'reports.4chan.org') {
$this->error('Bad request');
}
$method = $_SERVER['REQUEST_METHOD'] === 'POST' ? $_POST : $_GET;
if (isset($method['action'])) {
$action = $method['action'];
}
else {
$action = 'index';
}
if (in_array($action, $this->actions)) {
$this->$action();
}
else {
$this->error('Bad request');
}
}
}
$ctrl = new ChangePass();
$ctrl->run();