<?php

/**
 * File: export-ckl.php
 * Author: Ryan Prather <ryan.prather@cyberperspectives.com>
 * Purpose:
 * Created: Feb 20, 2017
 *
 * Copyright 2017: Cyber Perspective, LLC, All rights reserved
 * Released under the Apache v2.0 License
 *
 * See license.txt for details
 *
 * Change Log:
 *  - Feb 20, 2017 - File created
 *  - Mar 8, 2017 - Completed preliminary functionality
 *  - May 13, 2017 - Set default export path to TMP/ckl
 *                   Only exporting manual checklists and not export orphan findings
 *  - Oct 23, 2017 - Added a few more fields and added data to some fields that didn't have a value
 *  - Nov 25, 2017 - Fixed notice bug #346
 *  - Jan 6, 2018 - Bug fix #337 and formatting
 */
include_once 'config.inc';
include_once 'database.inc';
include_once 'helper.inc';
include_once 'array2xml.inc';

$db = new db();
$dt = new DateTime();

$cmd = getopt('s:t::c::h::d::', ['help::', 'debug::']);

if (!isset($cmd['s']) || !is_numeric($cmd['s'])) {
  die(usage());
}

if (isset($cmd['t'])) {
  $tgts = $db->get_Target_Details($cmd['s'], $cmd['t']);
}
elseif (isset($cmd['c'])) {
  $tgts = $db->get_Target_By_Category($cmd['c']);
}
else {
  $tgts = $db->get_Target_Details($cmd['s']);
}

if (isset($cmd['d'])) {
  if (file_exists($cmd['d'])) {
    $dest = realpath($cmd['d']) . "/";
  }
  else {
    die("Could not find destination path {$cmd['d']}");
  }
}
else {
  check_path(TMP . "/ckl", true);
  $dest = realpath(TMP . "/ckl") . "/";
}

print "Destination: $dest" . PHP_EOL;

$status_map = [
    'Not Reviewed' => 'Not_Reviewed',
    'Not a Finding' => 'NotAFinding',
    'Open' => 'Open',
    'Not Applicable' => 'Not_Applicable',
    'No Data' => 'Not_Reviewed',
    'Exception' => 'Open',
    'False Positive' => 'NotAFinding'
];

$xml = new Array2XML();
$xml->standalone = true;
$xml->formatOutput = true;

$chk_comp_count = 0;
$tgt_comp_count = 0;
$total_chk_count = 0;
$total_stigs = 0;

