sagacity/exec/installer.php
Ryan Prather 7f2f6a9046 Fix for #60
Some formatting
Die if installer.php fails to create password file
Update target counts after importing eChecklist and CKL
2018-10-25 17:48:57 -04:00

484 lines
16 KiB
PHP

<?php
/**
* File: installer.php
* Author: Ryan Prather <ryan.prather@cyberperspectives.com>
* Purpose: This script runs the installer processes
* Created: Nov 28, 2017
*
* Copyright 2017-2018: Cyber Perspective, LLC, All rights reserved
* Released under the Apache v2.0 License
*
* See license.txt for details
*
* Change Log:
* - Nov 28, 2017 - File created
* - Dec 27, 2017 - Fixed bug with SCG showing empty, and added download progress meta keys
* - Jan 2, 2018 - Add sleep to fix bug #357 race condition
* - Jan 10, 2018 - Formatting
* - Apr 29, 2018 - Removed settings to move to .sql file. Also, changed to pull CVEs from NVD instead of Mitre repo
*/
include_once 'helper.inc';
set_time_limit(0);
$params = [
'filter' => FILTER_SANITIZE_STRING,
'flag' => FILTER_NULL_ON_FAILURE
];
$db_step = [
'doc-root' => $params,
'pwd-file' => $params,
'tmp-path' => $params,
'log-path' => $params,
'log-level' => $params,
'db-server' => $params,
'root-uname' => $params,
'root-pwd' => $params,
'conf-root-pwd' => $params,
'web-pwd' => $params,
'local-path' => $params,
'action' => $params,
'sample-data' => ['filter' => FILTER_VALIDATE_BOOLEAN],
'cpe' => ['filter' => FILTER_VALIDATE_BOOLEAN],
'cve' => ['filter' => FILTER_VALIDATE_BOOLEAN],
'stig' => ['filter' => FILTER_VALIDATE_BOOLEAN],
'update-freq' => ['filter' => FILTER_VALIDATE_FLOAT, 'flag' => FILTER_NULL_ON_FAILURE]
];
$company_step = [
'company' => $params,
'comp-add' => $params,
'last-modified' => $params,
'creator' => $params,
'system-class' => $params,
'classified-by' => $params,
'scg' => $params,
'derived-on' => $params,
'declassify-on' => $params
];
$options_step = [
'flatten' => ['filter' => FILTER_VALIDATE_BOOLEAN],
'wrap-text' => ['filter' => FILTER_VALIDATE_BOOLEAN],
'notifications' => ['filter' => FILTER_VALIDATE_BOOLEAN],
'port-limit' => [
'filter' => FILTER_VALIDATE_INT,
'flag' => FILTER_REQUIRE_ARRAY,
'options' => ['max_range' => 10000]
],
'max-results' => [
'filter' => FILTER_VALIDATE_INT,
'flag' => FILTER_REQUIRE_ARRAY,
'options' => ['min_range' => 1, 'max_range' => 20]
],
'output-format' => [
'filter' => FILTER_VALIDATE_REGEXP,
'flag' => FILTER_NULL_ON_FAILURE,
'options' => ['regexp' => "/xlsx|xls|html|csv|pdf|ods/"]
]
];
$step = filter_input(INPUT_POST, 'step', FILTER_VALIDATE_INT);
if ($step == 0) {
$fields = filter_input_array(INPUT_POST, $db_step);
save_Database($fields);
}
elseif ($step == 1) {
$fields = filter_input_array(INPUT_POST, $company_step);
save_Company($fields);
}
elseif ($step == 2) {
$fields = filter_input_array(INPUT_POST, $options_step);
save_Options($fields);
}
/**
* Function to save database details and load data
*
* @param array $params
*/
function save_Database($params)
{
$config = file_get_contents("config.inc", FILE_USE_INCLUDE_PATH);
$php = null;
$mysql = null;
if (strtolower(substr(PHP_OS, 0, 3)) == 'lin') {
$res = [];
exec("which php", $res);
if (file_exists('/bin/php')) {
$php = realpath("/bin/php");
}
elseif (is_array($res) && isset($res[0]) && file_exists($res[0])) {
$php = realpath($res[0]);
}
else {
die(json_encode(['error' => 'Cannot find the PHP executable']));
}
$res = [];
exec("which mysql", $res);
if (file_exists('/bin/mysql')) {
$mysql = realpath('/bin/mysql');
}
elseif (is_array($res) && isset($res[0]) && file_exists($res[0])) {
$mysql = realpath($res[0]);
}
else {
die(json_encode(['error' => 'Cannot find the MySQL executable']));
}
}
else {
if (file_exists("c:/xampp/php/php.exe")) {
$php = realpath("c:/xampp/php/php.exe");
}
else {
die(json_encode(['error' => 'Cannot find the PHP executable']));
}
if (file_exists("c:/xampp/mysql/bin/mysql.exe")) {
$mysql = realpath("c:/xampp/mysql/bin/mysql.exe");
}
else {
die(json_encode(['error' => 'Cannot find the MySQL executable']));
}
}
my_str_replace("{DOC_ROOT}", realpath($params['doc-root']), $config);
my_str_replace("{PWD_FILE}", $params['pwd-file'], $config);
my_str_replace("'{E_ERROR}'", "E_{$params['log-level']}", $config);
my_str_replace("{PHP_BIN}", $php, $config);
my_str_replace("{PHP_CONF}", realpath(php_ini_loaded_file()), $config);
my_str_replace("{DB_SERVER}", $params['db-server'], $config);
my_str_replace("{DB_BIN}", $mysql, $config);
my_str_replace("'{UPDATE_FREQ}'", $params['update-freq'], $config);
my_str_replace("@new", "@step1", $config);
if (!file_exists($params['tmp-path'])) {
if (!mkdir($params['tmp-path'])) {
die(json_encode(['error' => 'Temporary path is not available. Please create and give Apache user write permissions']));
}
}
elseif (!is_dir($params['tmp-path']) || !is_writable($params['tmp-path'])) {
die(json_encode(['error' => 'TMP path is not a writable directory to Apache']));
}
my_str_replace("{TMP_PATH}", $params['tmp-path'], $config);
if (!file_exists($params['log-path'])) {
if (!mkdir($params['log-path'])) {
die(json_encode(['error' => 'Log path is not available. Please create and give Apache user write permissions']));
}
}
elseif (!is_dir($params['log-path']) || !is_writable($params['log-path'])) {
die(json_encode(['error' => 'Log path is not a writable directory by Apache']));
}
my_str_replace("{LOG_PATH}", $params['log-path'], $config);
file_put_contents("{$params['doc-root']}/config.inc", $config);
include_once 'config.inc';
include_once 'database.inc';
/* ---------------------------------
* CREATE DB PASSWORD FILE
* --------------------------------- */
$enc_pwd = my_encrypt($params['web-pwd']);
if(!file_put_contents(DOC_ROOT . "/" . PWD_FILE, $enc_pwd)) {
die(json_encode(['error' => "Could not create the password file"]));
}
if (isset($params['conf-root-pwd']) && $params['conf-root-pwd'] == $params['root-pwd']) {
$db = new mysqli(DB_SERVER, $params['root-uname'], '', 'mysql');
if (!$db->real_query("UPDATE user SET Password=PASSWORD('{$db->real_escape_string($params['root-pwd'])}') WHERE User='root'")) {
error_log($db->error);
die(json_encode(['error' => "Could not set the root users password, manually set it and try this again"]));
}
$db->real_query("FLUSH PRIVILEGES");
unset($db);
}
$zip = new ZipArchive();
$db = new mysqli(DB_SERVER, $params['root-uname'], $params['root-pwd'], 'mysql');
if ($db->connect_errno && $db->connect_errno == 1045) {
die(json_encode(['error' => 'There was a problem with the user/password combination, please go back and try again']));
}
elseif ($db->connect_errno) {
die(json_encode(['error' => "There was an error connecting to the database on " . DB_SERVER . " with user {$params['root-uname']} and {$params['root-pwd']}"]));
}
$help = new db_helper($db);
$svr_ver = (int) $db->server_version;
$maj = (int) ($svr_ver / 10000);
$svr_ver -= ($maj * 10000);
$min = (int) ($svr_ver / 100);
$svr_ver -= ($min * 100);
$update = $svr_ver;
if (version_compare("{$maj}.{$min}.{$update}", "5.5", "<=")) {
die(json_encode(['error' => "The current version of MySQL needs to be at least 5.5"]));
}
// set the character set and default database
$db->set_charset("utf8");
/* --------------------------------
* USER MANAGEMENT
* -------------------------------- */
$help->delete("mysql.user", null, [
[
'field' => 'User',
'op' => '=',
'value' => ''
]
]);
$help->execute();
$errors = [];
/* --------------------------------
* SCHEMA MANAGEMENT
* -------------------------------- */
if (!$db->real_query("CREATE DATABASE IF NOT EXISTS `rmf`")) {
$errors[] = $db->error;
}
if (!$db->real_query("CREATE DATABASE IF NOT EXISTS `sagacity`")) {
$errors[] = $db->error;
}
$db->real_query("DROP DATABASE IF EXISTS cdcol");
$db->real_query("DROP DATABASE IF EXISTS phpmyadmin");
$db->real_query("DROP DATABASE IF EXISTS test");
/* --------------------------------
* SET SCHEMA PERMISSIONS
* -------------------------------- */
$host = '%';
if (in_array(strtolower(DB_SERVER), ["localhost", "127.0.0.1"])) {
$host = 'localhost';
}
$help->select("mysql.user", ["COALESCE(COUNT(1), 0) AS 'count'"], [
[
'field' => 'User',
'op' => '=',
'value' => 'web'
]
]);
if (!$help->execute()['count']) {
if (!$db->real_query("CREATE USER 'web'@'$host' IDENTIFIED BY '{$db->real_escape_string($params['web-pwd'])}'")) {
$errors[] = $db->error;
}
}
else {
if (!$db->real_query("SET PASSWORD FOR 'web'@'$host' = PASSWORD('{$db->real_escape_string($params['web-pwd'])}')")) {
$errors[] = $db->error;
}
}
if (!$db->real_query("GRANT ALL ON `rmf`.* TO 'web'@'$host'")) {
$errors[] = $db->error;
}
if (!$db->real_query("GRANT ALL ON `sagacity`.* TO 'web'@'$host'")) {
$errors[] = $db->error;
}
if (count($errors)) {
die(json_encode(['errors' => implode("<br />", $errors)]));
}
$db->real_query("FLUSH PRIVILEGES");
chdir(realpath(DOC_ROOT));
$json = json_decode(file_get_contents("db_schema.json"));
foreach ($json->tables as $table) {
Sagacity_Error::err_handler("Creating {$table->schema}.{$table->name}");
$help->create_table_json($table);
if (isset($table->triggers)) {
// see if the first entry is a drop statement, run it and remove for subsequent statements
if (substr($table->triggers[0], 0, 4) == 'DROP') {
$db->real_query($table->triggers[0]);
unset($table->triggers[0]);
}
// concatenate the trigger into one string
$trig = implode(" ", $table->triggers);
if (!$db->real_query(str_replace("{host}", $host, $trig))) {
die($db->error);
}
}
$help->insert("sagacity.settings", [
'meta_key' => "{$table->schema}.{$table->name}",
'db_data' => json_encode($table)
], true);
if (!$help->execute()) {
$help->debug(E_WARNING, "JSON for {$table->schema}.{$table->name} table was not pushed to database");
}
}
/*
* ***********************************************************
* Load table data
* ***********************************************************
*/
chdir(DOC_ROOT);
$zip->open("Database_Baseline.zip");
$zip->extractTo("Database_Baseline");
chdir("Database_Baseline");
$sql_files = glob("*.sql");
$zip->close();
if (!$params['sample-data']) {
if (($key = array_search("sample_data.sql", $sql_files)) !== false) {
unset($sql_files[$key]);
unlink("sample_data.sql");
}
}
$defaults = <<<EOO
[client]
password="{$params['root-pwd']}"
port=3306
EOO;
file_put_contents(realpath(TMP) . "/defaults.tmp", $defaults);
$routines = glob("*routines.sql");
foreach ($routines as $file) {
if (($key = array_search($file, $sql_files)) !== false) {
unset($sql_files[$key]);
}
}
if (count($sql_files)) {
sort($sql_files);
foreach ($sql_files as $file) {
$output = [];
$cmd = realpath(DB_BIN) . " --defaults-file=\"" . realpath(TMP . "/defaults.tmp") . "\"" .
" --user={$params['root-uname']}" .
" --host=" . DB_SERVER .
" --default-character-set=utf8 < \"$file\"";
exec($cmd, $output);
if (preg_grep("/Access Denied/i", $output)) {
$errors[] = $output;
}
else {
unlink($file);
}
}
foreach ($routines as $file) {
$str = file_get_contents($file);
my_str_replace("{host}", $host, $str);
file_put_contents($file, $str);
$cmd = realpath(DB_BIN) . " --defaults-file=\"" . realpath(TMP . "/defaults.tmp") . "\"" .
" --user={$params['root-uname']}" .
" --host=" . DB_SERVER .
" --default-character-set=utf8 < \"$file\"";
exec($cmd);
unlink($file);
flush();
}
}
if (count($errors)) {
print json_encode(['errors' => implode("<br />", $errors)]);
return;
}
unlink(realpath(TMP . "/defaults.tmp"));
rmdir(realpath(DOC_ROOT . "/Database_Baseline"));
$cpe = null;
$cve = null;
$stig = null;
$action = null;
if ($params['cpe']) {
$cpe = " --cpe";
}
if ($params['cve']) {
$cve = " --nvd";
}
if ($params['stig']) {
$stig = " --stig";
}
$msg = null;
if ($params['action'] == 'do' || $params['action'] == 'po') {
$action = " --{$params['action']}";
$msg = "Files need to be placed in {doc_root}/tmp for parsing to work correctly";
}
print json_encode(['success' => true, 'msg' => $msg]);
if (!is_null($cpe) || !is_null($cve) || !is_null($stig)) {
include_once 'vendor/autoload.php';
$script = realpath(PHP_BIN) .
" -c " . realpath(PHP_CONF) .
" -f " . realpath(DOC_ROOT . "/exec/update_db.php") .
" --{$cpe}{$cve}{$stig}{$action}";
$process = new Cocur\BackgroundProcess\BackgroundProcess($script);
$process->run();
}
}
/**
* Function to save company information
*
* @param array $fields
*/
function save_Company($fields)
{
$config = file_get_contents("config.inc", FILE_USE_INCLUDE_PATH);
$scg_date = new DateTime($fields['derived-on']);
$declass_date = new DateTime($fields['declassify-on']);
if (!is_a($scg_date, "DateTime") || !is_a($declass_date, "DateTime")) {
print json_encode(['error' => 'Error parsing the dates']);
return;
}
my_str_replace("{COMPANY}", $fields['company'], $config);
my_str_replace("{COMP_ADD}", $fields['comp-add'], $config);
my_str_replace("{LAST_MODIFIED_BY}", $fields['last-modified'], $config);
my_str_replace("{CREATOR}", $fields['creator'], $config);
my_str_replace("{SYSTEM_CLASS}", $fields['system-class'], $config);
my_str_replace("{CLASSIFIED_BY}", $fields['classified-by'], $config);
my_str_replace("{SCG}", $fields['scg'], $config);
my_str_replace("{DERIVED_ON}", $scg_date->format("Y-m-d"), $config);
my_str_replace("{DECLASSIFY_ON}", $declass_date->format("Y-m-d"), $config);
my_str_replace("@step1", "@step2", $config);
file_put_contents(dirname(dirname(__FILE__)) . "/config.inc", $config);
print json_encode(['success' => true]);
}
/**
* Function to save Sagacity options
*
* @param array $fields
*/
function save_Options($fields)
{
$config = file_get_contents("config.inc", FILE_USE_INCLUDE_PATH);
my_str_replace("'{FLATTEN}'", ($fields['flatten'] ? 'true' : 'false'), $config);
my_str_replace("'{WRAP_TEXT}'", ($fields['wrap-text'] ? 'true' : 'false'), $config);
my_str_replace("'{NOTIFICATIONS}'", ($fields['notifications'] ? 'true' : 'false'), $config);
my_str_replace("'{PORT_LIMIT}'", $fields['port-limit'], $config);
my_str_replace("'{MAX_RESULTS}'", $fields['max-results'], $config);
my_str_replace("{ECHECKLIST_FORMAT}", $fields['output-format'], $config);
my_str_replace("@step2", "", $config);
file_put_contents(dirname(dirname(__FILE__)) . "/config.inc", $config);
print json_encode(['success' => true]);
}