<?php
// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
//
// All Rights Reserved. See copyright.txt for details and a complete list of authors.
// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
// $Id: tiki-admin_security.php 66769 2018-06-19 22:30:06Z drsassafras $

require_once('tiki-setup.php');
$access->check_permission('tiki_p_admin');

// tikiwiki preferences check
// do we need to get the preferences or are they already loaded?
$tikisettings = [];
if ($prefs['feature_file_galleries'] == 'y' && ! empty($prefs['fgal_use_dir']) && substr($prefs['fgal_use_dir'], 0, 1) != '/') { // todo: check if absolute path is in tiki root
	$tikisettings['fgal_use_dir'] = [
		'risk' => tra('unsafe') ,
		'setting' => $prefs['fgal_use_dir'],
		'message' => tra('The Path to store files in the filegallery should be outside the tiki root directory')
	];
}
if ($prefs['feature_galleries'] == 'y' && ! empty($prefs['gal_use_dir']) && substr($prefs['gal_use_dir'], 0, 1) != '/') {
	$tikisettings['gal_use_dir'] = [
		'risk' => tra('unsafe') ,
		'setting' => $prefs['gal_use_dir'],
		'message' => tra('The Path to store files in the imagegallery should be outside the tiki root directory')
	];
}
if ($prefs['feature_edit_templates'] == 'y') {
	$tikisettings['edit_templates'] = [
		'risk' => tra('unsafe') ,
		'setting' => tra('Enabled') ,
		'message' => tra('The feature "Edit Templates" is switched on. Do not allow anyone you cannot trust to use this feature. It can easily be used to inject php code.')
	];
}
if ($prefs['wikiplugin_snarf'] == 'y') {
	$tikisettings['wikiplugin_snarf'] = [
		'risk' => tra('unsafe') ,
		'setting' => tra('Enabled') ,
		'message' => tra('The "Snarf Wikiplugin" is activated. It can be used by wiki editors to include pages from the local network and via regex replacement create any HTML.')
	];
}
if ($prefs['wikiplugin_regex'] == 'y') {
	$tikisettings['wikiplugin_regex'] = [
		'risk' => tra('unsafe') ,
		'setting' => tra('Enabled') ,
		'message' => tra('The "Regex Wikiplugin" is activated. It can be used by wiki editors to create any HTML via regex replacement.')
	];
}
if ($prefs['wikiplugin_lsdir'] == 'y') {
	$tikisettings['wikiplugin_lsdir'] = [
		'risk' => tra('unsafe') ,
		'setting' => tra('Enabled') ,
		'message' => tra('The "Lsdir Wikiplugin" is activated. It can be used by wiki editors to view the contents of any directory.')
	];
}
if ($prefs['wikiplugin_bloglist'] == 'y') {
	$tikisettings['wikiplugin_bloglist'] = [
		'risk' => tra('unsafe') ,
		'setting' => tra('Enabled') ,
		'message' => tra('The "Bloglist Wikiplugin" is activated. It can be used by wiki editors to disclose private blog posts.')
	];
}
if ($prefs['wikiplugin_iframe'] == 'y') {
	$tikisettings['wikiplugin_iframe'] = [
		'risk' => tra('unsafe') ,
		'setting' => tra('Enabled') ,
		'message' => tra('The "iframe Wikiplugin" is activated. It can be used by wiki editors for cross site scripting attacks.')
	];
}
if ($prefs['wikiplugin_js'] == 'y') {
	$tikisettings['wikiplugin_js'] = [
		'risk' => tra('unsafe') ,
		'setting' => tra('Enabled') ,
		'message' => tra('The "js Wikiplugin" is activated. It can be used by wiki editors to use Javascript, which can be used to do all kind of nasty things like cross site scripting attacks, etc.')
	];
}
if ($prefs['wikiplugin_jq'] == 'y') {
	$tikisettings['wikiplugin_jq'] = [
		'risk' => tra('unsafe') ,
		'setting' => tra('Enabled') ,
		'message' => tra('The "JQ Wikiplugin" is activated. It can be used by wiki editors to use Javascript, which can be used to do all kind of nasty things like cross site scripting attacks, etc.')
	];
}
if ($prefs['wikiplugin_redirect'] == 'y') {
	$tikisettings['wikiplugin_redirect'] = [
		'risk' => tra('unsafe') ,
		'setting' => tra('Enabled') ,
		'message' => tra('The "Redirect Wikiplugin" is activated. It can be used by wiki editors for cross site scripting attacks.')
	];
}
if ($prefs['wikiplugin_module'] == 'y') {
	$tikisettings['wikiplugin_module'] = [
		'risk' => tra('unsafe') ,
		'setting' => tra('Enabled') ,
		'message' => tra('The "Module Wikiplugin" is activated. It can be used by wiki editors to add modules which permit to access information (see module list).')
	];
}
if ($prefs['wikiplugin_userlist'] == 'y') {
	$tikisettings['wikiplugin_userlist'] = [
		'risk' => tra('unsafe') ,
		'setting' => tra('Enabled') ,
		'message' => tra('The "Userlist Wikiplugin" is activated. It can be used by wiki editors to display the list of users.')
	];
}
if ($prefs['wikiplugin_usercount'] == 'y') {
	$tikisettings['wikiplugin_usercount'] = [
		'risk' => tra('unsafe') ,
		'setting' => tra('Enabled') ,
		'message' => tra('The "Usercount Wikiplugin" is activated. It can be used by wiki editors to display a count of the number of users.')
	];
}
if ($prefs['wikiplugin_sql'] == 'y') {
	$tikisettings['wikiplugin_sql'] = [
		'risk' => tra('unsafe') ,
		'setting' => tra('Enabled') ,
		'message' => tra('The "SQL Wikiplugin" is activated. It can be used by wiki editors to execute SQL commands.')
	];
}
if ($prefs['https_login'] != 'required') {
	$tikisettings['https_login'] = [
		'risk' => tra('risky') ,
		'setting' => ucfirst($prefs['https_login']),
		'message' => tra('To the extent secure logins are not required, data transmitted between the browser and server is not private.')
	];
}

