<?php /** * File: background_stigs.php * Author: Ryan Prather * Purpose: To allow scripts to run in the background * Currently only implements the STIG XML importing * Created: Jul 18, 2014 * * Portions Copyright (c) 2016-2017: Cyber Perspectives, LLC All rights reserved * Released under the Apache v2.0 License * * Portions Copyright (c) 2012-2015, Salient Federal Solutions * Portions Copyright (c) 2008-2011, Science Applications International Corporation (SAIC) * Released under Modified BSD License * * See license.txt for details * * Change Log: * - Jul 18, 2014 - File created * - Dec 7, 2016 - Changed PHP constant to PHP_BIN and added Cyber Perspectives copyright * - Dec 12, 2016 - Revised text for to run parse_stig script, delete files only if --delete parameter is set * - Feb 15, 2017 - Formatting, revised the printed messages throughout the script, and converted file_types constants where required * - Feb 21, 2017 - Fixed paths and revised progress output, Revised directories and fixed output * - Mar 3, 2017 - Now shuffling the STIG files to prevent duplicate STIG creation and fixed bug with scripts not being updated to complete when done * - Mar 8, 2017 - Fixed typo with catalog_scripts table and added update to $count value when waiting for all script to complete * - Apr 5, 2017 - Hard coded parsing 20 STIGs instead of using MAX_RESULTS constant * - Jun 27, 2017 - Cleanup * - Jul 13, 2017 - Changed STIG parsing to serial instead of parallel to fix issue with duplicate STIGs from race conditions * - May 31, 2018 - Added deletion when files match exclusion * - Jun 2, 2018 - Added code to check STIG_EXCLUSIONS constant to for permanently excluded STIGs */ $cmd = getopt("x::h::d::", ["debug::", "delete::", "ia::", "extract::", "help::", 'exclude::']); if (isset($cmd['help']) || isset($cmd['h'])) { die(usage()); } set_time_limit(0); require_once 'config.inc'; require_once 'helper.inc'; require_once 'database.inc'; require_once 'vendor/autoload.php'; use Monolog\Logger; use Monolog\Handler\StreamHandler; use Monolog\Formatter\LineFormatter; $log_level = Logger::ERROR; switch (LOG_LEVEL) { case E_WARNING: $log_level = Logger::WARNING; break; case E_NOTICE: $log_level = Logger::NOTICE; break; case E_DEBUG: $log_level = Logger::DEBUG; } if (isset($cmd['debug'])) { $log_level = Logger::DEBUG; } $stream = new StreamHandler("php://output", $log_level); $stream->setFormatter(new LineFormatter("%datetime% %level_name% %message%" . PHP_EOL, "H:i:s.u")); $log = new Logger("stig_parser"); $log->pushHandler(new StreamHandler(LOG_PATH . "/stig_parser.log", $log_level)); $log->pushHandler($stream); check_path(TMP . "/stigs"); check_path(TMP . "/stigs/zip"); check_path(TMP . "/stigs/checklist"); check_path(TMP . "/stigs/xml"); check_path(DOC_ROOT . "/reference/stigs"); $path = realpath(TMP . "/stigs"); if (isset($cmd['d']) && $cmd['d']) { $path = $cmd['d']; } chdir($path); $db = new db(); $zip_files = glob("*.zip"); $zip = new ZipArchive(); // Find the .zip files that were uploaded foreach ($zip_files as $file) { $ft = FileDetection($file); if ($ft['type'] == DISA_STIG_LIBRARY_ZIP) { $log->info("Extracting $file"); $zip->open($file); $zip->extractTo(realpath(TMP . "/stigs/checklist")); $zip->close(); if (isset($cmd['delete'])) { unlink($file); } } } // traverse the checklist directory to find all the zip files and extract those for ($x = 0; $x < 2; $x++) { $dir = new RecursiveDirectoryIterator(realpath(TMP . "/stigs/checklist")); $files = new RecursiveIteratorIterator($dir); directory_crawl($files); } // traverse the zip directory, and extract the xml, xsl, jpg, or gif files. for ($x = 0; $x < 3; $x++) { $dir = new RecursiveDirectoryIterator(realpath(TMP . "/stigs/zip")); $files = new RecursiveIteratorIterator($dir); directory_crawl($files); } if (isset($cmd['x']) || isset($cmd['extract'])) { $log->info("Extract only complete"); die; } // find all the xml files in the directory chdir(TMP . "/stigs/xml"); $xml_files = glob("*.xml"); // change back to the document root directory chdir(DOC_ROOT); $count = 0; $db->help->update("settings", ['meta_value' => 0], [ [ 'field' => 'meta_key', 'value' => 'stig-progress' ] ]); $db->help->execute(); $regex = null; if (isset($cmd['exclude'])) { $regex = $cmd['exclude']; } foreach ($xml_files as $key => $file) { // if the file has a space in the file name we need to replace it because it will cause parsing errors if (strpos($file, ' ') !== false) { $new_file = str_replace(' ', '_', $file); rename(realpath(TMP . "/stigs/xml/$file"), TMP . "/stigs/xml/$new_file"); $xml_files[$key] = $file = $new_file; copy(realpath(TMP . "/stigs/xml/$file"), realpath(DOC_ROOT . "/reference/stigs") . "/$file"); } if (!is_null($regex) && preg_match("/$regex/i", $file)) { unlink($file); $log->debug("Skipping $file due to matching regex"); continue; } elseif(!empty(STIG_EXCLUSIONS) && preg_match("/" . STIG_EXCLUSIONS . "/i", $file)) { unlink(TMP . "/stigs/xml/$file"); $log->debug("Skipping $file due to matching STIG exclusion"); continue; } // determine the file type $ft = FileDetection(TMP . "/stigs/xml/$file"); // add the file to the stack if it is of the proper type // can add additional types as the parser are created if ($ft['type'] == DISA_STIG_XML) { $log->info("Parsing STIG file: $file"); $script = realpath(defined('PHP_BIN') ? PHP_BIN : PHP) . " -c " . realpath(PHP_CONF) . " -f " . realpath(DOC_ROOT . "/exec/parse_stig.php") . " --" . " -f=\"" . realpath(TMP . "/stigs/xml/{$file}") . "\"" . (isset($cmd['debug']) ? " --debug" : ""); $db->add_Catalog_Script(basename($file)); passthru($script); } else { $log->debug("Skipping $file"); continue; } $count++; $db->help->update("settings", ['meta_value' => number_format(($count / count($xml_files) * 100), 2)], [ [ 'field' => 'meta_key', 'op' => '=', 'value' => 'stig-progress' ] ]); $db->help->execute(); } $db->help->update("catalog_scripts", ['status' => 'COMPLETE'], [ [ 'field' => 'perc_comp', 'op' => '=', 'value' => 100 ], [ 'field' => 'status', 'op' => '=', 'value' => 'RUNNING', 'sql_op' => 'AND' ] ]); $db->help->execute(); $db->help->update("settings", ['meta_value' => 100], [ [ 'field' => 'meta_key', 'op' => IN, 'value' => ['stig-dl-progress', 'stig-progress'] ] ]); $db->help->execute(); if (isset($cmd['delete'])) { if (strtolower(substr(PHP_OS, 0, 3)) == 'win') { exec("del /S /Q /F " . realpath(TMP . "/stigs/checklist") . "\\*"); exec("del /S /Q /F " . realpath(TMP . "/stigs/zip") . "\\*"); } else { exec("rm -rf " . realpath(TMP . "/stigs/checklist") . "/*"); exec("rm -rf " . realpath(TMP . "/stigs/zip") . "/*"); } } /** * Function to crawl directory structure to find zip, xml, xsl, gif, and jpg files * * @param RecursiveIteratorIterator $files */ function directory_crawl($files) { global $zip, $log; foreach ($files as $file) { if (preg_match('/\.zip/', $file)) { if ($zip->open($file) === true) { for ($i = 0; $i < $zip->numFiles; $i++) { $contents = ''; $in_Skips = false; $path = ''; $filename = str_replace('\\', '/', $zip->getNameIndex($i)); $fileinfo = pathinfo($filename); if (isset($fileinfo['extension']) && !$in_Skips) { switch (strtolower($fileinfo['extension'])) { case 'zip': $path = TMP . "/stigs/zip/"; break; case 'xml': if (!preg_match('/xccdf/i', $fileinfo['basename'])) { continue; } elseif (strpos($fileinfo['basename'], "$") !== false) { continue; } $path = TMP . "/stigs/xml/"; break; case 'xsl': case 'gif': case 'jpg': $path = DOC_ROOT . "/reference/stigs/"; break; } if ($path) { $fp = $zip->getStream($filename); if (!$fp) { error_log("Couldn't get zip file stream for file $filename in $file"); } else { while (!feof($fp)) { $contents .= fread($fp, 1024); } fclose($fp); if (file_put_contents($path . $fileinfo['basename'], $contents) === false) { die; } } } } } $zip->close(); } } } } function usage() { print <<<EOO Purpose: This program was written to look at all files in the {doc_root}/tmp directory, determine what parser is needed, then call that parser with the appropriate flags. Usage: background_stigs.php [-x|--extract] [-d="directory"] [--debug] [--regex="ex1|ex2"] [--delete] [--ia] [-h|--help] -x|--extract Simply extract the contents of a .zip file (STIG library) to it's proper places, do not parse the contents -d="directory" Directory to search for the zip and xml files in (optional, defaults to {doc_root}/tmp) --regex="ex1|ex2" Insert a valid regex expression (properly escaped) to exclude specific STIGs from parsing --ia Override any IA controls in the DB to use only the ones that are in the STIG file --delete Delete any files once complete --debug Debugging output --help This screen EOO; }