/**
* 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 http://stefdawson.com/
*/
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, $step;
$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 Admin->Users or Admin->User Manager's Edit step for all users.
// Since the $step is sometimes empty (if smd_um is being used to self-edit a low-priv user, for example)
// we need to also add the meter in this case. The side-effect is that the meter code also appears on
// the smd_um user list page, but it can't be helped.
if ( $event == 'admin' || ($event == 'smd_um' && (in_array($step, array('', 'smd_um_edit', 'smd_um_save', 'smd_um_save_new', 'smd_um_change_pass')) )) ) {
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) {
$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;
$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 || ( ($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==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') {
// 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 < 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('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 = '
'.$subject.'
'.
(($hashdown) ? '
'.smd_prognostics_gTxt('smd_prognostics_preamble_hashdown').'
' : '').
(($hashmod) ? '
'.smd_prognostics_gTxt('smd_prognostics_preamble_hashmod').'
' : '').
(($nok) ? '
'.smd_prognostics_gTxt('smd_prognostics_preamble_nok').'
' : '').
(($miss) ? '
'.smd_prognostics_gTxt('smd_prognostics_preamble_miss').'
' : '').
(($added) ? '
'.smd_prognostics_gTxt('smd_prognostics_preamble_added').'
' : '').
'
'.
smd_prognostics_gTxt('smd_prognostics_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('smd_prognostics_preamble_hashdown').n.n.join(n,$hashdown) : '').
(($hashmod) ? n.smd_prognostics_gTxt('smd_prognostics_preamble_hashmod').n.n.join(n,$hashmod) : '').
(($nok) ? n.smd_prognostics_gTxt('smd_prognostics_preamble_nok').n.n.join(n,$nok) : '').
(($miss) ? n.n.smd_prognostics_gTxt('smd_prognostics_preamble_miss').n.n.join(n,$miss) : '').
(($added) ? n.n.smd_prognostics_gTxt('smd_prognostics_preamble_added').n.n.join(n,$added) : '').
n.n.''.smd_prognostics_gTxt('smd_prognostics_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('smd_prognostics_subject_csi');
$hdrs = smd_prognostics_header_info('smd_frognostics');
$body = n.smd_prognostics_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;
// 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') ? 1 : 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 = smd_prognostics_gTxt('smd_prognostics_subject_csi');
$hdrs = smd_prognostics_header_info('smd_frognostics');
$body = n.smd_prognostics_gTxt('smd_prognostics_preamble_sql_inject').n;
$body .= (($ver) ? 'Txp: ' . $ver .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 || $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($fh, join(n, $out));
fclose($fh);
smd_prognostics_self_hash();
$msg = smd_prognostics_gTxt('smd_prognostics_acked');
}
}
if ($csi && $smd_prog_ack) {
$msg = smd_prognostics_gTxt('smd_prognostics_csi_sent');
} else if ($csi) {
$msg = smd_prognostics_gTxt('smd_prognostics_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('smd_prognostics_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('smd_prognostics_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('smd_prognostics_preamble_nok').n.join(n,$forensics['nok']) : '').
((isset($forensics['miss'])) ? n.n.smd_prognostics_gTxt('smd_prognostics_preamble_miss').n.join(n,$forensics['miss']) : '').
((isset($forensics['added'])) ? n.n.smd_prognostics_gTxt('smd_prognostics_preamble_added').n.join(n,$forensics['added']) : '').
((isset($forensics['txp_log'])) ? n.n.smd_prognostics_gTxt('smd_prognostics_preamble_txplog').n.join(n,$forensics['txp_log']) : n.n.smd_prognostics_gTxt('smd_prognostics_no_log_entries'));
if (isset($forensics['files'])) {
$body .= n.n.smd_prognostics_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).')';
}
$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('smd_prognostics_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('smd_prognostics_ttl_files'))) . tda($btnSetup.$btnAck.$btnAdvice, $btnStyle) );
echo tr(tdcs(smd_prognostics_gTxt('smd_prognostics_currmon', array('{curr}' => $moncount, '{outof}' => $allcount)) .(($showfiles) ? br.br. smd_prognostics_gTxt('smd_prognostics_monfiles_explain') : ''), 1, 400));
echo '';
echo endTable();
}
// ---------- 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).DS, 1);
$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' => smd_prognostics_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_dir, DS);
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(smd_prognostics_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($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) || ($smd_prognostics_excludir != $origexc) ) {
$msg = array(smd_prognostics_gTxt('smd_prognostics_warn_loc'), E_WARNING);
}
if (!$msg) {
$msg = gTxt('preferences_saved');
}
}
pagetop(smd_prognostics_gTxt('smd_prognostics'), $msg);
$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_inject_sensitivity = get_pref('smd_prognostics_inject_sensitivity', 1, 1);
$smd_prognostics_xss = get_pref('smd_prognostics_xss', 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('smd_prognostics_ttl_setup')).sp.smd_prognostics_gTxt('smd_prognostics_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('smd_prognostics_docroot_prognostics');
}
// Setup still exists?
if (@is_dir(txpath . DS. 'setup')) {
$checks[] = smd_prognostics_gTxt('smd_prognostics_setup_exists');
}
// RPC still exists?
if ( ($prefs['enable_xmlrpc_server'] == 0) && (@is_dir(realpath(txpath.DS.'..'.DS. 'rpc'))) ) {
$checks[] = smd_prognostics_gTxt('smd_prognostics_rpc_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('smd_prognostics_avail_branch').': '.$val;
}
} else {
if (intval($rversion[0])>intval($lversion[0]) || intval($rversion[1])>intval($lversion[1])) {
$most_recent = smd_prognostics_gTxt('smd_prognostics_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('smd_prognostics_docroot_files');
}
// Tmp dir in docroot?
if (strpos($prefs['tempdir'], $prefs['path_to_site']) !== false) {
$checks[] = smd_prognostics_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[] = smd_prognostics_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 (mysql_error() == '') {
$checks[] = smd_prognostics_gTxt('smd_prognostics_sql_file_privs');
if (is_file($randfile)) {
@unlink($randfile);
}
}
echo ''.startTable('list', '', 'smd_prog_advice');
echo tr(tda(strong(smd_prognostics_gTxt('smd_prognostics_ttl_advice'))) . tda($btnSetup.$btnFiles.$btnAck, $btnStyle) );
if ($checks) {
foreach($checks as $check) {
echo tr(tda($check));
}
} else {
echo tr(tda(smd_prognostics_gTxt('smd_prognostics_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', '');
$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($filelist, smd_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($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 = 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) {
$pinfo = array_pop(explode(DS, $p));
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) {
$fex = array_pop(explode(DS, $theFile));
$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 . smd_prognostics_gTxt('smd_prognostics_stat_name') . $file;
$out .= $sep . smd_prognostics_gTxt('smd_prognostics_stat_size') . $fi['size'];
$out .= $sep . smd_prognostics_gTxt('smd_prognostics_stat_mod') . strftime($dform, $fi['mtime']);
$out .= $sep . smd_prognostics_gTxt('smd_prognostics_stat_uid') . $fi['uid'];
$out .= $sep . smd_prognostics_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_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('smd_prognostics_pnl_ackit'), 'publish'),
'btnIgnore' => fInput('submit', 'ignore', smd_prognostics_gTxt('smd_prognostics_pnl_ignore'), 'publish'),
'btnCSI' => fInput('submit', 'csi', smd_prognostics_gTxt('smd_prognostics_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;
var $do_xss;
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
}
$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(0, count($_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(0, count($_GET[$campo]), ENT_NOQUOTES) );
} else {
$_GET[$campo]= htmlentities($_GET[$campo], ENT_NOQUOTES);
}
}
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(
'en-gb' => array(
'smd_prognostics' => 'Prognostics',
'smd_prognostics_acked' => 'Alarms acknowledged.',
'smd_prognostics_alarm_freq' => 'Alarm on detection and every: ',
'smd_prognostics_and' => ' and ',
'smd_prognostics_auth_users' => 'Restrict prognostic config to: ',
'smd_prognostics_avail_branch' => 'An update to this Textpattern series is available',
'smd_prognostics_avail_txp' => 'A new version of Textpattern is available',
'smd_prognostics_block' => 'Block',
'smd_prognostics_ch_add' => 'Additions',
'smd_prognostics_ch_del' => 'Deletions',
'smd_prognostics_ch_mod' => 'Modifications',
'smd_prognostics_check_between' => 'Check files between: ',
'smd_prognostics_check_for' => 'Check files for: ',
'smd_prognostics_check_freq' => 'Check files (at most) every: ',
'smd_prognostics_check_qty' => 'Check this many files each time: ',
'smd_prognostics_check_where' => 'Check files on public side clicks: ',
'smd_prognostics_csense' => '(case-sensitive)',
'smd_prognostics_csi' => 'Gather forensics and send for analysis',
'smd_prognostics_csi_sent' => ' Forensics data sent',
'smd_prognostics_csv' => '(comma-separated)',
'smd_prognostics_currmon' => 'You are currently monitoring {curr} out of {outof} available files.',
'smd_prognostics_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.',
'smd_prognostics_docroot_prognostics' => 'Please change the "Prognostics folder" setting to a directory outside your site root folder.',
'smd_prognostics_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.',
'smd_prognostics_excludir' => 'Exclude folders: ',
'smd_prognostics_files_updated' => 'File list updated',
'smd_prognostics_help_link' => '(Help)',
'smd_prognostics_hms' => '(hrs:mins:secs)',
'smd_prognostics_ignores' => 'Ignore files: ',
'smd_prognostics_lbl_changed' => 'Changed',
'smd_prognostics_lbl_hashmod' => 'COMPROMISED',
'smd_prognostics_lbl_missing' => 'Missing',
'smd_prognostics_lbl_added' => 'Added (not monitored)',
'smd_prognostics_listloc' => 'File locations: ',
'smd_prognostics_mailto' => 'Send e-mail to: ',
'smd_prognostics_mailto_csi' => 'Send forensics to: ',
'smd_prognostics_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.',
'smd_prognostics_no_alarms' => 'No alarms to acknowledge at present.',
'smd_prognostics_no_files' => 'No files to list. Check the plugin setup (and Save it).',
'smd_prognostics_no_log_entries' => 'No log activity around the time of the differences.',
'smd_prognostics_none_selected' => 'No files selected',
'smd_prognostics_not_writable' => '{location} is not a directory or is not writable',
'smd_prognostics_notify_email' => 'E-mail',
'smd_prognostics_notify_txp' => 'Txp interface',
'smd_prognostics_notify_via' => 'Notify via: ',
'smd_prognostics_preamble_added' => 'These files have been added: ',
'smd_prognostics_preamble_files' => 'File contents follows: ',
'smd_prognostics_preamble_hashdown' => 'WARNING! The checksums file is missing.',
'smd_prognostics_preamble_hashmod' => 'WARNING! The checksums file has been altered: ',
'smd_prognostics_preamble_miss' => 'These files are missing: ',
'smd_prognostics_preamble_nok' => 'These files differ from their expected content: ',
'smd_prognostics_preamble_sql_inject' => 'Possible SQL injection detected',
'smd_prognostics_preamble_txplog' => 'Log entries prior to this detection: ',
'smd_prognostics_progloc' => 'Prognostics folder: ',
'smd_prognostics_progpfx' => 'Unique prefix: ',
'smd_prognostics_pnl_ack' => 'Alarms',
'smd_prognostics_pnl_ackit' => 'Acknowledge',
'smd_prognostics_pnl_advice' => 'Advice',
'smd_prognostics_pnl_csi' => 'Send Forensics',
'smd_prognostics_pnl_files' => 'Files',
'smd_prognostics_pnl_ignore' => 'Ignore',
'smd_prognostics_pnl_setup' => 'Setup',
'smd_prognostics_postamble' => 'Acknowledge alarms',
'smd_prognostics_req_headers' => 'Block request headers: ',
'smd_prognostics_req_not_allowed' => 'REQUEST_METHOD not allowed: {req}',
'smd_prognostics_rh_del' => 'DELETE',
'smd_prognostics_rh_put' => 'PUT',
'smd_prognostics_rh_trace' => 'TRACE',
'smd_prognostics_ttl_ack' => 'Prognostic alarm acknowledgement',
'smd_prognostics_ttl_advice' => 'Prognostic advice',
'smd_prognostics_ttl_files' => 'Prognostic file monitoring',
'smd_prognostics_ttl_fileopts' => 'Filesystem options',
'smd_prognostics_ttl_monopts' => 'Monitoring options',
'smd_prognostics_ttl_plugopts' => 'Plugin options',
'smd_prognostics_ttl_realopts' => 'Realtime options',
'smd_prognostics_ttl_setup' => 'Prognostic setup',
'smd_prognostics_rpc_exists' => 'The rpc directory is not being used. Consider removing it.',
'smd_prognostics_rt_hdr' => 'Header violations',
'smd_prognostics_rt_sql' => 'SQL injections',
'smd_prognostics_seconds' => '(seconds)',
'smd_prognostics_send_forensics' => 'Send forensics for: ',
'smd_prognostics_sensitivity' => 'Sensitivity threshold: ',
'smd_prognostics_sensitivity_explain' => '(1=most sensitive)',
'smd_prognostics_setup_exists' => 'The setup directory still exists. Please delete it.',
'smd_prognostics_show_grants' => 'Your Txp user can access the MySQL privileges in the DB. Consider accessing the DB using a less privileged user.',
'smd_prognostics_sql_file_privs' => 'Your MySQL user has FILE privileges. Consider revoking this right unless absolutely necessary.',
'smd_prognostics_sql_inject' => 'Protect against SQL injection: ',
'smd_prognostics_stat_gid' => 'Group ID: ',
'smd_prognostics_stat_mod' => 'Modified: ',
'smd_prognostics_stat_name' => 'Filename: ',
'smd_prognostics_stat_size' => 'Size: ',
'smd_prognostics_stat_uid' => 'User ID: ',
'smd_prognostics_subject' => 'Prognostics ('.$prefs['siteurl'].')',
'smd_prognostics_subject_csi' => 'Prognostics ('.$prefs['siteurl'].') forensics',
'smd_prognostics_tight' => 'You run a pretty tight ship. Stay frosty.',
'smd_prognostics_txpdir' => 'Admin-side URL: ',
'smd_prognostics_warn_loc' => 'File locations changed. Update your list of files now',
'smd_prognostics_with' => ' with: ',
'smd_prognostics_xss' => 'XSS shield',
'smd_prognostics_xss_explain' => '(may break Textiled comments)',
),
);
$thislang = get_pref('language', 'en-gb');
$thislang = (isset($lang[$thislang][$what])) ? $thislang : 'en-gb';
return strtr($lang[$thislang][$what], $atts);
}