// Check if any of the mail-in accounts uses "Allow anonymous access"
if ($prefs['feature_mailin'] == 'y') {
	$mailinlib = TikiLib::lib('mailin');
	$accs = $mailinlib->list_active_mailin_accounts(0, -1, 'account_desc', '');

	// Check anonymous access
	$errorCnt = 0;
	foreach ($accs['data'] as $acc) {
		if ($acc['anonymous'] === 'y') {
			$errorCnt++;
		}
	}
	if ($errorCnt > 0) {
		$tikisettings['feature_mailin-anonymous'] = [
			'risk' => tra('unsafe') ,
			'setting' => tra('Enabled') ,
			'message' => tra('One or more mail-in accounts have enabled "Allow anonymous access", which disables all permission checking for incoming email. Check tiki-admin_mailin.php')
		];
	}

	// Check admin access
	$errorCnt = 0;
	foreach ($accs['data'] as $acc) {
		if ($acc['admin'] === 'y') {
			$errorCnt++;
		}
	}
	if ($errorCnt > 0) {
		$tikisettings['feature_mailin-admin'] = [
			'risk' => tra('unsafe') ,
			'setting' => tra('Enabled') ,
			'message' => tra('One or more mail-in accounts have enabled "Allow admin access", which allows for incoming email from admins. Admins have all rights, and web pages can easily be overwitten / tampered with. Check tiki-admin_mailin.php')
		];
	}
}

