<?php

# PLUGIN PREVIEW BY TEXTPATTERN.INFO

/**
 * smd_prognostics
 *
 * A Textpattern CMS plugin for pro-active diagnostics
 *  -> Detect changes to the file system
 *  -> Acknowledge alarms (sent via e-mail or to the admin interface)
 *
 * @author Stef Dawson
 * @link   https://stefdawson.com/
 * @todo   Monitor PHP / MySQL version? (https://forum.textpattern.io/viewtopic.php?pid=260163#p260163)
 */

global $smd_prognostics_event$smd_prognostics_checksums$smd_prognostics_sqlprot;

$smd_prognostics_event 'smd_prognostics';
$smd_prognostics_checksums rtrim(get_pref('smd_prognostics_dir'txpath1), DS) . DS get_pref('smd_prognostics_prefix''').'smd_prognostics_checksums.txt';
$hdron get_pref('smd_prognostics_req_headers''');
$smd_prognostics_sqlprot = new smd_prog_PhProtector(false);

if (
txpinterface === 'admin') {
    global 
$txp_user$event$step;

    
$smd_prognostics_privs '1,2';
    
$smd_prognostics_suppress gps('smd_prognostics_suppress');

    
$privs safe_field("privs""txp_users""name = '".doSlash($txp_user)."'");
    
$users get_pref('smd_prognostics_users''');
    
$allow = ($users) ? in_array($txp_userdo_list($users)) : true;

    if (
$allow) {
        
add_privs($smd_prognostics_event$smd_prognostics_privs);
        
add_privs('plugin_prefs.'.$smd_prognostics_event$smd_prognostics_privs);
        
register_tab('extensions'$smd_prognostics_eventgTxt('smd_prognostics'));
        
register_callback('smd_prognostics_setup''plugin_prefs.'.$smd_prognostics_event);
        
register_callback('smd_prognostics_dispatcher'$smd_prognostics_event);
        
register_callback('smd_prognostics_inject_css''admin_side''head_end');
    }

    if (
$hdron) {
        
register_callback('smd_prognostics_request_headers''admin_side''head_end');
    }

    
// Admin side callback for checking files
    
if (!$smd_prognostics_suppress && in_array($privsdo_list($smd_prognostics_privs))) {
        
register_callback('smd_prognostics''admin_side''main_content');
    }
}

// Public side callbacks
if (strpos(get_pref('smd_prognostics_check_where'''), 'public') !== false) {
    
register_callback('smd_prognostics''pretext');
}
if (
$hdron) {
    
register_callback('smd_prognostics_request_headers''pretext');
}
if (
strpos(get_pref('smd_prognostics_sql_inject''smd_no'), 'smd_no') === false) {
    
register_callback('smd_prognostics_sql_inject''pretext');
}

