global $smd_prognostics_event, $smd_prognostics_checksums, $smd_prognostics_sqlprot;
$smd_prognostics_event = 'smd_prognostics';
$smd_prognostics_checksums = rtrim(get_pref('smd_prognostics_dir', txpath, 1), 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 $smd_prognostics_style, $txp_user, $event;
$smd_prognostics_privs = '1,2';
$smd_prognostics_suppress = gps('smd_prognostics_suppress');
$smd_prognostics_style = 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; }',
'meter' =>
'#smd_prog_meter { margin:0 auto; border:1px solid #ccc; width:360px; padding:5px; font-size:120%; }
.smd_prog_pass_strength { color:green; font-weight:bold; }',
'advice' =>
'.smd_prog_advice { width:750px; }
#list.smd_prog_advice td { padding:5px; }
.smd_prog_btns { width:140px; }',
);
$privs = safe_field("privs", "txp_users", "name = '".doSlash($txp_user)."'");
$users = get_pref('smd_prognostics_users', '');
$allow = ($users) ? in_array($txp_user, do_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_event, smd_prognostics_gTxt('smd_prognostics'));
register_callback('smd_prognostics_setup', 'plugin_prefs.'.$smd_prognostics_event);
register_callback('smd_prognostics_dispatcher', $smd_prognostics_event);
}
// Password checker goes on the Admin->Users tab for all users
if ($event == 'admin') {
register_callback('smd_prognostics_chronostrength', '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($privs, do_list($smd_prognostics_privs))) {
register_callback('smd_prognostics', 'admin_side', 'pagetop_end');
}
}
// 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');
}
// ------------------------
function smd_prognostics_dispatcher($evt, $stp) {
if(!$stp or !in_array($stp, array(
'smd_prognostics_files',
'smd_prognostics_setup',
'smd_prognostics_advice',
'smd_prognostics_ack',
))) {
smd_prognostics_setup('');
} else $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;
$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==1 || ($now - get_pref('smd_prognostics_lastcheck', 0) > get_pref('smd_prognostics_check_freq', 3600)) && ($timenow>$beg) && ($timenow<$end) ) ? true : false;
// Parts shamelessly plagiarised from txp_diag
if ($tdiff) {
$sumhash = get_pref('smd_prognostics_sumhash', NULL, ($mode==1 ? 1 : 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') {
if ( file_exists($file) && ( (($ctr >= $so_far) && ($ctr < $until) || $mode == 1) ) ) {
$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 < 2 && ($now - $lasts[0] > $freqs[0])) ? true : false;
$lastact_mail = ($mode < 2 && ($now - $lasts[1] > $freqs[1])) ? true : false;
$lastmsg = get_pref('smd_prognostics_lastmsg', '');
$subject = smd_prognostics_gTxt('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 = '
'.$subject.'
'.
(($hashdown) ? '
'.smd_prognostics_gTxt('preamble_hashdown').'
' : '').
(($hashmod) ? '
'.smd_prognostics_gTxt('preamble_hashmod').'
' : '').
(($nok) ? '
'.smd_prognostics_gTxt('preamble_nok').'
' : '').
(($miss) ? '
'.smd_prognostics_gTxt('preamble_miss').'
' : '').
(($added) ? '
'.smd_prognostics_gTxt('preamble_added').'
' : '').
'
'.
smd_prognostics_gTxt('postamble').'
';
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.smd_prognostics_gTxt('preamble_hashdown').n.n.join(n,$hashdown) : '').
(($hashmod) ? n.smd_prognostics_gTxt('preamble_hashmod').n.n.join(n,$hashmod) : '').
(($nok) ? n.smd_prognostics_gTxt('preamble_nok').n.n.join(n,$nok) : '').
(($miss) ? n.n.smd_prognostics_gTxt('preamble_miss').n.n.join(n,$miss) : '').
(($added) ? n.n.smd_prognostics_gTxt('preamble_added').n.n.join(n,$added) : '').
n.n.''.smd_prognostics_gTxt('postamble').'';
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 = smd_prognostics_gTxt('subject_csi');
$hdrs = smd_prognostics_header_info('smd_frognostics');
$body = n.smd_prognostics_gTxt('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 mechanisms like SQL attacks?
exit(1);
}
}
// ---------- SQL injection detection
function smd_prognostics_sql_inject() {
global $smd_prognostics_sqlprot;
if($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);
// Send the forensics off if necessary
$to = get_pref('smd_prognostics_mailto_csi', '');
if ($to && $send) {
$subject = smd_prognostics_gTxt('subject_csi');
$hdrs = smd_prognostics_header_info('smd_frognostics');
$body = n.smd_prognostics_gTxt('preamble_sql_inject').n;
$body .= 'TXP: ' . txp_version .n. 'PHP: ' . phpversion() .n. 'MySQL: ' . mysql_get_server_info() .n. ((is_callable('apache_get_version')) ? 'Apache: ' . apache_get_version().n : '');
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;
$submit = gps('submit');
$ignore = gps('ignore');
$csi = gps('csi');
$ack = gps('selected');
$smd_prog_ack = (is_array($ack)) ? $ack : (($ack) ? array($ack) : array());
$out = array();
if ($submit == 'Acknowledge' || $ignore == '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 == '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_exists($file) && $file != $smd_prognostics_checksums) {
$content = smd_prognostics_prep_file($file);
$out[] = $file.': ('.( ($ignore == 'Ignore') ? 'NULL' : md5($content) ).')';
}
}
$fh = fopen($smd_prognostics_checksums, "w");
fwrite($fh, join(n, $out));
fclose($fh);
smd_prognostics_self_hash();
$msg = smd_prognostics_gTxt('acked');
}
}
if ($csi && $smd_prog_ack) {
$msg = smd_prognostics_gTxt('csi_sent');
} else if ($csi) {
$msg = smd_prognostics_gTxt('none_selected');
}
pagetop(smd_prognostics_gTxt('smd_prognostics'), $msg);
extract(smd_prognostics_buttons());
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';
echo $btnCSS.startTable('list');
echo tr(tda(strong(smd_prognostics_gTxt('ttl_ack')), ' colspan="3"') . tda($btnSetup.$btnFiles.$btnAdvice, $btnStyle) );
echo '';
echo endTable();
if ($csi && $forensics) {
// Send the forensics off
$to = get_pref('smd_prognostics_mailto_csi', '');
if ($to) {
$subject = smd_prognostics_gTxt('subject_csi');
$hdrs = smd_prognostics_header_info('smd_frognostics');
$body =
n.'TXP: ' . txp_version .n. 'PHP: ' . phpversion() .n. 'MySQL: ' . mysql_get_server_info() .n. ((is_callable('apache_get_version')) ? 'Apache: ' . apache_get_version().n : '').
((isset($forensics['nok'])) ? n.smd_prognostics_gTxt('preamble_nok').n.join(n,$forensics['nok']) : '').
((isset($forensics['miss'])) ? n.n.smd_prognostics_gTxt('preamble_miss').n.join(n,$forensics['miss']) : '').
((isset($forensics['added'])) ? n.n.smd_prognostics_gTxt('preamble_added').n.join(n,$forensics['added']) : '').
((isset($forensics['txp_log'])) ? n.n.smd_prognostics_gTxt('preamble_txplog').n.join(n,$forensics['txp_log']) : n.n.smd_prognostics_gTxt('no_log_entries'));
if (isset($forensics['files'])) {
$body .= n.n.smd_prognostics_gTxt('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 == 'Save') {
$outfile = array();
foreach ($smd_prognostics_files as $file) {
$content = smd_prognostics_prep_file($file);
$outfile[] = $file.': ('.md5($content).')';
}
$fh = fopen($smd_prognostics_checksums, "w");
fwrite($fh, join(n, $outfile));
if ($adds) {
$additions = array_diff($filelist, $smd_prognostics_files);
$added = array();
foreach ($additions as $addition) {
$added[] = $addition.': (NULL)';
}
fwrite($fh, n.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 = smd_prognostics_gTxt('files_updated');
}
pagetop(smd_prognostics_gTxt('smd_prognostics'), $msg);
extract(smd_prognostics_buttons());
$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 = (isset($filesel) && $filesel);
echo $btnCSS.startTable('list');
echo tr(tda(strong(smd_prognostics_gTxt('ttl_files'))) . tda($btnSetup.$btnAck.$btnAdvice, $btnStyle) );
echo tr(tdcs(smd_prognostics_gTxt('currmon', array('{curr}' => $moncount, '{outof}' => $allcount)) .(($showfiles) ? br.br. smd_prognostics_gTxt('monfiles_explain') : ''), 1, 400));
echo '';
echo endTable();
}
// ---------- Setup / prefs
function smd_prognostics_setup($msg='') {
global $smd_prognostics_event, $smd_prognostics_checksums, $prefs;
pagetop(smd_prognostics_gTxt('smd_prognostics'), $msg);
$origloc = get_pref('smd_prognostics_listloc', realpath(txpath.DS.'..'.DS).DS, 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_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' => smd_prognostics_gTxt('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 (!in_array($throw[0], $throw_codes) ) {
$throw[0] = '400';
}
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_dir, DS);
if ($submit == 'Save') {
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
$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($file, rtrim($smd_prognostics_dir, DS).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) {
$warnloc = smd_prognostics_gTxt('warn_loc');
}
}
$smd_prognostics_dir = get_pref('smd_prognostics_dir', txpath, 1);
$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', 10, 1);
$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', 86400, 1);
$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).DS, 1);
$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_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);
extract(smd_prognostics_buttons());
$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&step=plugin_help&name=$smd_prognostics_event#smd_setup";
echo $btnCSS.startTable('list', '', 'smd_prognostics_setup');
echo tr(tdcs(strong(smd_prognostics_gTxt('ttl_setup')).sp.smd_prognostics_gTxt('help_link', array('{link}' => $helpLink)), 2) . tda($btnFiles.$btnAck.$btnAdvice, $btnStyle) );
echo '';
echo endTable();
}
// ---------- Security advice
function smd_prognostics_advice($msg='') {
global $smd_prognostics_event, $smd_prognostics_style, $prefs;
require_once txpath.'/lib/IXRClass.php';
pagetop(smd_prognostics_gTxt('smd_prognostics'), $msg);
extract(smd_prognostics_buttons());
$checks = array();
// Prognostics dir in docroot?
if (strpos(get_pref('smd_prognostics_dir', txpath), $prefs['path_to_site']) !== false) {
$checks[] = smd_prognostics_gTxt('docroot_prognostics');
}
// Setup still exists?
if (@is_dir(txpath . DS. 'setup')) {
$checks[] = smd_prognostics_gTxt('setup_exists');
}
// New TXP version/branch available? Adapted from txplib_update.php
$client = new IXR_Client('http://rpc.textpattern.com');
$uid = get_pref('blog_uid', md5(uniqid(mt_rand(),true)));
if ($client->query('tups.getTXPVersion',$uid)) {
$response = $client->getResponse();
if (is_array($response)) {
ksort($response);
$version = get_pref('version', '');
$lversion = explode('.',$version);
$branch = substr($version,0,3);
foreach ($response as $key => $val) {
$rversion = explode('.',$val);
if ($key == 'txp_current_version_'.$branch) {
if (isset($lversion[2]) && isset($rversion[2]) && (intval($rversion[2])>intval($lversion[2]))) {
$most_recent = smd_prognostics_gTxt('avail_branch').': '.$val;
}
} else {
if (intval($rversion[0])>intval($lversion[0]) || intval($rversion[1])>intval($lversion[1])) {
$most_recent = smd_prognostics_gTxt('avail_txp').': '.$val;
}
}
}
if (isset($most_recent)) {
$checks[] = $most_recent;
}
}
}
// Files dir in docroot?
if (strpos($prefs['file_base_path'], $prefs['path_to_site']) !== false) {
$checks[] = smd_prognostics_gTxt('docroot_files');
}
// Tmp dir in docroot?
if (strpos($prefs['tempdir'], $prefs['path_to_site']) !== false) {
$checks[] = smd_prognostics_gTxt('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[] = smd_prognostics_gTxt('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 (mysql_error() == '') {
$checks[] = smd_prognostics_gTxt('sql_file_privs');
if (is_file($randfile)) {
@unlink($randfile);
}
}
echo ''.startTable('list', '', 'smd_prog_advice');
echo tr(tda(strong(smd_prognostics_gTxt('ttl_advice'))) . tda($btnSetup.$btnFiles.$btnAck, $btnStyle) );
if ($checks) {
foreach($checks as $check) {
echo tr(tda($check));
}
} else {
smd_prognostics_gTxt('tight');
}
echo endTable();
}
// ---------- 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', '');
$smd_prognostics_excludir = do_list(get_pref('smd_prognostics_excludir', ''));
$smd_prognostics_ignores = get_pref('smd_prognostics_ignores', '');
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($filelist, smd_prognostics_rglob("*", GLOB_MARK, $loc, $smd_prognostics_excludir, $smd_prognostics_ignores), smd_prognostics_rglob(".*", GLOB_MARK, $loc, $smd_prognostics_excludir, $smd_prognostics_ignores));
}
}
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($content, 0, 1536000); // First 1.5KB
if (strpos($part, 0x00) || (strpos($part, 0x0D) !== false) || (strpos($part, 0x0A) !== false)) {
// Likely a binary file: leave it be
$dostrip = false;
}
}
if ($dostrip) {
$content = strtr( $content, array("\r\n" => "\n", "\$HeadURL: http:" => "\$HeadURL: https:") );
}
return $content;
}
// Frankensteined from http://snipplr.com/view/16233/recursive-glob/
function smd_prognostics_rglob($pattern, $flags=0, $path='', $excl=array(), $ign='') {
global $smd_prognostics_checksums;
// 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[] = strtr( preg_quote($item), array("\*" => ".*", "\?" => ".") );
unset($ignarr[$idx]); // Remove the wildcard filename from the ignore list
}
}
$regexclude = ($regarr) ? '/^(' . join('|', $regarr) . ')$/' : '';
$permexclude = array_merge(array(basename($smd_prognostics_checksums)), $ignarr);
if (!$path && ($dir = dirname($pattern)) != '.') {
if ($dir == '\\' || $dir == DS) $dir = '';
return smd_prognostics_rglob(basename($pattern), $flags, $dir . DS, $excl, $ign);
}
$paths = glob($path . '*', GLOB_ONLYDIR | GLOB_NOSORT);
$files = glob($path . $pattern, $flags);
foreach ($paths as $p) {
$pinfo = array_pop(explode(DS, $p));
if (!in_array($pinfo, $excl)) {
$files = array_merge($files, smd_prognostics_rglob($pattern, $flags, $p . DS, $excl, $ign));
foreach($files as $idx => $theFile) {
$fex = array_pop(explode(DS, $theFile));
$rex = ($regexclude) ? preg_match($regexclude, $fex) : false;
if($fex=='' || in_array($fex, $permexclude) || $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 . smd_prognostics_gTxt('stat_name') . $file;
$out .= $sep . smd_prognostics_gTxt('stat_size') . $fi['size'];
$out .= $sep . smd_prognostics_gTxt('stat_mod') . strftime($dform, $fi['mtime']);
$out .= $sep . smd_prognostics_gTxt('stat_uid') . $fi['uid'];
$out .= $sep . smd_prognostics_gTxt('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_windows() ? "\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;
}
// Common buttons
function smd_prognostics_buttons() {
global $smd_prognostics_event, $smd_prognostics_style;
$ret = array (
'btnSave' => fInput('submit', 'submit', gTxt('save'), 'publish'),
'btnAck' => '',
'btnAckIt' => fInput('submit', 'submit', smd_prognostics_gTxt('pnl_ackit'), 'publish'),
'btnIgnore' => fInput('submit', 'ignore', smd_prognostics_gTxt('pnl_ignore'), 'publish'),
'btnCSI' => fInput('submit', 'csi', smd_prognostics_gTxt('pnl_csi'), 'publish'),
'btnAdvice' => '',
'btnFiles' => '',
'btnSetup' => '',
'btnStyle' => ' class="smd_prog_btns" style="border:0;height:25px;"',
'btnCSS' => '',
);
return $ret;
}
// Multi-file dropdown selection
function smd_prognostics_multisel($selname='', $tree=array(), $sel=array()) {
$out[] = '';
return join('',$out);
}
// Password strength meter
function smd_prognostics_chronostrength($evt, $stp) {
global $event, $smd_prognostics_style;
echo <<{$smd_prognostics_style['meter']}
EOS;
}
//****************************************************************
// 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;
public function smd_prog_PhProtector($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
}
}
/*
* Main function to be called in a index page that redirects to other pages
*
*/
public function isMalicious() {
$sqli = false;
$num_bad_words1 = $this->CheckGet();
$num_bad_words2 = $this->CheckPost();
if ($num_bad_words1 > 0) {
$sqli = true;
}
if ($num_bad_words2 > 0) {
$sqli = true;
}
return $sqli;
}
//check for sql injection and XSS in Post variables
private function CheckPost() {
$num_bad_words = 0;
foreach($_POST as $campo => $input) {
$_POST[$campo]= doArray($_POST[$campo], 'htmlentities'); // XSS PROTECTION
$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){
$_GET[$campo]= doArray($_GET[$campo], 'htmlentities'); // XSS PROTECTION
if($this->isIdInjection($campo,$input)){ //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;
/*
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."/";
if (preg_match ($expression, strtolower($input))) {
//dmp('*WA*',$badword, $input);
//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."/";
if (preg_match ($expression, strtolower($input))) {
//dmp('*BW*',$badword, $input);
//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."/";
if (preg_match ($expression, strtolower($input))) {
//dmp('*B-W-A*',$badword, $input);
//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
// ------------------------
// Plugin-specific replacement strings - localise as required
// TODO: Textpack this
function smd_prognostics_gTxt($what, $atts = array()) {
global $prefs, $smd_prognostics_event;
$lang = array(
'acked' => 'Alarms acknowledged.',
'alarm_freq' => 'Alarm on detection and every: ',
'and' => ' and ',
'auth_users' => 'Restrict prognostic config to: ',
'avail_branch' => 'An update to this Textpattern series is available',
'avail_txp' => 'A new version of Textpattern is available',
'block' => 'Block',
'ch_add' => 'Additions',
'ch_del' => 'Deletions',
'ch_mod' => 'Modifications',
'check_between' => 'Check files between: ',
'check_for' => 'Check files for: ',
'check_freq' => 'Check files (at most) every: ',
'check_qty' => 'Check this many files each time: ',
'check_where' => 'Check files on public side clicks: ',
'csi' => 'Gather forensics and send for analysis',
'csi_sent' => ' Forensics data sent',
'csv' => '(comma-separated)',
'currmon' => 'You are currently monitoring {curr} out of {outof} available files.',
'docroot_files' => 'Consider moving the "files" folder outside of your site root folder. You can do this via the "Admin > Prefs > Advanced > File directory path" setting.',
'docroot_prognostics' => 'Please change the "Prognostics folder" setting to a directory outside your site root folder.',
'docroot_tmp' => 'Consider moving the "tmp" folder outside of your site root folder. You can do this via the "Admin > Prefs > Advanced > Temporary directory path" setting.',
'excludir' => 'Exclude folders: ',
'files_updated' => 'File list updated',
'help_link' => '(Help)',
'hms' => '(hrs:mins:secs)',
'ignores' => 'Ignore files: ',
'lbl_changed' => 'Changed',
'lbl_hashmod' => 'COMPROMISED',
'lbl_missing' => 'Missing',
'lbl_added' => 'Added (not monitored)',
'listloc' => 'File locations: ',
'mailto' => 'Send e-mail to: ',
'mailto_csi' => 'Send forensics to: ',
'monfiles_explain' => 'Select all the files you wish to monitor and click Save. Altering this list will automatically acknowledge any outstanding alarms against the selected files.',
'no_alarms' => 'No alarms to acknowledge at present.',
'no_files' => 'No files to list. Check the plugin setup (and Save it).',
'no_log_entries' => 'No log activity around the time of the differences.',
'none_selected' => 'No files selected',
'notify_email' => 'E-mail',
'notify_txp' => 'TXP interface',
'notify_via' => 'Notify via: ',
'preamble_added' => 'These files have been added: ',
'preamble_files' => 'File contents follows: ',
'preamble_hashdown' => 'WARNING! The checksums file is missing.',
'preamble_hashmod' => 'WARNING! The checksums file has been altered: ',
'preamble_miss' => 'These files are missing: ',
'preamble_nok' => 'These files differ from their expected content: ',
'preamble_sql_inject' => 'Possible SQL injection detected',
'preamble_txplog' => 'Log entries prior to this detection: ',
'progloc' => 'Prognostics folder: ',
'progpfx' => 'Unique prefix: ',
'pnl_ack' => 'Alarms',
'pnl_ackit' => 'Acknowledge',
'pnl_advice' => 'Advice',
'pnl_csi' => 'Send Forensics',
'pnl_files' => 'Files',
'pnl_ignore' => 'Ignore',
'pnl_setup' => 'Setup',
'postamble' => 'Acknowledge alarms',
'req_headers' => 'Block request headers: ',
'req_not_allowed' => 'REQUEST_METHOD not allowed: {req}',
'rh_del' => 'DELETE',
'rh_put' => 'PUT',
'rh_trace' => 'TRACE',
'ttl_ack' => 'Prognostic alarm acknowledgement',
'ttl_advice' => 'Prognostic advice',
'ttl_files' => 'Prognostic file monitoring',
'ttl_fileopts' => 'Filesystem options',
'ttl_monopts' => 'Monitoring options',
'ttl_plugopts' => 'Plugin options',
'ttl_realopts' => 'Realtime options',
'ttl_setup' => 'Prognostic setup',
'smd_prognostics' => 'Prognostics',
'rt_hdr' => 'Header violations',
'rt_sql' => 'SQL injections',
'seconds' => '(seconds)',
'send_forensics' => 'Send forensics for: ',
'setup_exists' => 'The setup directory still exists. Please delete it.',
'show_grants' => 'Your TXP user can access the MySQL privileges in the DB. Consider accessing the DB using a less privileged user.',
'sql_file_privs' => 'Your MySQL user has FILE privileges. Consider revoking this right unless absolutely necessary.',
'sql_inject' => 'Protect against SQL injection: ',
'stat_gid' => 'Group ID: ',
'stat_mod' => 'Modified: ',
'stat_name' => 'Filename: ',
'stat_size' => 'Size: ',
'stat_uid' => 'User ID: ',
'subject' => 'Prognostics ('.$prefs['siteurl'].')',
'subject_csi' => 'Prognostics ('.$prefs['siteurl'].') forensics',
'tight' => 'You run a pretty tight ship. Stay frosty.',
'txpdir' => 'Admin-side URL: ',
'warn_loc' => 'File locations have changed. Ensure you update your list of files now',
'with' => ' with: ',
);
return strtr($lang[$what], $atts);
}