//check to see if installer lock is being used
//check multitiki
if (is_file('db/virtuals.inc')) {
	$virtuals = array_map('trim', file('db/virtuals.inc'));
	foreach ($virtuals as $v) {
		if ($v) {
			if (is_file("db/$v/local.php") && is_readable("db/$v/local.php")) {
				$virt[$v] = 'y';
			} else {
				$virt[$v] = 'n';
			}
		}
	}
} else {
	$virt = false;
	$virtuals = false;
}
$multi = '';
if ($virtuals) {
	if (isset($_SERVER['TIKI_VIRTUAL']) && is_file('db/' . $_SERVER['TIKI_VIRTUAL'] . '/local.php')) {
		$multi = $_SERVER['TIKI_VIRTUAL'];
	} elseif (isset($_SERVER['SERVER_NAME']) && is_file('db/' . $_SERVER['SERVER_NAME'] . '/local.php')) {
		$multi = $_SERVER['SERVER_NAME'];
	} elseif (isset($_SERVER['HTTP_HOST']) && is_file('db/' . $_SERVER['HTTP_HOST'] . '/local.php')) {
		$multi = $_SERVER['HTTP_HOST'];
	}
}
$tikidomain = $multi;
$tikidomainslash = (! empty($tikidomain) ? $tikidomain . '/' : '');
if (! file_exists('db/' . $tikidomainslash . 'lock')) {
	$tikisettings['installer lock'] = [
		'risk' => tra('unsafe') ,
		'setting' => tra('Unlocked') ,
		'message' => tra('The installer is not locked. The installer could be accessed, putting the database at risk of being altered or destroyed.')
	];
}

$fmap = [
	'good' => ['icon' => 'ok', 'class' => 'success'],
	'safe' => ['icon' => 'ok', 'class' => 'success'],
	'bad' => ['icon' => 'ban', 'class' => 'danger'],
	'unsafe' => ['icon' => 'ban', 'class' => 'danger'],
	'risky' => ['icon' => 'warning', 'class' => 'warning'],
	'ugly' => ['icon' => 'warning', 'class' => 'warning'],
	'info' => ['icon' => 'information', 'class' => 'info'],
	'unknown' => ['icon' => 'help', 'class' => 'muted'],
];
$smarty->assign('fmap', $fmap);


ksort($tikisettings);
$smarty->assign_by_ref('tikisettings', $tikisettings);
// array for severity in tiki_secdb table. This can go into a extra table if
// the array grows to much.
$secdb_severity = [
	//1000 Path disclosure
	1000 => tra('Path disclosure') ,
	1001 => tra('Path disclosure through error message') ,
	//2000 SQL injection
	2000 => tra('SQL injection') ,
	2001 => tra('SQL injection by authenticated user') ,
	2002 => tra('SQL injection by authenticated user with special privileges') ,
	2003 => tra('SQL injection without authentication') ,
	//3000 command injection
	3000 => tra('PHP command injection') ,
	3001 => tra('PHP command injection by authenticated user') ,
	3002 => tra('PHP command injection by authenticated user with special privileges') ,
	3003 => tra('PHP command injection without authentication') ,
	//4000 File upload
	4000 => tra('File upload')
];
// dir walk & check functions
/**
 * @param $dir
 * @param $result
 */