if ($tgt_count = count($tgts)) {
  print "Total Targets: $tgt_count" . PHP_EOL;

  foreach ($tgts as $tgt) {
    $host_ip = (is_array($tgt->interfaces) && count($tgt->interfaces) ? current($tgt->interfaces)->get_IPv4() : null);
    $host_fqdn = (is_array($tgt->interfaces) && count($tgt->interfaces) ? current($tgt->interfaces)->get_FQDN() : null);
    $host_mac = (is_array($tgt->interfaces) && count($tgt->interfaces) ? current($tgt->interfaces)->get_MAC() : null);
    //$host_mac = (count($tgt->interfaces) ? current($tgt->interfaces)->get_Mac() : null);

    print "Target: {$tgt->get_Name()}" . PHP_EOL;

    foreach ($tgt->checklists as $key => $chk) {
      if ($chk->name == 'Orphan' || $chk->type != 'manual') {
        unset($tgt->checklists[$key]);
      }
    }

    $total_chk_count += $chk_count = (is_array($tgt->checklists) ? count($tgt->checklists) : 0);

    print "Total Checklists: $chk_count" . PHP_EOL;

    foreach ($tgt->checklists as $chk) {
      print "Type: {$chk->type}\tChecklist: {$chk->name}" . PHP_EOL;
      $class = '';
      $stig_class = '';

      switch ($chk->get_Classification()) {
        case 'U':
          $class = 'UNCLASSIFIED';
          $stig_class = "Unclass";
          break;
        case 'FOUO':
          $class = 'UNCLASSIFIED//FOUO';
          $stig_class = "FOUO";
          break;
        case 'S':
          $class = 'SECRET';
          $stig_class = "Secret";
          break;
      }

      $arr = [
        '@comment' => "CyberPerspectives Sagacity v" . VER,
        'ASSET' => [
          'ROLE'			=> 'None',
		  'ASSET_TYPE'      => 'Computing',
          'HOST_NAME'       => $tgt->get_Name(),
          'HOST_IP'         => $host_ip,
          'HOST_MAC'        => $host_mac,
          'HOST_FQDN'       => $host_fqdn,
          'TECH_AREA'       => '',
          'TARGET_KEY'      => '',
          'WEB_OR_DATABASE' => false,
          'WEB_DB_SITE'     => '',
          'WEB_DB_INSTANCE' => ''
        ],
        'STIGS' => [
          'iSTIG' => [
            'STIG_INFO' => [
              'SI_DATA' => [
                [
                  'SID_NAME' => 'version',
                  'SID_DATA' => $chk->get_Version()
                ],
                [
                  'SID_NAME' => 'classification',
                  'SID_DATA' => $class
                ],
                [
                  'SID_NAME' => 'customname'
                ],
                [
                  'SID_NAME' => 'stigid',
                  'SID_DATA' => $chk->get_Checklist_ID()
                ],
                [
                  'SID_NAME' => 'description',
                  'SID_DATA' => $chk->get_Description()
                ],
                [
                  'SID_NAME' => 'filename',
                  'SID_DATA' => $chk->get_File_Name()
                ],
                [
                  'SID_NAME' => 'releaseinfo',
                  'SID_DATA' => "Release: {$chk->get_Release()} Benchmark Date: {$chk->get_Date()->format("j M Y")}"
                ],
                [
                  'SID_NAME' => 'title',
                  'SID_DATA' => $chk->get_Name()
                ],
                [
                  'SID_NAME' => 'uuid',
                  'SID_DATA' => UUID::v4()
                ],
                [
                  'SID_NAME' => 'notice',
                  'SID_DATA' => 'terms-of-use'
                ],
                [
                  'SID_NAME' => 'source',
                  'SID_DATA' => 'STIG.DOD.MIL'
                ]
              ]
            ]
          ]
        ]
      ];

      $pdis = get_checklist_data($tgt, $chk);
      $stig_data = [];

      $total_stigs += $pdi_count = (is_array($pdis) ? count($pdis) : 0);
      $count = 0;

      $findings = $db->get_Finding($tgt);

      foreach ($pdis as $pdi) {
        if (isset($findings[$pdi['pdi_id']])) {
          $find = $findings[$pdi['pdi_id']];
        }

        $sev = 'low';
        if ($pdi['CAT'] == 'I') {
          $sev = 'high';
        }
        elseif ($pdi['CAT'] == 'II') {
          $sev = 'medium';
        }

        $ccis = preg_grep("/CCI\-/", explode(" ", $pdi['IA_Controls']));
        $cci_list = [];

        if (is_array($ccis) && count($ccis)) {
          foreach ($ccis as $cci) {
            $cci_list[] = [
              'VULN_ATTRIBUTE' => 'CCI_REF',
              'ATTRIBUTE_DATA' => $cci
            ];
          }
        }

        // decoding because check contents are already encoded
        $cc = str_replace("\\n", "<br />", htmlentities(html_entity_decode($pdi['check_contents'])));

        $stig_data = array_merge([
          [
            'VULN_ATTRIBUTE' => 'Vuln_Num',
            'ATTRIBUTE_DATA' => $pdi['VMS_ID']
          ],
          [
            'VULN_ATTRIBUTE' => 'Severity',
            'ATTRIBUTE_DATA' => $sev
          ],
          [
            'VULN_ATTRIBUTE' => 'Group_Title',
            'ATTRIBUTE_DATA' => $pdi['group_title']
          ],
          [
            'VULN_ATTRIBUTE' => 'Rule_ID',
            'ATTRIBUTE_DATA' => $pdi['SCAP_Rule']
          ],
          [
            'VULN_ATTRIBUTE' => 'Rule_Ver',
            'ATTRIBUTE_DATA' => $pdi['STIG_ID']
          ],
          [
            'VULN_ATTRIBUTE' => 'Rule_Title',
            'ATTRIBUTE_DATA' => $pdi['short_title']
          ],
          [
            'VULN_ATTRIBUTE' => 'Vuln_Discuss',
            'ATTRIBUTE_DATA' => $pdi['Description']
          ],
          [
            'VULN_ATTRIBUTE' => 'IA_Controls',
            'ATTRIBUTE_DATA' => $pdi['IA_Controls']
          ],
          [
            'VULN_ATTRIBUTE' => 'Check_Content',
            'ATTRIBUTE_DATA' => $cc
          ],
          [
            'VULN_ATTRIBUTE' => 'Fix_Text',
            'ATTRIBUTE_DATA' => htmlentities($pdi['fix_text'])
          ],
          [
            'VULN_ATTRIBUTE' => 'False_Positives',
            'ATTRIBUTE_DATA' => ''
          ],
          [
            'VULN_ATTRIBUTE' => 'False_Negatives',
            'ATTRIBUTE_DATA' => ''
          ],
          [
            'VULN_ATTRIBUTE' => 'Documentable',
            'ATTRIBUTE_DATA' => 'false'
          ],
          [
            'VULN_ATTRIBUTE' => 'Mitigations',
            'ATTRIBUTE_DATA' => ''
          ],
          [
            'VULN_ATTRIBUTE' => 'Potential_Impact',
            'ATTRIBUTE_DATA' => ''
          ],
          [
            'VULN_ATTRIBUTE' => 'Third_Party_Tools',
            'ATTRIBUTE_DATA' => ''
          ],
          [
            'VULN_ATTRIBUTE' => 'Mitigation_Control',
            'ATTRIBUTE_DATA' => ''
          ],
          [
            'VULN_ATTRIBUTE' => 'Responsibility',
            'ATTRIBUTE_DATA' => ''
          ],
          [
            'VULN_ATTRIBUTE' => 'Security_Override_Guidance',
            'ATTRIBUTE_DATA' => ''
          ],
          [
            'VULN_ATTRIBUTE' => 'Check_Content_Ref',
            'ATTRIBUTE_DATA' => ''
          ],
          [
            'VULN_ATTRIBUTE' => 'Class',
            'ATTRIBUTE_DATA' => $stig_class
          ],
          [
            'VULN_ATTRIBUTE' => 'STIGRef',
            'ATTRIBUTE_DATA' => "{$chk->get_Name()} :: Release: {$chk->get_Release()} Benchmark Date: {$chk->get_Date()->format("j M Y")}"
          ],
          [
            'VULN_ATTRIBUTE' => 'TargetKey',
            'ATTRIBUTE_DATA' => ''
          ]
            ], $cci_list);

        $status = 'Not_Reviewed';
        $notes = '';

        if (is_a($find, 'finding')) {
          $status = $status_map[$find->get_Finding_Status_String()];
          $notes = $find->get_Notes();
        }

        $arr['STIGS']['iSTIG']['VULN'][] = [
          'STIG_DATA'              => $stig_data,
          'STATUS'                 => $status,
          'FINDING_DETAILS'        => $notes,
          'COMMENTS'               => '',
          'SEVERITY_OVERRIDE'      => '',
          'SEVERITY_JUSTIFICATION' => ''
        ];

        $count++;

        printf("\r%.2f%%", ($count / $pdi_count) * 100);
      }

      print PHP_EOL;

      $file = $xml->createXML('CHECKLIST', $arr);

      $file->save("{$dest}{$tgt->get_Name()}_{$chk->get_Checklist_ID()}_{$chk->get_type()}_{$dt->format("Ymd")}.ckl");
    }
  }
}

