<?php /*

 Composr
 Copyright (c) Christopher Graham, 2004-2024

 See docs/LICENSE.md for full licensing information.


 NOTE TO PROGRAMMERS:
   Do not edit this file. If you need to make changes, save your changed file to the appropriate *_custom folder
   **** If you ignore this advice, then your website upgrades (e.g. for bug fixes) will likely kill your changes ****

*/

/**
 * @license    http://opensource.org/licenses/cpal_1.0 Common Public Attribution License
 * @copyright  Christopher Graham
 * @package    core_cns
 */

/**
 * Approve an IP address, indirectly by passing through a confirmation code.
 */
function approve_ip_script()
{
    require_code('site');
    attach_to_screen_header('<meta name="robots" content="noindex" />'); // XHTMLXHTML

    $keep = keep_symbol(['1']);

    $code = either_param_string('code', '');
    if ($code == '') {
        $title = get_screen_title('CONFIRM');
        require_code('form_templates');
        $fields = new Tempcode();
        $fields->attach(form_input_codename(do_lang_tempcode('CODE'), '', 'code', '', true));
        $submit_name = do_lang_tempcode('PROCEED');
        $url = find_script('approve_ip') . $keep;
        $middle = do_template('FORM_SCREEN', [
            '_GUID' => 'd92ce4ec82dc709f920a4ce6760778de',
            'TITLE' => $title,
            'SKIP_WEBSTANDARDS' => true,
            'HIDDEN' => '',
            'URL' => $url,
            'FIELDS' => $fields,
            'TEXT' => do_lang_tempcode('MISSING_CONFIRM_CODE'),
            'SUBMIT_ICON' => 'buttons/proceed',
            'SUBMIT_NAME' => $submit_name,
        ]);
        $echo = globalise($middle, null, '', true, true);
        $echo->evaluate_echo();
        exit();
    }

    // If we're still here, we're ok to go
    require_lang('cns');
    $test = $GLOBALS['FORUM_DB']->query_select_value_if_there('f_member_known_login_ips', 'i_val_code', ['i_val_code' => $code]);
    if ($test === null) {
        warn_exit(do_lang_tempcode('ALREADY_APPROVED_IP'));
    }
    $GLOBALS['FORUM_DB']->query_update('f_member_known_login_ips', ['i_val_code' => '', 'i_time' => time()], ['i_val_code' => $code], '', 1);
    if ((get_option('maintenance_script_htaccess') == '1') && (maintenance_script_htaccess_option_available())) {
        adjust_htaccess();
    }

    require_code('site2');
    redirect_exit(get_base_url() . $keep, get_screen_title('CONFIRM'), do_lang_tempcode('SUCCESS'));
}

/**
 * See if the maintenance_script_htaccess option is available (if the environment is compatible).
 *
 * @return boolean Whether it is
 */
function maintenance_script_htaccess_option_available() : bool
{
    if (get_forum_type() != 'cns') {
        return false;
    }

    require_code('global4');
    if (!is_possibly_apache()) {
        return false;
    }

    $path = get_file_base() . '/.htaccess';

    if (!is_file($path) || !cms_is_writable($path)) {
        return false;
    }

    // two-factor is not compatible with the 'require valid-user' directive for HTTP-Auth. Note this will still hide the option if require valid-user is commented out.
    $contents = cms_file_get_contents_safe($path, FILE_READ_LOCK);
    if (($contents !== false) && (strpos($contents, 'require valid-user') !== false)) {
        return false;
    }

    // Disable option if we have too many IP addresses; we do not want the htaccess file to get too long.
    $ips = $GLOBALS['FORUM_DB']->query_select_value('f_member_known_login_ips', 'COUNT(DISTINCT i_ip_address)', ['i_val_code' => '']);
    $ips += $GLOBALS['FORUM_DB']->query_select_value('f_members', 'COUNT(DISTINCT m_ip_address)', ['m_validated' => 1]);
    if ($ips >= 500) {
        return false;
    }

    return true;
}

/**
 * Adjust the .htaccess file with an approved IP address.
 */
function adjust_htaccess()
{
    $path = get_file_base() . '/.htaccess';

    if (!is_file($path)) {
        return;
    }

    require_code('failure');

    $option_enabled = ((maintenance_script_htaccess_option_available()) && (get_option('maintenance_script_htaccess') === '1'));

    $contents = cms_file_get_contents_safe($path, FILE_READ_LOCK);

    $lines = [
        '<FilesMatch ^((rootkit_detection|upgrader|uninstall|data/upgrader2|config_editor|code_editor)\.php)$>',
    ];
    $start_pos = strpos($contents, $lines[0]);

    if ($option_enabled) {
        $lines[] = 'Require all denied';

        $ips = $GLOBALS['FORUM_DB']->query_select('f_member_known_login_ips', ['DISTINCT i_ip_address'], ['i_val_code' => '']);
        $ips = array_merge($ips, $GLOBALS['FORUM_DB']->query_select('f_members', ['DISTINCT m_ip_address AS i_ip_address'], ['m_validated' => 1]));
        $ips = array_unique(collapse_1d_complexity('i_ip_address', $ips));
        foreach ($ips as $ip) {
            $ip = ip_wild_to_apache($ip);
            if ($ip != '') {
                $lines[] = 'Require ip ' . $ip;
            }
        }
    }

    $lines = array_merge($lines, [
        '</FilesMatch>',
    ]);
    $final_line = $lines[count($lines) - 1];

    if (!$option_enabled) {
        $lines = []; // Clear $lines if option is disabled so we erase the entire FilesMatch block instead of leaving it in empty (which breaks Apache)
    }

    if ($start_pos === false) {
        if (!empty($lines)) {
            $contents .= "\n" . implode("\n", $lines) . "\n";
        }
    } else {
        $end_pos = strpos($contents, $final_line, $start_pos);
        if ($end_pos === false) {
            fatal_exit(do_lang_tempcode('INTERNAL_ERROR', escape_html('87fde62550f25942ac44d38925cb9fc1'))); // Should never happen, things would crash if so! But we can't proceed
        }

        $contents = trim(substr($contents, 0, $start_pos) . implode("\n", $lines) . substr($contents, $end_pos + strlen($final_line))) . "\n";
    }

    require_code('files');
    cms_file_put_contents_safe($path, $contents, FILE_WRITE_SYNC_FILE | FILE_WRITE_FIX_PERMISSIONS);
}
