bitslip6/bitfire

View on GitHub
custom-plugin/includes.php

Summary

Maintainability
A
3 hrs
Test Coverage
<?php

namespace BitFirePlugin;

use BitFire\Config AS CFG;
use ThreadFin\FileData;

use const BitFire\WAF_ROOT;

use function BitFireSvr\trim_off;
use function ThreadFin\get_sub_dirs;
use function ThreadFin\contains;
use function ThreadFin\dbg;
use function ThreadFin\ends_with;

define("BitFire\\CMS_INCLUDED", true);

// list of plugin package files that can be used to determine package versions
// can be an empty list if no such files exist
const ENUMERATION_FILES = ["readme.txt", "license.txt", "package.json", "composer.json"];

// list of directories from document root that can be used to determine package versions
// can be an empty list if no such files exist
const PLUGIN_DIRS = ["/plugins/", "/themes/"];

// list of request parameters used for different api actions or page views
// can be an empty list if no such parameters exist
const ACTION_PARAMS = ["do", "page", "action", "screen-id"];

// files used to determine package versions for plugin malware scan
// can be an empty list if no such files exist
const PACKAGE_FILES = ["readme.txt", "README.txt", "package.json"];

/**
 * get the wordpress version from a word press root directory
 */
function get_cms_version(string $root_dir): string {
    $full_path = "$root_dir/wp-includes/version.php";
    $wp_version = "1.0";
    if (file_exists($full_path)) {
        @include $full_path;
    }
    return trim_off($wp_version, "-");
}


/**
 * file_type is an enumeration of malware scan file types.  
 * These files are stored in separate tables with the 
 * file_type to table mapping function.
 * 
 * @param string $path the full path to the file
 * @return string an enumeration of the file type
 */
function file_type(string $path) : string {
    if (contains($path, "/plugins/")) { return "my_plugin"; }
    else { return "my_core"; }
}


/**
 * mapping function from file types to table names
 * these tables store hashes from different sources
 * 
 * @param string $type the file_type as defined by file_type()
 * @return string the name of the table to store hashes in
 */
function type_to_table(string $type) : string {
    switch ($type) {
        case "my_plugin": return "plugin_hashes";
        case "my_core": return "core_hashes";
        default: return "core_hashes";
    }
}

/**
 * convert a file path to a source path.  If the plugin has HTTP
 * access to original source code, this function should return
 * an HTTP path to the source code.
 * 
 * If no such path exists, this function should return empty string.
 * 
 * @param string $rel_path the relative path to the file, from the plugin root
 *                         path, or the document root if no such path exists 
 * @param string $type     the file type as determined by file_type()
 * @param string $ver      the version of the plugin/theme/core as determined by
 *                         package_to_ver()
 * @param string $name     the plugin/theme/module name for the file 
 *                         (if available)
 * @return string - the url to the source code
 */
function path_to_source(string $rel_path, string $type, string $ver, ?string $name=null) : string {

    $source = "";
    switch($type) {
        case "my_plugin":
            $source = "plugin.svn.my-corp.com/{$name}/tags/{$ver}/{$rel_path}";
            break;
        case "my_core":
            $source = "core.svn.my-corp.com/tags/{$ver}/{$rel_path}";
            break;
    }

    $source = "https://" . str_replace("//", "/", $source);
    return $source;
}


/**
 * when scanning for malware, the system will attempt to identify package
 * version files for identifying original source code and file hashes.
 * 
 * This function should accept any file from PACKAGE_FILES and return
 * the version number of the package. You may create your own package
 * version files in your deployment process and parse them here as well.
 * 
 * This function is called once for every line in the package file.
 * 
 * @param string $carry  the current version number found for the file
 * @param string $line   the next full line of the file
 * @return string 
 */
function package_to_ver(string $carry, string $line) : string {
    // If we have already identified a version number, use that number
    // (assumes first version number is correct).  Remove this
    // line to take the last version number, or change to accept any
    // version number based on any criteria you may have.  State can
    // be stored in local scoped "static" variables (NOT RECOMMENDED!).
    if (!empty($carry)) { return $carry; }

    // matches VeRsIoN: "1.2.3.4"
    if (preg_match("/version[\'\":\s]+([\d\.]+)/i", $line, $matches)) { return $matches[1]; }
    return $carry;
}


/**
 * since many code bases have thousands of files, each top level module
 * directory is scanned individually. This function should return a list
 * of directory paths to scan 1 at a time for malware.
 * 
 * @param string $root  - typically the DOCUMENT_ROOT (includes trailing slash)
 * @return array the list of directories to scan
 */
function malware_scan_dirs(string $root) : array {
    $check_list = ["modules", "components", "wp-content/plugins", "wp-content/themes"];

    $all_dirs = get_sub_dirs($root);
    $base_dirs = array_diff($all_dirs, $check_list);

    $keep_dirs = array_map(function($x) use ($root) {
        if (file_exists("{$root}/{$x}")) {
            return get_sub_dirs("{$root}/{$x}");
        }
        return [];
    }, $check_list);
    $flat_dirs = array_merge(...array_values($keep_dirs));

    return array_merge($base_dirs, $flat_dirs);
}

/**
 * wrapper function for cms mail implementation
 * @param string $subject 
 * @param string $message 
 * @return void 
 */
function mail(string $subject, string $message) {
    $domain_list = CFG::arr("valid_domains");
    $domain = end($domain_list);
    $headers = "From: bitfire@$domain\r\nReply-To: no-reply@$domain\r\nX-Mailer: PHP/".phpversion();
    // ma il(CFG::str("email"), $subject, $message, $headers);
} 

const PARAM_SEARCH = 1;
const LOG_ACTION = 2;
const DST_USER = 4;
const RISKY_DB_PARAM = ["wp_capabilities"];

/**
 * stub for function auditing
 * @param string $fn_name 
 * @param string $file 
 * @param string $line 
 * @param mixed $args 
 * @return void 
 */
function fn_audit(string $fn_name, string $file, string $line, ...$args) {
    static $fn_map = [
        "update_user_meta" => [1 => DST_USER, 2 => RISKY_DB_PARAM, 3=> PARAM_SEARCH],
        "wp_create_user" => [1 => LOG_ACTION],
        "wp_insert_user" => [1 => LOG_ACTION]
    ];
    $src = $file . ":" . $line;
    $x = FileData::new(WAF_ROOT."cache/{$fn_name}.json")->read()->un_json()->lines;
    if (!isset($x[$src])) { 
        $x[$src] = [];
    }
}


// find a plugin / theme version number located in $path
function version_from_path(string $path, string $default_ver = "") {
    return "1.0";
}