function md5_check_dir($dir, &$result, $svn_diff = [])
{
 // save all suspicious files in $result
	global $tikilib, $tiki_versions, $tikipath;
	$c_tiki_versions = count($tiki_versions);
	$query = "select * from `tiki_secdb` where `filename`=?";
	$d = dir($dir);
	while (false !== ($e = $d->read())) {
		$entry = $dir . '/' . $e;
		if (is_dir($entry)) {
			if ($e != '..' && $e != '.' && $entry != './temp/templates_c' && $entry != './temp') { // do not descend and no checking of templates_c since the file based md5 database would grow to big
				md5_check_dir($entry, $result, $svn_diff);
			}
		} elseif (preg_match('/\.(sql|css|tpl|js|php)$/', $e)) {
			if (! is_readable($entry)) {
				$result[$entry] = tra('File is not readable. Unable to check.');
			} else {
				$md5val = md5_file($entry);
				$dbresult = $tikilib->query($query, [$entry]);
				$is_tikifile = false;
				$is_tikiver = [];
				$valid_tikiver = [];
				$severity = 0;
				// we could avoid the following with a second sql, but i think, this is faster.
				while ($res = $dbresult->FetchRow()) {
					$is_tikifile = true; // we know the filename ... probably modified
					if ($res['md5_value'] == $md5val) {
						$is_tikiver[] = $res['tiki_version']; // found
						$severity = $res['severity'];
					}
					$k = array_search($res['tiki_version'], $tiki_versions);
					if ($k > 0) {
						//record the valid versions in this array
						if ($res['md5_value'] == $md5val) {
							$valid_tikiver[$k] = true;
						} else {
							$valid_tikiver[$k] = false;
						}
					}
				}
				if ($is_tikifile == false) {
					if ($svn_diff && isset($svn_diff[substr($entry, 2)]) && $svn_diff[substr($entry, 2)] !== 'unversioned') {
						$result[$entry] = tra('This Tiki file differs from the SVN repository version. Check if this file was uploaded and if it is dangerous.');
					} else {
						$result[$entry] = tra('This is not a Tiki file. Check if this file was uploaded and if it is dangerous.');
					}
				} elseif ($is_tikifile == true && count($is_tikiver) == 0) {
					$result[$entry] = tra('This is a modified File. Cannot check version. Check if it is dangerous.');
				} else {
					// check if we have a most recent valid version
					$most_recent = false;
					for ($i = $c_tiki_versions; $i > 0; $i--) { // search $valid_tikiver top to down to find the most recent version
						if (isset($valid_tikiver[$i])) {
							if ($valid_tikiver[$i] == false) {
								//$most_recent stays false. we break
								break;
							} else {
								$most_recent = true; // in this case we have found the most recent version. good
								break;
							}
						}
					}
					// use result of most_recent to decide
					if ($most_recent == false) {
						$result[$entry] = tra('This file is from another Tiki version: ') . implode(' ' . tra('or') . ' ', $is_tikiver);
					}
				}
			}
		}
	}
	$d->close();
}
// if check installation is pressed, walk through all files and compute md5 sums
if (isset($_POST['check_files'])) {
	global $tiki_versions;
	require_once('lib/setup/twversion.class.php');
	$version = new TWVersion();
	$tiki_versions = $version->tikiVersions();
	$tiki_versions[] = $version->version;
	$result = [];

	if (preg_match('/svn$/', $version->version) && is_readable('doc/devtools/svntools.php')) {	// svn checkout
		require_once('doc/devtools/svntools.php');
		$svn_diff = svn_files_differ('./');
	} else {
		$svn_diff = [];
	}

	md5_check_dir(".", $result, $svn_diff);
	$smarty->assign('filecheck', true);
	$smarty->assign_by_ref('tikifiles', $result);
}
define('S_ISUID', '2048');
define('S_ISGID', '1024');
define('S_ISVTX', '512');
define('S_IRUSR', '256');
define('S_IWUSR', '128');
define('S_IXUSR', '64');
define('S_IRGRP', '32');
define('S_IWGRP', '16');
define('S_IXGRP', '8');
define('S_IROTH', '4');
define('S_IWOTH', '2');
define('S_IXOTH', '1');
// Function to check Filesystem permissions
/**
 * @param $dir
 * @param $result
 */