print <<<EOO

Total Targets: $tgt_count
Total Checklists: $total_chk_count
Total STIGs: $total_stigs

EOO;

/**
 *
 * @global db $db
 *
 * @param target $tgt
 * @param checklist $chk
 *
 * @return mixed
 */
function get_checklist_data($tgt, $chk) {
  if (!is_a($tgt, 'target') || !is_a($chk, 'checklist')) {
    return;
  }

  global $db;

  $db->help->select("sagacity.pdi", ["pdi.*", "pcl.*", "s.description AS 'Description'"], [
    [
      'field' => 'tc.tgt_id',
      'op'    => '=',
      'value' => $tgt->get_ID()
    ],
    [
      'field'  => 'tc.chk_id',
      'op'     => '=',
      'value'  => $chk->id,
      'sql_op' => 'AND'
    ]
      ], [
    'table_joins' => [
      "JOIN sagacity.pdi_checklist_lookup pcl ON pcl.pdi_id = pdi.pdi_id",
      "JOIN sagacity.target_checklist tc ON tc.chk_id = pcl.checklist_id",
      "JOIN sagacity.stigs s ON s.pdi_id = pdi.pdi_id"
    ]
  ]);
  $pdis = $db->help->execute();

  return $pdis;
}

/**
 * Function retrieve
 *
 * @global db $db
 *
 * @param target $tgt
 * @param checklist $chk
 *
 * @return mixed
 */
function get_finding_data($tgt, $chk) {
  global $db;
  $ret = [];

  return $ret;
}

/**
 * Usage output
 */
function usage() {
  print <<<EOO
Purpose: This script was written to be able to export CKL files from the data contained in the database.

Usage: php export-ckl.php [-d={destination}] -s={ste id} [-c={category id}] [-t={target id}] [-h|--help]

 -s={STE ID}        Export a CKL for each assigned checklist for ALL targets in this ST&E
 -c={Category ID}   Export CKL files for all targets contained in this Category
 -t={Target ID}     Export CKL file for each assigned checklist for this target

 -d={destination}   Location of where you want the files saved

 -h|--help          This screen
 --debug            Debugging output

EOO;
}