// -------------------------------------------------------------
// CSS definitions: hopefully kind to themers.
function smd_prognostics_get_style_rules()
{
    
$smd_prognostics_styles = array(
        
'msg' =>
         
'.smd_prog_warn { margin:0 auto 10px; width:500px; background:#ffc; border:2px dashed red; padding:10px; color:#444; }
          .smd_prog_warn a { font-weight:bold; color:#963; }
          .smd_prog_warn span { font-weight:bold; }
          .smd_prog_warn div { font-weight:bold; padding-bottom:10px; }'
,
        
'setup' =>
         
'.smd_label { text-align:right!important; vertical-align:middle; }
          .smd_prog_btns form { display:inline; }
          .smd_prognostics_setup h3 { margin:25px 0 0; }'
,
        
'advice' =>
         
'.smd_prog_advice { width:750px; }
          #list.smd_prog_advice td { padding:5px; }
          .smd_prog_btns { width:140px; }'
,
    );

    return 
$smd_prognostics_styles;
}

// -------------------------------------------------------------
function smd_prognostics_inject_css($evt$stp)
{
    global 
$smd_prognostics_event$event$step;

    if (
$event == $smd_prognostics_event) {
        
$smd_prognostics_style smd_prognostics_get_style_rules();

        
$styles = array();

        switch (
$step) {
            case 
'smd_prognostics_advice':
            
$styles[] = 'advice';
            
$styles[] = 'setup';
            break;
        }

        if (
$styles) {
            echo 
'<style type="text/css">';
            foreach (
$styles as $style) {
                echo 
$smd_prognostics_style[$style];
            }
            echo 
'</style>';
        }
    }

    return;
}

// ------------------------
function smd_prognostics_dispatcher($evt$stp)
{
    
$available_steps = array(
        
'smd_prognostics_files'  => false,
        
'smd_prognostics_setup'  => false,
        
'smd_prognostics_advice' => false,
        
'smd_prognostics_ack'    => false,
    );

    if (!
$stp or !bouncer($stp$available_steps)) {
        
$stp 'smd_prognostics_setup';
    }
    
$stp();
}

// Stub to call the verification code from the URL
function smd_prognostics($evt$stp)
{
    return 
smd_do_prognostics();
}

// Verify the file checksums
// $mode = 0: normal / 1: no update (return arrays only) / 2: silent update
function smd_do_prognostics($mode 0)
{
    global 
$smd_prognostics_event$smd_prognostics_checksums;

    
$smd_prognostics_style smd_prognostics_get_style_rules();

    
$now time();
    
$nok $miss $added $allfiles $hashdown $hashmod = array();
    
$timenow date('H:i:s'$now);
    list(
$beg$end) = do_list(get_pref('smd_prognostics_check_between''|'), '|'); // Default is pipe so we can guarantee two return vals
    
$tdiff = ($mode == || ( ($timenow $beg) && ($timenow $end) && ($now get_pref('smd_prognostics_lastcheck'0) > get_pref('smd_prognostics_check_freq'3600)) ) ) ? true false;

    
// Parts shamelessly plagiarised from txp_diag
    
if ($tdiff) {
        
$sumhash get_pref('smd_prognostics_sumhash'NULL, ($mode==0));
        
$lookat get_pref('smd_prognostics_check_for''');
        
$adds = (strpos($lookat'add') !== false);
        
$dels = (strpos($lookat'delete') !== false);
        
$mods = (strpos($lookat'modify') !== false);

        if (
$cs = @file($smd_prognostics_checksums)) {
            
$hash md5(smd_prognostics_prep_file($smd_prognostics_checksums));
            if (
$hash != $sumhash) {
                
$hashmod[] = $smd_prognostics_checksums;
            } else {
                
$ctr 0;
                
$qty_per = (($qty get_pref('smd_prognostics_check_qty''')) == '') ? count($cs) : $qty;
                
$so_far get_pref('smd_prognostics_qty_so_far'0);
                
$until $so_far $qty_per;
                foreach (
$cs as $c) {
                    if (
preg_match('@^(\S+): \((.*)\)$@'trim($c), $m)) {
                        list(,
$file,$md5) = $m;
                        if (
$dels && !file_exists($file)) {
                            
$miss[] = $file;
                        } else if (
$mods && $md5 != 'NULL') {
                            
// Check file_exists last as it's the slowest operation; allows PHP to short circuit conditionals faster
                            
if ( ( (($ctr >= $so_far) && ($ctr $until) || $mode == 1) ) && file_exists($file) ) {
                                
$content smd_prognostics_prep_file($file);
                                if ((
md5($content) != $md5) && ($file != $smd_prognostics_checksums)) {
                                    
$nok[] = $file;
                                }
                            }
                            
$ctr++;
                        }
                        
$allfiles[] = $file;
                    }
                }

                
// Stash the story so far ready for next time the function is called
                
if ($mode != 1) {
                    
set_pref('smd_prognostics_qty_so_far', (($until $ctr) ? $until 0), 'smd_prognos'PREF_HIDDEN'text_input');
                }
                if (
$adds) {
                    
$filelist smd_prognostics_readfiles();
                    
$added array_diff($filelist$allfiles);
                }

                if (
$mode != 1) {
                    
set_pref('smd_prognostics_lastcheck'$now'smd_prognos'PREF_HIDDEN'text_input');
                }
            }
        } else {
            
// File doesn't exist
            
$hashdown[] = $smd_prognostics_checksums;
        }
    }

    
// Assemble message
    
if ($nok || $miss || $added || $hashdown || $hashmod) {
        if (
$mode==1) {
            return array(
$nok$miss$added$hashdown$hashmod);
        } else {
            
$via get_pref('smd_prognostics_notify_via''');
            
$detect get_pref('smd_prognostics_lastdetect''');
            
$lasts explode(','get_pref('smd_prognostics_lastact'0));
            
$freqs explode(','get_pref('smd_prognostics_alarm_freq'86400));
            if (!isset(
$lasts[1])) {
                
$lasts[1] = $lasts[0];
            }
            if (!isset(
$freqs[1])) {
                
$freqs[1] = $freqs[0];
            }

            
$lastact_txp = ($mode && ($now $lasts[0] > $freqs[0])) ? true false;
            
$lastact_mail = ($mode && ($now $lasts[1] > $freqs[1])) ? true false;
            
$lastmsg get_pref('smd_prognostics_lastmsg''');

            
$subject gTxt('smd_prognostics_subject');
            
$msg join('|'array_merge($hashdown$hashmod$nok$miss$added));

            if (
txpinterface === 'admin' && strpos($via'txp') !== false) {
                if (
$lastact_txp || ($lastmsg != md5($msg))) {
                    
$txpdir get_pref('smd_prognostics_txpdir'hu.smd_prognostics_guess_admin_dir());
                    
$out '<style type="text/css">'.$smd_prognostics_style['msg'].'</style><div class="smd_prog_warn"><div>'.$subject.'</div>'.
                        ((
$hashdown) ? '<p>'.gTxt('smd_prognostics_preamble_hashdown').'</p><ul><li>'.join('</li><li>',$hashdown).'</li></ul>' '').
                        ((
$hashmod) ? '<p>'.gTxt('smd_prognostics_preamble_hashmod').'</p><ul><li>'.join('</li><li>',$hashmod).'</li></ul>' '').
                        ((
$nok) ? '<p>'.gTxt('smd_prognostics_preamble_nok').'</p><ul><li>'.join('</li><li>',$nok).'</li></ul>' '').
                        ((
$miss) ? '<p>'.gTxt('smd_prognostics_preamble_miss').'</p><ul><li>'.join('</li><li>',$miss).'</li></ul>' '').
                        ((
$added) ? '<p>'.gTxt('smd_prognostics_preamble_added').'</p><ul><li>'.join('</li><li>',$added).'</li></ul>' '').
                        
'<p><a href="'.$txpdir.'/index.php?event='.$smd_prognostics_event.a.'step=smd_prognostics_ack'.a.'smd_prognostics_suppress=1">'.
                        
gTxt('smd_prognostics_postamble').'</a></p></div>';

                    
set_pref('smd_prognostics_lastact'$now.','.$lasts[1], 'smd_prognos'PREF_HIDDEN'text_input');
                    echo 
$out;
                }
            }

            if (
strpos($via'email') !== false) {
                if (
$lastact_mail || ($lastmsg != md5($msg))) {
                    
$to get_pref('smd_prognostics_mailto''');
                    if (
$to) {
                        
$hdrs smd_prognostics_header_info('smd_prognostics');
                        
$body =
                            ((
$hashdown) ? n.gTxt('smd_prognostics_preamble_hashdown').n.n.join(n,$hashdown) : '').
                            ((
$hashmod) ? n.gTxt('smd_prognostics_preamble_hashmod').n.n.join(n,$hashmod) : '').
                            ((
$nok) ? n.gTxt('smd_prognostics_preamble_nok').n.n.join(n,$nok) : '').
                            ((
$miss) ? n.n.gTxt('smd_prognostics_preamble_miss').n.n.join(n,$miss) : '').
                            ((
$added) ? n.n.gTxt('smd_prognostics_preamble_added').n.n.join(n,$added) : '').
                            
n.n.'<a href="'.$hdrs['txpdir'].'/index.php?event='.$smd_prognostics_event.a.'step=smd_prognostics_ack'.a.'smd_prognostics_suppress=1">'.gTxt('smd_prognostics_postamble').'</a>';
                        
mail($to$subject$body$hdrs['headers']);
                    }
                    
set_pref('smd_prognostics_lastact'$lasts[0].','.$now'smd_prognos'PREF_HIDDEN'text_input');
                }
            }

            
set_pref('smd_prognostics_lastmsg'md5($msg), 'smd_prognos'PREF_HIDDEN'text_input');

            if (
$lastmsg != md5($msg)) {
                if (
$detect == '') {
                    
set_pref('smd_prognostics_lastdetect'$now'smd_prognos'PREF_HIDDEN'text_input');
                } else {
                    
$detect explode(',',$detect);
                    
set_pref('smd_prognostics_lastdetect'$detect[0].','.$now'smd_prognos'PREF_HIDDEN'text_input');
                }
            }
        }
    }
}

// ----------  Catch unexpected request headers
function smd_prognostics_request_headers($evt$stp) {
    
$block explode('|'get_pref('smd_prognostics_req_headers'''));
    
$hdr serverSet('REQUEST_METHOD');

    if (
in_array($hdr$block)) {
        
$send = (strpos(get_pref('smd_prognostics_rt_forensics'), 'hdr') !== false);

        
// Send the forensics off if necessary
        
$to get_pref('smd_prognostics_mailto_csi''');
        if (
$to && $send) {
            
$subject gTxt('smd_prognostics_subject_csi');
            
$hdrs smd_prognostics_header_info('smd_frognostics');
            
$body n.gTxt('smd_prognostics_req_not_allowed', array('{req}' => $hdr)).n;
            foreach (
$_SERVER $_REQUEST $_ENV as $key => $var) {
                
$body .= n.$key.': '.$var;
            }

            
mail($to$subject$body$hdrs['headers']);
        }

        
//TODO: offer alternative die mechanisms like SQL attacks?
        
exit(1);
    }
}

// ----------  SQL injection detection
function smd_prognostics_sql_inject() {
    global 
$smd_prognostics_sqlprot$permlink_mode$DB;

    
// Determine comment preview step and ignore if so
    
$is_prevu false;
    
$com psa(array(
        
'parentid',
        
'preview',
        
'backpage',
    ));
    if (
$com['preview']) {
        
$urlparts explode('/'$com['backpage']);
        
$num = ($permlink_mode == 'messy') ? count($urlparts);
        
$artic safe_field('id','textpattern'"ID=".doSlash($com['parentid']).(($num 1) ? " AND url_title='".doSlash($urlparts[$num-1])."'" ''));
        
$is_prevu = ($artic) ? true false;
    }
    if(!
$is_prevu && $smd_prognostics_sqlprot->isMalicious()) {
        
$opts do_list(get_pref('smd_prognostics_sql_inject''|'), '|');
        
$blok = (strpos($opts[0], 'smd_block') !== false);
        
$send = (strpos(get_pref('smd_prognostics_rt_forensics'), 'sql') !== false);
        
$ver = (defined('txp_version')) ? txp_version get_pref('version''');

        
// Send the forensics off if necessary
        
$to get_pref('smd_prognostics_mailto_csi''');
        if (
$to && $send) {
            
$subject gTxt('smd_prognostics_subject_csi');
            
$hdrs smd_prognostics_header_info('smd_frognostics');
            
$body n.gTxt('smd_prognostics_preamble_sql_inject').n;
            
$body .= (($ver) ? 'Txp: ' $ver .'') . 'PHP: ' phpversion() .n'MySQL: ' mysqli_get_server_info($DB->link) .n. ((is_callable('apache_get_version')) ? 'Apache: ' apache_get_version().'');
            foreach (
$_SERVER $_REQUEST $_ENV as $key => $var) {
                
$body .= n.$key.': '.$var;
            }

            
mail($to$subject$body$hdrs['headers']);
        }

        if (isset(
$opts[1]) && !empty($opts[1])) {
            
$parts do_list($opts[1], ':');
            if (
$parts[0] == 'txp_form') {
                
$msg parse_form($parts[1]);
            } else {
                
$msg $opts[1];
            }
        } else {
            
$msg '';
        }

        if (
$blok) {
            echo 
$msg;
            exit(
1);
        } else {
            
txp_die($msg$opts[0]);
        }
    }
}

// -----------------
// Admin-side panels
// -----------------
// ----------  Alarm acknowledgement
function smd_prognostics_ack($msg '')
{
    global 
$smd_prognostics_event$smd_prognostics_checksums$prefs$DB;

    
$method ps('edit_method');
    
$submit = ($method == 'acknowledge');
    
$ignore = ($method == 'ignore');
    
$csi    = ($method == 'csi');

    
$ack gps('selected');
    
$smd_prog_ack = (is_array($ack)) ? $ack : (($ack) ? array($ack) : array());

    
$out = array();

    if (
$submit || $ignore) {
        if (
$cs = @file($smd_prognostics_checksums)) {
            foreach (
$cs as $c) {
                if (
preg_match('@^(\S+): \((.*)\)$@'trim($c), $m)) {
                    list(,
$file,$md5) = $m;
                    if ((
$key array_search($file$smd_prog_ack)) !== false) {
                        if (
file_exists($file)) {
                            
$content smd_prognostics_prep_file($file);
                            
$out[] = $file.': ('.( ($ignore) ? 'NULL' md5($content) ).')';
                        }
                        
// Remove files that already have checksums so we're left with additions
                        
unset($smd_prog_ack[$key]);
                    } else {
                        
$out[] = trim($c);
                    }
                }
            }

            
// Tack on any new files
            
foreach ($smd_prog_ack as $file) {
                if ( (
$file != $smd_prognostics_checksums) && file_exists($file) ) {
                    
$content smd_prognostics_prep_file($file);
                    
$out[] = $file.': ('.( ($ignore) ? 'NULL' md5($content) ).')';
                }
            }

            
$fh fopen($smd_prognostics_checksums"w");
            
fwrite($fhjoin(n$out));
            
fclose($fh);
            
smd_prognostics_self_hash();
            
$msg gTxt('smd_prognostics_acked');
        }
    }

    if (
$csi && $smd_prog_ack) {
         
$msg gTxt('smd_prognostics_csi_sent');
    } else if (
$csi) {
         
$msg gTxt('smd_prognostics_none_selected');
    }

    
pagetop(gTxt('smd_prognostics_ttl_ack'), $msg);

    list(
$nok$miss$added$hashdown$hashmod) = smd_do_prognostics(1);
    
$nok is_array($nok) ? $nok : array();
    
$miss is_array($miss) ? $miss : array();
    
$added is_array($added) ? $added : array();
    
$hashmod is_array($hashmod) ? $hashmod : array();
    
$errnum count(array_merge($nok$miss$added$hashmod));
    
$forensics = array();
    
$lform 'Y-m-d H:i:s';
    
$med '';

    echo 
n'<div class="txp-layout">'.
        
n'<div class="txp-layout-2col">'.
        
n'<h1 class="txp-heading">'.
        
ngTxt('smd_prognostics_ttl_ack').
        
n'</h1>'.
        
n'</div>'.
        
n'<div id="'.$smd_prognostics_event.'_control" class="txp-layout-2col">'.
        
nsmd_prognostics_button_bar('ack').
        
n'</div>'.
        
n'<div id="'.$smd_prognostics_event.'_container" class="txp-container">'.
        
n'<form class="smd_prognostics-ack-form" name="longform" method="post" action="?event='.$smd_prognostics_event.a.'step=smd_prognostics_ack'.a.'smd_prognostics_suppress=1">'.
        
n'<div class="txp-listtables">'.
        
nstartTable('''''txp-list');

    if (
$errnum 0) {
        foreach(
$hashmod as $naughty) {
            
$sel in_array($naughty$smd_prog_ack);
            echo 
tr(td(checkbox('selected[]'$naughty$sel), '''multi-edit').tda(gTxt('smd_prognostics_lbl_hashmod')).tda($naughty));
        }

        foreach(
$nok as $naughty) {
            
$sel in_array($naughty$smd_prog_ack);
            echo 
tr(td(checkbox('selected[]'$naughty$sel), '''multi-edit').tda(gTxt('smd_prognostics_lbl_changed')).tda($naughty));

            if (
$sel) {
                
$fi stat($naughty);
                
$forensics['nok'][] = smd_prognostics_forensic_output($naughty$fi);
                
$forensics['files'][$naughty] = chunk_split(base64_encode(file_get_contents($naughty)));
            }
        }

        foreach(
$miss as $naughty) {
            
$sel in_array($naughty$smd_prog_ack);
            echo 
tr(td(checkbox('selected[]'$naughty$sel), '''multi-edit').tda(gTxt('smd_prognostics_lbl_missing')).tda($naughty));

            if (
$sel) {
                
$forensics['miss'][] = $naughty;
            }
        }

        foreach(
$added as $naughty) {
            
$sel in_array($naughty$smd_prog_ack);
            echo 
tr(td(checkbox('selected[]'$naughty$sel), '''multi-edit').tda(gTxt('smd_prognostics_lbl_added')).tda($naughty));

            if (
$sel) {
                
$fi stat($naughty);
                
$forensics['added'][] = smd_prognostics_forensic_output($naughty$fi);
                
$forensics['files'][$naughty] = chunk_split(base64_encode(file_get_contents($naughty)));
            }
        }

        if (
$smd_prog_ack && $prefs['logging'] != 'none') {
            
$detect explode(',',get_pref('smd_prognostics_lastdetect'time()));

            if (!isset(
$detect[1])) {
                
$detect[1] = $detect[0];
            }

            
$rs safe_rows('*''txp_log'"time BETWEEN '" date($lform, ($detect[0] - $prefs['smd_prognostics_check_freq'])) . "' AND '" date($lform, ($detect[1] + 5)) . "'");

            foreach (
$rs as $row) {
                
$forensics['txp_log'][] = join(',',$row);
            }
        }

        
$methods = array(
            
'acknowledge' => array('label' => gTxt('smd_prognostics_btn_ackit')),
            
'ignore'      => array('label' => gTxt('smd_prognostics_btn_ignore')),
            
'csi'         => array('label' => gTxt('smd_prognostics_btn_csi')),
        );

        
$med multi_edit($methods$smd_prognostics_event'smd_prognostics_ack');

        if (
get_pref('smd_prognostics_mailto_csi''') == '') {
            unset(
$methods['csi']);
        }
    } else {
        echo 
tr(td(gTxt('smd_prognostics_no_alarms')));
        
set_pref('smd_prognostics_lastdetect''''smd_prognos'PREF_HIDDEN'text_input');
    }
    echo 
endTable().
        
'</div>'.
        
$med.
        
tInput().
        
'</form>'.
        
'</div></div>'.
        
script_js( <<<EOS
            $(document).ready(function() {
                $('.smd_prognostics-ack-form').txpMultiEditForm({
                    'row' : 'tr',
                    'highlighted' : 'tr'
                });
            });
EOS
        );

    if (
$csi && $forensics) {
        
// Send the forensics off.
        
$to get_pref('smd_prognostics_mailto_csi''');
        if (
$to) {
            
$subject gTxt('smd_prognostics_subject_csi');
            
$hdrs smd_prognostics_header_info('smd_frognostics');
            
$body =
                
n.'Txp: ' txp_version .n'PHP: ' phpversion() .n'MySQL: ' mysqli_get_server_info($DB->link) .n. ((is_callable('apache_get_version')) ? 'Apache: ' apache_get_version().'').
                ((isset(
$forensics['nok'])) ? n.gTxt('smd_prognostics_preamble_nok').n.join(n,$forensics['nok']) : '').
                ((isset(
$forensics['miss'])) ? n.n.gTxt('smd_prognostics_preamble_miss').n.join(n,$forensics['miss']) : '').
                ((isset(
$forensics['added'])) ? n.n.gTxt('smd_prognostics_preamble_added').n.join(n,$forensics['added']) : '').
                ((isset(
$forensics['txp_log'])) ? n.n.gTxt('smd_prognostics_preamble_txplog').n.join(n,$forensics['txp_log']) : n.n.gTxt('smd_prognostics_no_log_entries'));
            if (isset(
$forensics['files'])) {
                
$body .= n.n.gTxt('smd_prognostics_preamble_files');
                foreach (
$forensics['files'] as $fn => $content) {
                    
$body .= n.n.$fn.n.n.$content.n;
                }
            }

            
mail($to$subject$body$hdrs['headers']);
        }
    }
}

// ---------- File management
function smd_prognostics_files($msg '')
{
    global 
$smd_prognostics_event$smd_prognostics_checksums;

    
extract(doSlash(gpsa(array('submit'))));
    
$smd_prognostics_files gps('smd_prognostics_files');

    if (!
is_array($smd_prognostics_files)) {
        
$smd_prognostics_files = array();
    }

    
$adds = (strpos(get_pref('smd_prognostics_check_for'), 'add') !== false);
    
$filelist smd_prognostics_readfiles();
    
$allcount count($filelist);

    if (
$submit) {
        
$outfile = array();
        foreach (
$smd_prognostics_files as $file) {
            
$content smd_prognostics_prep_file($file);
            
$outfile[] = $file.': ('.md5($content).')';
        }

        if (
is_writable(dirname($smd_prognostics_checksums))) {
            
$fh = @fopen($smd_prognostics_checksums"w");
            if (
$fh) {
                
fwrite($fhjoin(n$outfile));

                if (
$adds) {
                    
$additions array_diff($filelist$smd_prognostics_files);
                    
$added = array();
                    foreach (
$additions as $addition) {
                        
$added[] = $addition.': (NULL)';
                    }
                    
fwrite($fhn.join(n$added));
                }
                
fclose($fh);
                
smd_prognostics_self_hash();
                
smd_do_prognostics(2); // Silently acknowledge all the files
                
set_pref('smd_prognostics_lastdetect''''smd_prognos'PREF_HIDDEN'text_input');
                
$msg gTxt('smd_prognostics_files_updated');
            } else {
                
$msg = array(gTxt('smd_prognostics_not_writable', array('{location}' => $smd_prognostics_checksums)), E_WARNING);
            }
        } else {
            
$msg = array(gTxt('smd_prognostics_not_writable', array('{location}' => dirname($smd_prognostics_checksums))), E_WARNING);
        }
    }

    
pagetop(gTxt('smd_prognostics_ttl_files'), $msg);

    
$smd_prognostics_files = array();

    if (
$cs = @file($smd_prognostics_checksums)) {
        foreach (
$cs as $c) {
            if (
preg_match('@^(\S+): \((.*)\)$@'trim($c), $m)) {
                list(,
$file,$md5) = $m;

                if (
$md5 != 'NULL') {
                    
$smd_prognostics_files[] = $file;
                }
            }
        }
    }

    
$moncount count($smd_prognostics_files);

    if (
$filelist) {
        
$filez = array();

        foreach(
$filelist as $key => $val) {
            
$filez[$val] = $filelist[$key];
        }

        
$filesel smd_prognostics_multisel('smd_prognostics_files'$filez$smd_prognostics_files);
    }

    
$showfiles = (!empty($filesel));

    echo 
n'<div class="txp-layout">'.
        
n'<div class="txp-layout-2col">'.
        
n'<h1 class="txp-heading">'.
        
ngTxt('smd_prognostics_ttl_files').
        
n'</h1>'.
        
n'</div>'.
        
n'<div id="'.$smd_prognostics_event.'_control" class="txp-layout-2col">'.
        
nsmd_prognostics_button_bar('fil').
        
n'</div>'.
        
n'<div id="'.$smd_prognostics_event.'_container" class="txp-container">'.
        
n'<form class="smd_prognostics-files-form" method="post" action="?event='.$smd_prognostics_event.a.'step=smd_prognostics_files">'.
        
nstartTable('''''txp-list').
        
ntr(tdcs(gTxt('smd_prognostics_currmon', array('{curr}' => $moncount'{outof}' => $allcount)) .(($showfiles) ? br.brgTxt('smd_prognostics_monfiles_explain') : ''), 1400)).
        
n. (($showfiles) ? tr(tda($filesel)) : tr(tda(gTxt('smd_prognostics_no_files')))).
        
ntr(tda(fInput('hidden''smd_prognostics_suppress'1).fInput('submit''submit'gTxt('save'), 'publish'))).
        
nendTable().
        
ntInput().
        
n'</form>'.
        
n'</div></div>';
}

// ---------- Setup / prefs
function smd_prognostics_setup($msg '')
{
    global 
$smd_prognostics_event$smd_prognostics_checksums$prefs;

    
$origloc get_pref('smd_prognostics_listloc'realpath(txpath.DS.'..'.DS).DS1);
    
$origexc get_pref('smd_prognostics_excludir''images, files, tmp'1);
    
$origdir rtrim(get_pref('smd_prognostics_dir'realpath(txpath), 1), DS);
    
$origpfx get_pref('smd_prognostics_prefix'''1);

    
$preflist = array(
        
'smd_prognostics_check_freq',
        
'smd_prognostics_check_qty',
        
'smd_prognostics_alarm_freq',
        
'smd_prognostics_check_where',
        
'smd_prognostics_mailto',
        
'smd_prognostics_mailto_csi',
        
'smd_prognostics_users',
        
'smd_prognostics_dir',
        
'smd_prognostics_prefix',
        
'smd_prognostics_listloc',
        
'smd_prognostics_excludir',
        
'smd_prognostics_ignores',
        
'smd_prognostics_inject_sensitivity',
        
'smd_prognostics_xss',
        
'smd_prognostics_txpdir',
    );

    
$warnloc '';
    
$notify gps('smd_prognostics_notify_via');
    
$chfor gps('smd_prognostics_check_for');
    
$reqs gps('smd_prognostics_req_headers');
    
$sqlin gps('smd_prognostics_sql_inject');
    
$tween gps('smd_prognostics_check_between');
    
$rtfor gps('smd_prognostics_rt_forensics');

    
// Only one of these valid status codes is allowed to be thrown
    
$throw_codes = array(
        
'smd_no' => gTxt('no'),
        
'smd_block' => gTxt('smd_prognostics_block'),
        
'200' => '200 OK',
        
'301' => '301 Moved Permanently',
        
'302' => '302 Found',
        
'307' => '307 Temporary Redirect',
        
'401' => '401 Unauthorized',
        
'403' => '403 Forbidden',
        
'404' => '404 Not Found',
        
'410' => '410 Gone',
        
'414' => '414 Request-URI Too Long',
        
'500' => '500 Internal Server Error',
        
'501' => '501 Not Implemented',
    );

    if (!
is_array($tween)) {
        
$tween = array();
    }

    foreach (
$tween as $idx => $val) {
        if (empty(
$val)) {
            
$tween[$idx] = ($idx==0) ? '00:00' '23:59';
        } else {
            
$timeparts do_list($val':');
            foreach (
$timeparts as $num) {
                if (!
is_numeric($num)) {
                    
$tween[$idx] = ($idx==0) ? '00:00' '23:59';
                    break;
                }
            }
        }
    }

    
$smd_prognostics_notify_via join('|', ((is_array($notify)) ? $notify : array($notify)));
    
$smd_prognostics_check_for join('|', ((is_array($chfor)) ? $chfor : array($chfor)));
    
$smd_prognostics_req_headers join('|', ((is_array($reqs)) ? $reqs : array($reqs)));
    
$smd_prognostics_sql_inject join('|', ((is_array($sqlin)) ? $sqlin : array($sqlin)));
    
$smd_prognostics_check_between join('|', ((is_array($tween)) ? $tween : array($tween)));
    
$smd_prognostics_rt_forensics join('|', ((is_array($rtfor)) ? $rtfor : array($rtfor)));

    
// Grab the saved pref values and tack on the array item(s)
    
extract(doSlash(gpsa(array_merge(array('submit'), $preflist))));
    
$preflist[] = 'smd_prognostics_notify_via';
    
$preflist[] = 'smd_prognostics_check_for';
    
$preflist[] = 'smd_prognostics_req_headers';
    
$preflist[] = 'smd_prognostics_sql_inject';
    
$preflist[] = 'smd_prognostics_check_between';
    
$preflist[] = 'smd_prognostics_rt_forensics';
    
$smd_prognostics_dir rtrim($smd_prognostics_dirDS);

    if (
$submit) {
        if ((
$smd_prognostics_dir != $origdir) || ($smd_prognostics_prefix != $origpfx)) {
            if (
is_dir($smd_prognostics_dir) && is_writable($smd_prognostics_dir)) {
                
// Everything OK so do nothing for now
            
} else {
                
// Ignore new dir and reset it to what it was before
                
$msg = array(gTxt('smd_prognostics_not_writable', array('{location}' => $smd_prognostics_dir)), E_WARNING);
                
$smd_prognostics_dir $origdir;
            }

            
// Room for more config files as and when they are required
            
$filelist[] = $smd_prognostics_checksums;

            foreach (
$filelist as $file) {
                
$filename $smd_prognostics_prefix.ltrim(basename($file), $origpfx);
                
rename($filertrim($smd_prognostics_dirDS).DS.$filename);
            }
        }

        
// Write all the prefs
        
foreach ($preflist as $prefval) {
            
set_pref(doSlash($prefval), doSlash($$prefval), 'smd_prognos'PREF_HIDDEN'text_input');
        }

        if ( (
$smd_prognostics_listloc != $origloc) || ($smd_prognostics_excludir != $origexc) ) {
            
$msg = array(gTxt('smd_prognostics_warn_loc'), E_WARNING);
        }

        if (!
$msg) {
            
$msg gTxt('preferences_saved');
        }
    }

    
pagetop(gTxt('smd_prognostics_ttl_setup'), $msg);

    
$smd_prognostics_dir get_pref('smd_prognostics_dir'txpath1);
    
$smd_prognostics_prefix get_pref('smd_prognostics_prefix'''1);
    
$smd_prognostics_check_for get_pref('smd_prognostics_check_for''delete|modify'1);
    
$smd_prognostics_check_where get_pref('smd_prognostics_check_where''admin'1);
    
$smd_prognostics_check_freq get_pref('smd_prognostics_check_freq'101);
    
$smd_prognostics_check_between get_pref('smd_prognostics_check_between''00:00|23:59'1);
    
$smd_prognostics_check_qty get_pref('smd_prognostics_check_qty''30'1);
    
$smd_prognostics_alarm_freq get_pref('smd_prognostics_alarm_freq'864001);
    
$smd_prognostics_notify_via get_pref('smd_prognostics_notify_via'''1);
    
$smd_prognostics_mailto get_pref('smd_prognostics_mailto'''1);
    
$smd_prognostics_mailto_csi get_pref('smd_prognostics_mailto_csi'''1);
    
$smd_prognostics_users get_pref('smd_prognostics_users'''1);
    
$smd_prognostics_listloc get_pref('smd_prognostics_listloc'realpath(txpath.DS.'..'.DS).DS1);
    
$smd_prognostics_excludir get_pref('smd_prognostics_excludir''images, files, tmp'1);
    
$smd_prognostics_ignores get_pref('smd_prognostics_ignores''error_log'1);
    
$smd_prognostics_req_headers get_pref('smd_prognostics_req_headers''TRACE|PUT|DELETE'1);
    
$smd_prognostics_sql_inject get_pref('smd_prognostics_sql_inject''0|'1);
    
$smd_prognostics_inject_sensitivity get_pref('smd_prognostics_inject_sensitivity'11);
    
$smd_prognostics_xss get_pref('smd_prognostics_xss'01);
    
$smd_prognostics_rt_forensics get_pref('smd_prognostics_rt_forensics''1|1'1);
    
$smd_prognostics_txpdir get_pref('smd_prognostics_txpdir'hu.smd_prognostics_guess_admin_dir(), 1);

    
$adds = (strpos($smd_prognostics_check_for'add') !== false);
    
$dels = (strpos($smd_prognostics_check_for'delete') !== false);
    
$mods = (strpos($smd_prognostics_check_for'modify') !== false);
    
$rh_trace = (strpos($smd_prognostics_req_headers'TRACE') !== false);
    
$rh_put = (strpos($smd_prognostics_req_headers'PUT') !== false);
    
$rh_del = (strpos($smd_prognostics_req_headers'DELETE') !== false);
    
$rt_hdr = (strpos($smd_prognostics_rt_forensics'hdr') !== false);
    
$rt_sql = (strpos($smd_prognostics_rt_forensics'sql') !== false);
    
$tweens do_list($smd_prognostics_check_between'|');
    
$throws do_list($smd_prognostics_sql_inject'|');
    
$helpLink "?event=plugin".a."step=plugin_help".a."name=$smd_prognostics_event#smd_setup";

    
//TODO: refactor with inputLabel()
    
echo n'<div class="txp-layout">'.
        
n'<div class="txp-layout-2col">'.
        
n'<h1 class="txp-heading">'.
        
ngTxt('smd_prognostics_ttl_setup'), spgTxt('smd_prognostics_help_link', array('{link}' => $helpLink), 'raw').
        
n'</h1>'.
        
n'</div>'.
        
n'<div id="'.$smd_prognostics_event.'_control" class="txp-layout-2col">'.
        
nsmd_prognostics_button_bar('set').
        
n'</div>'.
        
n'<div id="'.$smd_prognostics_event.'_container" class="txp-container">'.
        
n'<form class="smd_prognostics-setup-form" method="post" action="?event='.$smd_prognostics_event.a.'step=smd_prognostics_setup">'.
        
nstartTable('''''smd_prognostics_setup').
        
ntr(tdcs(hed(gTxt('smd_prognostics_ttl_monopts'), 3), 2)).
        
ntr(
            
tda(gTxt('smd_prognostics_check_where'), ' class="smd_label"').
            
tda(radioSet(
                array(
                    
'admin' => gTxt('no'),
                    
'adminpublic' => gTxt('yes')
                ), 
'smd_prognostics_check_where'$smd_prognostics_check_where), ' id="smd_prognostics_check_where"')
        ).
        
ntr(
            
tda(gTxt('smd_prognostics_check_freq'), ' class="smd_label"').
            
tda(fInput('text''smd_prognostics_check_freq'$smd_prognostics_check_freq'','','',15).sp.gTxt('smd_prognostics_seconds'))
        ).
        
ntr(
            
tda(gTxt('smd_prognostics_check_between'), ' class="smd_label"').
            
tda(
                
fInput('text''smd_prognostics_check_between[]'$tweens[0], '','','',15).
                
gTxt('smd_prognostics_and').
                
fInput('text''smd_prognostics_check_between[]'$tweens[1], '','','',15).sp.gTxt('smd_prognostics_hms')
            )
        ).
        
ntr(
            
tda(gTxt('smd_prognostics_check_qty'), ' class="smd_label"').
            
tda(fInput('text''smd_prognostics_check_qty'$smd_prognostics_check_qty'','','',15))
        ).
        
ntr(
            
tda(gTxt('smd_prognostics_alarm_freq'), ' class="smd_label"').
            
tda(fInput('text''smd_prognostics_alarm_freq'$smd_prognostics_alarm_freq'','','',15).sp.gTxt('smd_prognostics_seconds'))
        ).
        
ntr(
            
tda(gTxt('smd_prognostics_notify_via'), ' class="smd_label"').
            
tda(
                
checkbox('smd_prognostics_notify_via[]''txp'strpos($smd_prognostics_notify_via'txp') !== false) .
                
'<label>'.gTxt('smd_prognostics_notify_txp').'</label>'.
                
checkbox('smd_prognostics_notify_via[]''email'strpos($smd_prognostics_notify_via'email') !== false).
                
'<label>'.gTxt('smd_prognostics_notify_email').'</label>'
            
)
        ).
        
ntr(
            
tda(gTxt('smd_prognostics_mailto'), ' class="smd_label"').
            
tda(fInput('text''smd_prognostics_mailto'$smd_prognostics_mailto'','','',60).sp.gTxt('smd_prognostics_csv'))
        ).
        
ntr(
            
tda(gTxt('smd_prognostics_mailto_csi'), ' class="smd_label"').
            
tda(fInput('text''smd_prognostics_mailto_csi'$smd_prognostics_mailto_csi'','','',60).sp.gTxt('smd_prognostics_csv'))
        ).
        
ntr(tdcs(hed(gTxt('smd_prognostics_ttl_realopts'), 3), 2)).
        
ntr(
            
tda(gTxt('smd_prognostics_req_headers'), ' class="smd_label"').
            
tda(
                
checkbox('smd_prognostics_req_headers[]''TRACE'$rh_trace).
                
'<label>'.gTxt('smd_prognostics_rh_trace').'</label>'.
                
checkbox('smd_prognostics_req_headers[]''PUT'$rh_put).
                
'<label>'.gTxt('smd_prognostics_rh_put').'</label>'.
                
checkbox('smd_prognostics_req_headers[]''DELETE'$rh_del).
                
'<label>'.gTxt('smd_prognostics_rh_del').'</label>'
            
)
        ).
        
ntr(
            
tda(gTxt('smd_prognostics_sql_inject'), ' class="smd_label"').
            
tda(
                
selectInput('smd_prognostics_sql_inject[]'$throw_codes$throws[0], 0).
                
gTxt('smd_prognostics_with').
                
fInput('text''smd_prognostics_sql_inject[]'$throws[1], '','','',60).br.
                
gTxt('smd_prognostics_sensitivity').
                
fInput('text''smd_prognostics_inject_sensitivity'$smd_prognostics_inject_sensitivity'','','',5).sp.gTxt('smd_prognostics_sensitivity_explain').br.
                
gTxt('smd_prognostics_xss').
                
yesnoRadio('smd_prognostics_xss'$smd_prognostics_xss).sp.gTxt('smd_prognostics_xss_explain')
            )
        ).
        
ntr(
            
tda(gTxt('smd_prognostics_send_forensics'), ' class="smd_label"').
            
tda(
                
checkbox('smd_prognostics_rt_forensics[]''hdr'$rt_hdr).
                
'<label>'.gTxt('smd_prognostics_rt_hdr').'</label>'.
                
checkbox('smd_prognostics_rt_forensics[]''sql'$rt_sql).
                
'<label>'.gTxt('smd_prognostics_rt_sql').'</label>'
            
)
        ).
        
ntr(tdcs(hed(gTxt('smd_prognostics_ttl_fileopts'), 3), 2)).
        
ntr(
            
tda(gTxt('smd_prognostics_check_for'), ' class="smd_label"').
            
tda(
                
checkbox('smd_prognostics_check_for[]''add'$adds) .
                
'<label>'.gTxt('smd_prognostics_ch_add').'</label>'.
                
checkbox('smd_prognostics_check_for[]''delete'$dels).
                
'<label>'.gTxt('smd_prognostics_ch_del').'</label>'.
                
checkbox('smd_prognostics_check_for[]''modify'$mods).
                
'<label>'.gTxt('smd_prognostics_ch_mod').'</label>'
            
)
        ).
        
ntr(
            
tda(gTxt('smd_prognostics_listloc'), ' class="smd_label"').
            
tda(fInput('text''smd_prognostics_listloc'$smd_prognostics_listloc'','','',60).sp.gTxt('smd_prognostics_csv'))
        ).
        
ntr(
            
tda(gTxt('smd_prognostics_excludir'), ' class="smd_label"').
            
tda(fInput('text''smd_prognostics_excludir'$smd_prognostics_excludir'','','',60).sp.gTxt('smd_prognostics_csv'))
        ).
        
ntr(
            
tda(gTxt('smd_prognostics_ignores'), ' class="smd_label"').
            
tda(fInput('text''smd_prognostics_ignores'$smd_prognostics_ignores'','','',60).sp.gTxt('smd_prognostics_csv'))
        ).
        
ntr(tdcs(hed(gTxt('smd_prognostics_ttl_plugopts'), 3), 2)).
        
ntr(
            
tda(gTxt('smd_prognostics_auth_users'), ' class="smd_label"').
            
tda(fInput('text''smd_prognostics_users'$smd_prognostics_users'','','',60).sp.gTxt('smd_prognostics_csv').sp.gTxt('smd_prognostics_csense'))
        ).
        
ntr(
            
tda(gTxt('smd_prognostics_txpdir'), ' class="smd_label"').
            
tda(fInput('text''smd_prognostics_txpdir'$smd_prognostics_txpdir'','','',60))
        ).
        
ntr(
            
tda(gTxt('smd_prognostics_progloc'), ' class="smd_label"').
            
tda(fInput('text''smd_prognostics_dir'$smd_prognostics_dir'','','',60))
        ).
        
ntr(
            
tda(gTxt('smd_prognostics_progpfx'), ' class="smd_label"').
            
tda(fInput('text''smd_prognostics_prefix'$smd_prognostics_prefix'','','',60))
        ).
        
ntr(tda(fInput('submit''submit'gTxt('save'), 'publish'))).
        
nendTable().
        
ntInput().
        
n'</form>'.
        
n'</div>'.
        
n'</div>';
}

// ----------  Security advice
function smd_prognostics_advice($msg '')
{
    global 
$smd_prognostics_event$prefs$event$DB;
    require_once 
txpath.'/lib/IXRClass.php';

    
pagetop(gTxt('smd_prognostics_ttl_advice'), $msg);

    
$checks = array();

    
// Prognostics dir in docroot?
    
if (strpos(get_pref('smd_prognostics_dir'txpath), $prefs['path_to_site']) !== false) {
        
$checks[] = gTxt('smd_prognostics_docroot_prognostics');
    }

    
// Setup still exists?
    
if (@is_dir(txpath DS'setup')) {
        
$checks[] = gTxt('smd_prognostics_setup_exists');
    }

    
// RPC still exists?
    
if ( ($prefs['enable_xmlrpc_server'] == 0) && (@is_dir(realpath(txpath.DS.'..'.DS'rpc'))) ) {
        
$checks[] = gTxt('smd_prognostics_rpc_exists');
    }

    
// New Txp version/branch available?
    
include_once txpath.'/include/txp_diag.php';
    
$now time();
    
$updateInfo unserialize(get_pref('smd_prognostics_last_update_check'''));

    if (!
$updateInfo || ( $now > ($updateInfo['when'] + (60*60*24)) )) {
        
$updates checkUpdates();
        
$checks[] = $updateInfo['msg'] = ($updates) ? gTxt($updates['msg'], array('{version}' => $updates['version'])) : '';
        
$updateInfo['when'] = $now;
        
set_pref('smd_prognostics_last_update_check'serialize($updateInfo), 'smd_prognos'PREF_HIDDEN'text_input');
    }

    
// Files dir in docroot?
    
if (strpos($prefs['file_base_path'], $prefs['path_to_site']) !== false) {
        
$checks[] = gTxt('smd_prognostics_docroot_files');
    }

    
// Tmp dir in docroot?
    
if (strpos($prefs['tempdir'], $prefs['path_to_site']) !== false) {
        
$checks[] = gTxt('smd_prognostics_docroot_tmp');
    }

/*
    // TODO: check what SHOW GRANTS returns and determine if it could be a security risk
    $res = safe_query('SHOW GRANTS');
    if (numRows($res) > 0) {
        $checks[] = gTxt('smd_prognostics_show_grants');
    }
*/

    // Does this MySQL user have FILES privs?
    
$randfile $prefs['tempdir'].DS.rand().time().'.sql';
    
$res = @safe_query('SELECT id INTO OUTFILE "'.$randfile.'" FIELDS TERMINATED BY "\t" LINES TERMINATED BY "\n" FROM txp_image WHERE 1 LIMIT 1');
    if (
mysqli_error($DB->link) == '') {
        
$checks[] = gTxt('smd_prognostics_sql_file_privs');
        if (
is_file($randfile)) {
            @
unlink($randfile);
        }
    }

    echo 
n'<div class="txp-layout">'.
        
n'<div class="txp-layout-2col">'.
        
n'<h1 class="txp-heading">'.
        
ngTxt('smd_prognostics_ttl_advice').
        
n'</h1>'.
        
n'</div>'.
        
n'<div id="'.$smd_prognostics_event.'_control" class="txp-layout-2col">'.
        
nsmd_prognostics_button_bar('adv').
        
n'</div>'.
        
n'<div id="'.$smd_prognostics_event.'_container" class="txp-container">'.
        
nstartTable('''''smd_prognostics_advice');

    if (
$checks) {
        foreach(
$checks as $check) {
            echo 
ntr(tda($check));
        }
    } else {
        echo 
ntr(tda(gTxt('smd_prognostics_tight')));
    }

    echo 
endTable(),
        
n'</div></div>';
}

// ---------- Common buttons
function smd_prognostics_button_bar($active '')
{
    global 
$smd_prognostics_event;

    
$active = ($active) ? $active 'setup';

    
$btns = array(
        
'ack' => eLink($smd_prognostics_event'smd_prognostics_ack''smd_prognostics_suppress''1'gTxt('smd_prognostics_pnl_ack')),
        
'adv' => sLink($smd_prognostics_event'smd_prognostics_advice'gTxt('smd_prognostics_pnl_advice')),
        
'fil' => sLink($smd_prognostics_event'smd_prognostics_files'gTxt('smd_prognostics_pnl_files')),
        
'set' => sLink($smd_prognostics_event'smd_prognostics_setup'gTxt('smd_prognostics_pnl_setup')),
    );

    return 
graf(
            ((
$active == 'set') ? strong($btns['set']) : $btns['set'])
            .
n.(($active == 'fil') ? strong($btns['fil']) : $btns['fil'])
            .
n.(($active == 'ack') ? strong($btns['ack']) : $btns['ack'])
            .
n.(($active == 'adv') ? strong($btns['adv']) : $btns['adv'])
        , 
' class="txp-buttons"');
}

// ---------- Hash the hashfile
function smd_prognostics_self_hash() {
    global 
$smd_prognostics_checksums;

    if (
file_exists($smd_prognostics_checksums)) {
        
$content smd_prognostics_prep_file($smd_prognostics_checksums);
        
$hash md5($content);
    } else {
        
$hash NULL;
    }
    
set_pref('smd_prognostics_sumhash'$hash'smd_prognos'PREF_HIDDEN'text_input');
}

// ---------- Compile list of files to monitor
function smd_prognostics_readfiles() {
    
$filelist = array();

    
$smd_prognostics_listloc get_pref('smd_prognostics_listloc''');
    
$excludes smd_prognostics_ignore_list();

    if (
$smd_prognostics_listloc) {
        foreach (
do_list($smd_prognostics_listloc) as $loc) {
            
// NOTE: not using GLOB_BRACE to grab regular and dot files, since it's not always available cross-OS
            
$filelist array_merge($filelistsmd_prognostics_rglob("*"GLOB_MARK$loc$excludes), smd_prognostics_rglob(".*"GLOB_MARK$loc$excludes));
        }
    }
    return 
$filelist;
}

// ---------- Prepare file contents for md5. Assumes file exists
// Checks to see if large files are binary before wasting time stripping newlines and stuff
function smd_prognostics_prep_file($file) {
    
$content file_get_contents($file);
    
$stat stat($file);
    
$dostrip true;
    
// Perform the strip regardless on files < 1Kb as it has little impact
    
if ($stat['size'] > 1024000) {
        
$part substr($content01536000); // First 1.5KB
        
if (strpos($part0x00) || (strpos($part0x0D) !== false) || (strpos($part0x0A) !== false)) {
            
// Likely a binary file: leave it be
            
$dostrip false;
        }
    }
    if (
$dostrip) {
        
$content str_replace(array("\r\n""\$HeadURL: http:"), array("\n""\$HeadURL: https:"), $content);
    }
    return 
$content;
}

// ---------- Pre-compute excluded files and dirs, expanding wildcards in the process
function smd_prognostics_ignore_list() {
    global 
$smd_prognostics_checksums;

    
$excl do_list(get_pref('smd_prognostics_excludir'''));
    
$ign get_pref('smd_prognostics_ignores''');

    
// Add any files to permanently exclude here (NOT dirs: they're ignored automatically)
    
$ignarr = ($ign) ? do_list($ign) : array();

    
// Expand any wildcard filenames
    
$regarr = array();
    foreach (
$ignarr as $idx => $item) {
        if ( (
strpos($item'*') !== false) || (strpos($item'?') !== false) ) {
            
$regarr[] = str_replace( array("\*""\?"), array(".*""."), preg_quote($item) );
            unset(
$ignarr[$idx]); // Remove the wildcard filename from the ignore list
        
}
    }
    
$regexclude = ($regarr) ? '/^(' join('|'$regarr) . ')$/' '';
    
$permexclude array_merge(array(basename($smd_prognostics_checksums)), $ignarr);

    return array(
$excl$regexclude$permexclude);
}

// Frankensteined from http://snipplr.com/view/16233/recursive-glob/
function smd_prognostics_rglob($pattern$flags=0$path=''$excludes = array(), $excl=array(), $ign='') {
    if (!
$path && ($dir dirname($pattern)) != '.') {
        if (
$dir == '\\' || $dir == DS$dir '';
        return (array)
smd_prognostics_rglob(basename($pattern), $flags$dir DS$excludes);
    }
    
$paths glob($path '*'GLOB_ONLYDIR GLOB_NOSORT);
    
$files glob($path $pattern$flags);

    if (
is_array($paths)) {
        foreach (
$paths as $p) {
            
$parts explode(DS$p);
            
$pinfo array_pop($parts);
            if (!
in_array($pinfo$excludes[0])) {
                
$files array_merge((array)$files, (array)smd_prognostics_rglob($pattern$flags$p DS$excludes));
                foreach(
$files as $idx => $theFile) {
                    
$parts explode(DS$theFile);
                    
$fex array_pop($parts);
                    
$rex = ($excludes[1]) ? preg_match($excludes[1], $fex) : false;
                    if(
$fex=='' || in_array($fex$excludes[2]) || $rex) {
                        unset(
$files[$idx]);
                    }
                }
            }
        }
    }
    return 
$files;
}

// Format forensic data for a file
function smd_prognostics_forensic_output($file$fi) {
    global 
$prefs;

    
$dform $prefs['dateformat'];
    
$sep n;
    
$out '';

    
$out .= $sep gTxt('smd_prognostics_stat_name') . $file;
    
$out .= $sep gTxt('smd_prognostics_stat_size') . $fi['size'];
    
$out .= $sep gTxt('smd_prognostics_stat_mod') . strftime($dform$fi['mtime']);
    
$out .= $sep gTxt('smd_prognostics_stat_uid') . $fi['uid'];
    
$out .= $sep gTxt('smd_prognostics_stat_gid') . $fi['gid'];

    return 
$out;
}

// ---------- Does what it says on the tin
// Assumes 'textpattern' is the admin-side directory if server var not set. Must fix in core one day with true constant
function smd_prognostics_guess_admin_dir() {
    
$admindir trim(dirname($_SERVER['PHP_SELF']), '/\\');
    
$admindir = (empty($admindir)) ? 'textpattern' $admindir;
    return 
$admindir;
}

// ---------- Get common server info for mail headers, etc
function smd_prognostics_header_info($fromname) {
    
$domainparts do_list(doStrip(serverSet('SERVER_NAME')), '.');
    
$numparts count($domainparts);
    
$domain $domainparts[$numparts-2] . '.' $domainparts[$numparts-1];
    
$reply_to 'noreply@'.$domain;
    
$txpdir get_pref('smd_prognostics_txpdir'hu.smd_prognostics_guess_admin_dir());
    
$sep IS_WIN "\r\n" "\n";
    
$headers "From: $fromname <$reply_to>".
        
$sep.'Reply-To: '.$reply_to.
        
$sep.'X-Mailer: Textpattern'.
        
$sep.'Content-Transfer-Encoding: 8bit'.
        
$sep.'Content-Type: text/plain; charset="UTF-8"'.
        
$sep;

    
$out = array(
        
'domain' => $domain,
        
'reply_to' => $reply_to,
        
'txpdir' => $txpdir,
        
'sep' => $sep,
        
'headers' => $headers,
    );

    return 
$out;
}

// Multi-file dropdown selection
function smd_prognostics_multisel($selname=''$tree=array(), $sel=array()) {
    
$out[] = '<select id="'.$selname.'" name="'.$selname.'[]" class="list" style="height:400px;" multiple="multiple">';
    foreach (
$tree as $leaf) {
        
$selected='';
        if (
in_array($leaf$sel)) {
            
$selected ' selected="selected"';
        }

        
$out[] = t.'<option'.$selected.'>'.htmlspecialchars($leaf).'</option>'.n;
    }
    
$out[] = '</select>';
    return 
join('',$out);
}



//****************************************************************
// Web page      : http://code.google.com/p/phprotector
// Autor                : Hugo Sousa        adamastor666@gmail.com
// Date              : 2010-03-25
// Version          : 0.3.1.1
//
//***************************************************************
class smd_prog_PhProtector {
    var 
$SHOW_ERRORS;
    var 
$do_xss;

    public function 
__construct($show_errors) {
        
$this->SHOW_ERRORS=$show_errors;
        if (
$this->SHOW_ERRORS) {
            
error_reporting(E_ERROR E_WARNING E_PARSE);  //Show errors
            
ini_set('display_errors'"1"); //display errors
        
} else {
            
ini_set('display_errors'"0"); //display errors
            
ini_set('log_errors'"1"); //log_errors
        
}
        
$this->do_xss get_pref('smd_prognostics_xss'0);
    }

    
/*
    * Main function to be called in a index page that redirects to other pages
    *
    */
    
public function isMalicious() {
        
$sqli 0;

        
$sqli callback_event('smd_frognostics''sql_injection'false);

        if (!
$sqli) {
            
$num_bad_words1 $this->CheckGet();
            
$num_bad_words2 $this->CheckPost();

            
$thresh explode(','get_pref('smd_prognostics_inject_sensitivity'1));
            if (!isset(
$thresh[1])) {
                
$thresh[1] = $thresh[0];
            }
            
$thresh[0] = is_numeric($thresh[0]) ? $thresh[0] : 1;
            
$thresh[1] = is_numeric($thresh[1]) ? $thresh[1] : 1;

            if (
$num_bad_words1 >= $thresh[0]) {
                
$sqli true;
            }

            if (
$num_bad_words2 >= $thresh[1]) {
                
$sqli true;
            }
        }

        return ((
$sqli <= 0) ? false $sqli);
    }

    
//check for sql injection and XSS in Post variables
    
private function CheckPost() {
        
$num_bad_words 0;

        foreach(
$_POST as $campo => $input) {
            
// XSS PROTECTION
            
if ($this->do_xss) {
                if (
is_array($_POST[$campo])) {
                    
$_POST[$campo] = array_map('htmlentities'$_POST[$campo], array_fill(0count($_POST[$campo]), ENT_NOQUOTES) );
                } else {
                    
$_POST[$campo]= htmlentities($_POST[$campo], ENT_NOQUOTES);
                }
            }
            
$num_bad_words $num_bad_words $this->wordExists($input); //SQL INJECTION
        
}

        return 
$num_bad_words;
    }

    
//check for sql injection and XSS in GET variables
    
private function CheckGet() {
        
$num_bad_words 0;

        foreach(
$_GET as $campo => $input) {
            
// XSS PROTECTION
            
if ($this->do_xss) {
                if (
is_array($_GET[$campo])) {
                    
$_GET[$campo] = array_map('htmlentities'$_GET[$campo], array_fill(0count($_GET[$campo]), ENT_NOQUOTES) );
                } else {
                    
$_GET[$campo]= htmlentities($_GET[$campo], ENT_NOQUOTES);
                }
            }
            
$strinput = (is_array($input)) ? join(''$input) : $input;
            if(
$this->isIdInjection($campo$strinput)){ //SQL ID INJECTION
                
$num_bad_words =    $num_bad_words 0.5;
            }

            
$num_bad_words $num_bad_words $this->wordExists($input); //SQL INJECTION
        
}

        return 
$num_bad_words;
    }

    
/**
    *   return true if injection sql word is found.
    *   The input is tested if is equal to a sql injection pattern
    *   \b[^a-z]*?drop[^a-z]*?\b
    *    http://www.pagecolumn.com/tool/regtest.htm
    **//*        "/*","+"               */
    
private function wordExists($input) {
        
$num_bad_words 0;
        
$input is_array($input) ? $input : array($input);

        
/*
         WORD AFTER
        */
        
$baddelim1 "[^a-z]*"//the delim should be from "a" to "b" anything else is considered sql injection :)
        
$baddelim2 "[^a-z]+";
        
$badwords= array("union""select""show""insert""update""delete""drop""truncate""create""load_file""exec""#""--");
        
//"/*"
        
foreach($badwords as $badword) {
            
$expression "/".$baddelim1.strtolower($badword).$baddelim2."/";
            foreach (
$input as $wrd) {
                if (
preg_match ($expressionstrtolower($wrd))) {
//dmp('*WA*',$badword, $wrd);
                    //die("sql injection!");
                    
$num_bad_words++;
                }
            }
        }

        
/*
        BEFORE WORD
        */
        
$baddelim1 "[^a-z]+"//the delim should be from "a" to "b" anything else is considered sql injection :)
        
$baddelim2 "[^a-z]*";
        
$badwords= array("@@version""@@datadir""user""version");

        foreach(
$badwords as $badword) {
            
$expression "/".$baddelim1.strtolower($badword).$baddelim2."/";
            foreach (
$input as $wrd) {
                if (
preg_match ($expressionstrtolower($wrd))) {
//dmp('*BW*',$badword, $wrd);
                    //die("sql injection!");
                    
$num_bad_words++;
                }
            }
        }

        
/*
        BEFORE WORD AFTER
        */
        
$baddelim1 "[^a-z]+"//the delim should be from "a" to "b" anything else is considered sql injection :)
        
$baddelim2 "[^a-z]+";
        
$badwords= array("benchmark""--""varchar""convert""char""limit""information_schema","table_name""from""where""order");

        foreach(
$badwords as $badword) {
            
$expression "/".$baddelim1.strtolower($badword).$baddelim2."/";
            foreach (
$input as $wrd) {
                if (
preg_match ($expressionstrtolower($wrd))) {
//dmp('*B-W-A*',$badword, $wrd);
                    //die("sql injection!");
                    
$num_bad_words++;
                }
            }
        }
//dmp($num_bad_words);
        
return $num_bad_words;
    }

    
/**
    *   return true if an ID is not really an ID
    *
    **/
    
private function isIDInjection($campo$input) {
        
$reg="/^id/";

        if(
preg_match($reg$campo)) {
            if(!
$this->stringIsNumberNotZero($input)) {
                return 
true;     // if is ID and NOT INTEGER or NULL -> SQL INJECTION!!
            
}
        }

        return 
false;
    }

    
/**
    *   return true if the string is a number (different from 0, the id could not be zero!)
    *  TODO: check if *all* chars are zero and fail, e.g. id=0000
    **/
    
private function stringIsNumberNotZero$string ) {
        if (empty(
$string)) {
            return 
false;
        }
        return 
ctype_digit($string);
    }
//end class