function check_dir_perms($dir, &$result)
{
	static $depth = 0;
	$depth++;
	$d = dir($dir);
	while (false !== ($e = $d->read())) {
		$entry = $dir . '/' . $e;
		if ($e != '..' && ($e != '.' || $depth == 1)) {
			$result[$entry]['w'] = is_writable($entry);
			$result[$entry]['r'] = is_readable($entry);
			$result[$entry]['t'] = filetype($entry);
			$s = stat($entry);
			if (function_exists('posix_getpwuid')) {
				$t = posix_getpwuid($s['uid']);
				$result[$entry]['u'] = $t['name'];
				$t = posix_getgrgid($s['gid']);
				$result[$entry]['g'] = $t['name'];
			} else {
				$result[$entry]['u'] = $s['uid'];
				$result[$entry]['g'] = $s['gid'];
			}
			$m = (int)$s['mode'];
			if ($m >= 32768) {
				$m -= 32768; // clear file type indicators
			}
			if ($m >= 16384) {
				$m -= 16384;
			}
			if ($m >= 8192) {
				$m -= 8192;
			}
			if ($m >= 4096) {
				$m -= 4096;
			}
			$result[$entry]['p'] = $m;
			$result[$entry]['suid'] = ($m >= S_ISUID && ($m -= S_ISUID) >= 0);
			$result[$entry]['sgid'] = ($m >= S_ISGID && ($m -= S_ISGID) >= 0);
			$result[$entry]['sticky'] = ($m >= S_ISVTX && ($m -= S_ISVTX) >= 0);
			$result[$entry]['ur'] = ($m >= S_IRUSR && ($m -= S_IRUSR) >= 0);
			$result[$entry]['uw'] = ($m >= S_IWUSR && ($m -= S_IWUSR) >= 0);
			$result[$entry]['ux'] = ($m >= S_IXUSR && ($m -= S_IXUSR) >= 0);
			$result[$entry]['gr'] = ($m >= S_IRGRP && ($m -= S_IRGRP) >= 0);
			$result[$entry]['gw'] = ($m >= S_IWGRP && ($m -= S_IWGRP) >= 0);
			$result[$entry]['gx'] = ($m >= S_IXGRP && ($m -= S_IXGRP) >= 0);
			$result[$entry]['or'] = ($m >= S_IROTH && ($m -= S_IROTH) >= 0);
			$result[$entry]['ow'] = ($m >= S_IWOTH && ($m -= S_IWOTH) >= 0);
			$result[$entry]['ox'] = ($m >= S_IXOTH && ($m -= S_IXOTH) >= 0);
			if ($result[$entry]['t'] == 'dir' && $e != '.') {
				check_dir_perms($entry, $result);
			}
		}
	}
	$depth--;
}
if (isset($_REQUEST['check_file_permissions'])) {
	$fileperms = [];
	check_dir_perms('.', $fileperms);
	// walk throug array to find problematic entries
	$worldwritable = [];
	$suid = [];
	$executable = [];
	$strangeinode = [];
	$apachewritable = [];
	foreach ($fileperms as $fname => $fperms) {
		if ($fperms['suid']) {
			$suid[$fname] = & $fileperms[$fname];
		}
		if ($fperms['ow']) {
			$worldwritable[$fname] = & $fileperms[$fname];
		}
		if ($fperms['t'] != 'dir' && ($fperms['ux'] || $fperms['gx'] || $fperms['ox'])) {
			$executable[$fname] = & $fileperms[$fname];
		}
		if ($fperms['t'] != 'dir' && $fperms['t'] != 'file' && $fperms['t'] != 'link') {
			$strangeinode[$fname] = & $fileperms[$fname];
		}
		if ($fperms['w']) {
			$apachewritable[$fname] = & $fileperms[$fname];
		}
	}
	$smarty->assign_by_ref('worldwritable', $worldwritable);
	$smarty->assign_by_ref('suid', $suid);
	$smarty->assign_by_ref('executable', $executable);
	$smarty->assign_by_ref('strangeinode', $strangeinode);
	$smarty->assign_by_ref('apachewritable', $apachewritable);
	$smarty->assign('permcheck', true);
}
// disallow robots to index page:
$smarty->assign('metatag_robots', 'NOINDEX, NOFOLLOW');
$smarty->assign('mid', 'tiki-admin_security.tpl');
$smarty->display("tiki.tpl");
