<?php

define('ADMSEC_DISABLE_DEACTIVATE', 1);


if(!defined('IN_MYBB'))
	die('This file cannot be accessed directly.');


function admsec_info() {
	return array(
		'name'			=> 'Admin Security Enhancements',
		'description'	=> 'Fixes some vulerabilities in the AdminCP, suggested by <a href="http://community.mybboard.net/thread-66409.html">frostschutz</a>.',
		'website'		=> 'http://mybbhacks.zingaburga.com/',
		'author'		=> 'ZiNgA BuRgA',
		'authorsite'	=> 'http://zingaburga.com/',
		'version'		=> '1.02',
		'compatibility'	=> '14*,15*,16*',
		'guid'			=> ''
	);
}



function admsec_deactivate() {
	if(defined('ADMSEC_DISABLE_DEACTIVATE') && ADMSEC_DISABLE_DEACTIVATE)
		die('Disabling this plugin is not allowed.  If you wish to deactivate this plugin, please delete the 3rd line in this plugin file (containing the ADMSEC_DISABLE_DEACTIVATE definition)');
}


function admsec_sanitize_template(&$tpl) {
	$tpl = preg_replace('~(\\{\s*\\$\s*)\\{~', '$1\\{', $tpl);
}


// patch add/edit templates
$plugins->add_hook('admin_style_templates_add_template', 'admsec_tpl_edit');
$plugins->add_hook('admin_style_templates_edit_template', 'admsec_tpl_edit');
function admsec_tpl_edit() {
	global $mybb;
	if($mybb->request_method == 'post' && $mybb->input['template'])
		admsec_sanitize_template($mybb->input['template']);
}

// patch find & replace templates too
$plugins->add_hook('admin_style_templates_search_replace', 'admsec_tpl_replace');
function admsec_tpl_replace() {
	global $mybb;
	if($mybb->input['type'] == 'templates' && $mybb->input['find'] && $mybb->request_method == 'post' && $mybb->input['replace'])
		admsec_sanitize_template($mybb->input['replace']);
}

// there's no hooks in the themes.php file :(
$plugins->add_hook('admin_load', 'admsec_import_themes_hook');
function admsec_import_themes_hook() {
	if($GLOBALS['run_module'] != 'style' || $GLOBALS['action_file'] != 'themes.php')
		return;
	global $mybb;
	if($mybb->input['action'] == 'import' && $mybb->request_method == 'post') {
		// hook into the DB to sanitize added templates
		control_object($GLOBALS['db'], '
			function insert_query($table, $array) {
				if($table == "templates") {
					admsec_import_theme_esc($array["template"]);
				}
				return parent::insert_query($table, $array);
			}
		');
		
		function admsec_import_theme_esc(&$str) {
			// $str has already gone through escape_string
			// this is really dirty and I dunno if this really works all the time
			// ideally, this shouldn't be done
			$str = preg_replace('~(\\{\s*\\$\s*)\\{~', '$1\\\\\\{', $str);
			// above is different from admsec_sanitize_template in that we do a double backslash as to escape the query
		}
	}
}

$plugins->add_hook('global_start', 'admsec_rm_db_pass');
function admsec_rm_db_pass() {
	$GLOBALS['mybb']->config['database']['backup_type'] = $GLOBALS['mybb']->config['database']['type'];
	unset($GLOBALS['mybb']->config['database']['password'], $GLOBALS['mybb']->config['database']['type']);
	// unsetting the DB type forces run_shutdown to reload the config
	// TODO: unset for multi-connection setups
}

$plugins->add_hook('global_end', 'admsec_fix_db_type');
function admsec_fix_db_type() {
	global $mybb;
	if($mybb->debug_mode && $mybb->usergroup['cancp'] == 1) {
		control_object($GLOBALS['maintimer'], '
			function stop() {
				$dbconf =& $GLOBALS[\'mybb\']->config[\'database\'];
				$dbconf[\'type\'] = $dbconf[\'backup_type\'];
				return parent::stop();
			}
		');
	}
}

// try our best to restore the config in the event of run_shutdown()
$plugins->add_hook('redirect', 'admsec_restore_config_redirect');
function admsec_restore_config_redirect(&$a) {
	global $mybb;
	if(!($mybb->settings['redirects'] == 1 && ($mybb->user['showredirect'] != 0 || !$mybb->user['uid']))) {
		include MYBB_ROOT.'inc/config.php';
		$mybb->config =& $config;
	} // if not, will call output_page anyway
}
$plugins->add_hook('post_output_page', 'admsec_restore_config_output');
function admsec_restore_config_output() {
	include MYBB_ROOT.'inc/config.php';
	$GLOBALS['mybb']->config =& $config;
}


$plugins->add_hook('admin_tools_backupdb_backup', 'admsec_backup_listblock');
function admsec_backup_listblock() {
	global $mybb;
	if($mybb->request_method == 'post') {
		if(is_array($mybb->input['tables'])) {
			$prefixlen = strlen(TABLE_PREFIX);
			foreach($mybb->input['tables'] as $k => &$table) {
				if(substr($table, 0, $prefixlen) != TABLE_PREFIX)
					unset($mybb->input['tables'][$k]);
			}
		}
	} else {
		control_object($GLOBALS['db'], '
			function list_tables($database, $prefix="") {
				if(!$prefix) $prefix = $this->table_prefix;
				return parent::list_tables($database, $prefix);
			}
		');
	}
}



if(!function_exists('control_object')) {
	function control_object(&$obj, $code) {
		static $cnt = 0;
		$newname = '_objcont_'.(++$cnt);
		$objserial = serialize($obj);
		$classname = get_class($obj);
		$checkstr = 'O:'.strlen($classname).':"'.$classname.'":';
		$checkstr_len = strlen($checkstr);
		if(substr($objserial, 0, $checkstr_len) == $checkstr) {
			$vars = array();
			// grab resources/object etc, stripping scope info from keys
			foreach((array)$obj as $k => $v) {
				if($p = strrpos($k, "\0"))
					$k = substr($k, $p+1);
				$vars[$k] = $v;
			}
			if(!empty($vars))
				$code .= '
					function ___setvars(&$a) {
						foreach($a as $k => &$v)
							$this->$k = $v;
					}
				';
			eval('class '.$newname.' extends '.$classname.' {'.$code.'}');
			$obj = unserialize('O:'.strlen($newname).':"'.$newname.'":'.substr($objserial, $checkstr_len));
			if(!empty($vars))
				$obj->___setvars($vars);
		}
		// else not a valid object or PHP serialize has changed
	}
}
