Merging Nighthawk (anti-spam work) and Scribus (AJAX work + debugging + CLI installer) branches
--- a/includes/clientside/static/comments.js Sun Jan 25 20:35:32 2009 -0500
+++ b/includes/clientside/static/comments.js Sun Jan 25 21:21:07 2009 -0500
@@ -54,7 +54,8 @@
materializeComment(response);
break;
case 'error':
- new MessageBox(MB_OK|MB_ICONSTOP, ( response.title ? response.title : 'Error fetching comment data' ), response.error);
+ load_component(['messagebox', 'fadefilter', 'flyin']);
+ new MessageBox(MB_OK|MB_ICONSTOP, ( response.title ? response.title : $lang.get('comment_ajax_err_generic_title') ), response.error);
break;
default:
alert(ajax.responseText);
@@ -180,8 +181,10 @@
tplvars.DATA = this_comment.comment_data;
tplvars.SIGNATURE = this_comment.signature;
- if ( this_comment.approved != '1' )
+ if ( this_comment.approved == '0' )
tplvars.SUBJECT += ' <span style="color: #D84308">' + $lang.get('comment_msg_note_unapp') + '</span>';
+ else if ( this_comment.approved == '2' )
+ tplvars.SUBJECT += ' <span style="color: #D84308">' + $lang.get('comment_msg_note_spam') + '</span>';
// Name
tplvars.NAME = this_comment.name;
--- a/includes/comment.php Sun Jan 25 20:35:32 2009 -0500
+++ b/includes/comment.php Sun Jan 25 21:21:07 2009 -0500
@@ -123,7 +123,7 @@
$count_total++;
( $row['approved'] == 1 ) ? $count_appr++ : $count_unappr++;
- if ( !$this->perms->get_permissions('mod_comments') && $row['approved'] == 0 )
+ if ( !$this->perms->get_permissions('mod_comments') && $row['approved'] != COMMENT_APPROVED )
continue;
// Localize the rank
@@ -142,7 +142,7 @@
<div id="posthide_'.$seed.'" style="display: none;">
' . $row['comment_data'] . '
</div>
- <p><span style="opacity: 0.4; filter: alpha(opacity=40);">Post from foe hidden.</span> <span style="text-align: right;"><a href="#showpost" onclick="document.getElementById(\'posthide_'.$seed.'\').style.display=\'block\'; this.parentNode.parentNode.parentNode.removeChild(this.parentNode.parentNode); return false;">Display post</a></span></p>
+ <p><span style="opacity: 0.4; filter: alpha(opacity=40);">' . $lang->get('comment_msg_foe_comment_hidden') . '</span> <span style="text-align: right;"><a href="#showpost" onclick="document.getElementById(\'posthide_'.$seed.'\').style.display=\'block\'; this.parentNode.parentNode.parentNode.removeChild(this.parentNode.parentNode); return false;">' . $lang->get('comment_btn_display_foe_comment') . '</a></span></p>
';
$row['comment_data'] = $wrapper;
}
@@ -193,7 +193,7 @@
break;
case 'edit':
$cid = (string)$data['id'];
- if ( !preg_match('#^([0-9]+)$#i', $cid) || intval($cid) < 1 )
+ if ( !ctype_digit($cid) || intval($cid) < 1 )
{
echo '{"mode":"error","error":"HACKING ATTEMPT"}';
return false;
@@ -228,7 +228,7 @@
break;
case 'delete':
$cid = (string)$data['id'];
- if ( !preg_match('#^([0-9]+)$#i', $cid) || intval($cid) < 1 )
+ if ( !ctype_digit($cid) || intval($cid) < 1 )
{
echo '{"mode":"error","error":"HACKING ATTEMPT"}';
return false;
@@ -266,17 +266,28 @@
// Guest authorization
if ( getConfig('comments_need_login') == '2' && !$session->user_logged_in )
- $errors[] = 'You need to log in before posting comments.';
+ $errors[] = $lang->get('comment_err_need_login');
// CAPTCHA code
if ( getConfig('comments_need_login') == '1' && !$session->user_logged_in )
{
$real_code = $session->get_captcha($data['captcha_id']);
- if ( strtolower($real_code) != strtolower($data['captcha_code']) )
- $errors[] = 'The confirmation code you entered was incorrect.';
+ if ( strtolower($real_code) !== strtolower($data['captcha_code']) )
+ $errors[] = $lang->get('comment_err_captcha_wrong');
$session->kill_captcha();
}
+ // Spam check
+ $spam_policy = getConfig('comment_spam_policy', 'moderate');
+ $sc_name = ( $session->user_logged_in ) ? $session->username : $data['name'];
+ $sc_mail = ( $session->user_logged_in ) ? $session->email : false;
+ $sc_url = ( $session->user_logged_in ) ? $session->user_extra['user_homepage'] : false;
+ $spamcheck = $spam_policy === 'accept' ? true : spamalyze($data['text'], $sc_name, $sc_mail, $sc_url);
+ if ( !$spamcheck && $spam_policy === 'reject' )
+ {
+ $errors[] = $lang->get('comment_err_spamcheck_failed_rejected');
+ }
+
if ( count($errors) > 0 )
{
$ret = Array(
@@ -295,7 +306,9 @@
$src = $text;
$sql_text = $db->escape($text);
$text = RenderMan::render($text);
- $appr = ( getConfig('approve_comments') == '1' ) ? '0' : '1';
+ $appr = ( getConfig('approve_comments') == '1' ) ? COMMENT_UNAPPROVED : COMMENT_APPROVED;
+ if ( $appr === COMMENT_APPROVED && $spam_policy === 'moderate' && !$spamcheck )
+ $appr = COMMENT_SPAM;
$time = time();
$date = enano_date('F d, Y h:i a', $time);
$ip = $_SERVER['REMOTE_ADDR'];
@@ -358,7 +371,7 @@
}
$cid = (string)$data['id'];
- if ( !preg_match('#^([0-9]+)$#i', $cid) || intval($cid) < 1 )
+ if ( !ctype_digit($cid) || intval($cid) < 1 )
{
echo '{"mode":"error","error":"HACKING ATTEMPT"}';
return false;
--- a/includes/constants.php Sun Jan 25 20:35:32 2009 -0500
+++ b/includes/constants.php Sun Jan 25 21:21:07 2009 -0500
@@ -67,6 +67,11 @@
define('PAGE_GRP_NORMAL', 3);
define('PAGE_GRP_REGEX', 4);
+// Comment types
+define('COMMENT_APPROVED', 1);
+define('COMMENT_UNAPPROVED', 0);
+define('COMMENT_SPAM', 2);
+
// Session key types
// Short keys last for getConfig('session_short_time', '720'); in minutes and auto-renew.
// Long keys last for getConfig('session_remember_time', '30'); in days and do NOT auto-renew.
--- a/includes/functions.php Sun Jan 25 20:35:32 2009 -0500
+++ b/includes/functions.php Sun Jan 25 21:21:07 2009 -0500
@@ -1971,6 +1971,11 @@
// <
// The rule is so specific because everything else will have been filtered by now
$html = preg_replace('/<(script|iframe)(.+?)src=([^>]*)</i', '<\\1\\2src=\\3<', $html);
+
+ // Vulnerability reported by fuzion from nukeit.org:
+ // XSS in closing HTML tag style attribute
+ // Fix: escape all closing tags with non-whitelisted characters
+ $html = preg_replace('!</((?:.*)([^a-z0-9-_:]+)(?:.*))>!', '</\\1>', $html);
// Restore stripped comments
$i = 0;
@@ -2159,6 +2164,46 @@
}
/**
+ * Portal function allowing spam-filtering plugins.
+ * Hooking guide:
+ * - Attach to spam_check
+ * - Return either true or false - true if the message is spam-free, false if it fails your test
+ * @example
+ <code>
+ $plugins->attachHook('spam_check', 'return my_spam_check($string);');
+ function my_spam_check($string)
+ {
+ if ( stristr($string, 'viagra') )
+ return false;
+
+ return true;
+ }
+ </code>
+ * @param string String to check for spam
+ * @param string Author name
+ * @param string Author e-mail
+ * @param string Author website
+ * @param string Author IP
+ * @return bool
+ */
+
+function spamalyze($string, $name = false, $email = false, $url = false, $ip = false)
+{
+ global $db, $session, $paths, $template, $plugins; // Common objects
+ if ( !$ip )
+ $ip =& $_SERVER['REMOTE_ADDR'];
+
+ $code = $plugins->setHook('spam_check');
+ foreach ( $code as $cmd )
+ {
+ $result = eval($cmd);
+ if ( !$result )
+ return false;
+ }
+ return true;
+}
+
+/**
* Paginates (breaks into multiple pages) a MySQL result resource, which is treated as unbuffered.
* @param resource The MySQL result resource. This should preferably be an unbuffered query.
* @param string A template, with variables being named after the column name
--- a/includes/pageprocess.php Sun Jan 25 20:35:32 2009 -0500
+++ b/includes/pageprocess.php Sun Jan 25 21:21:07 2009 -0500
@@ -198,6 +198,11 @@
return false;
}
}
+ if ( $this->revision_id > 0 && !$this->perms->get_permissions('history_view') )
+ {
+ $this->err_access_denied();
+ return false;
+ }
// Is there a custom function registered for handling this namespace?
// DEPRECATED (even though it only saw its way into one alpha release.)
@@ -443,6 +448,13 @@
}
}
+ // Spam check
+ if ( !spamalyze($text) )
+ {
+ $this->raise_error($lang->get('editor_err_spamcheck_failed'));
+ return false;
+ }
+
//
// Protection validated; update page content
//
--- a/includes/pageutils.php Sun Jan 25 20:35:32 2009 -0500
+++ b/includes/pageutils.php Sun Jan 25 21:21:07 2009 -0500
@@ -694,7 +694,7 @@
$i++;
$strings = Array();
$bool = Array();
- if ( $session->get_permissions('mod_comments') || $row['approved'] )
+ if ( $session->get_permissions('mod_comments') || $row['approved'] == COMMENT_APPROVED )
{
$list .= $i . ' : { \'comment\' : unescape(\''.rawurlencode($row['comment_data']).'\'), \'name\' : unescape(\''.rawurlencode($row['name']).'\'), \'subject\' : unescape(\''.rawurlencode($row['subject']).'\'), }, ';
--- a/includes/plugins.php Sun Jan 25 20:35:32 2009 -0500
+++ b/includes/plugins.php Sun Jan 25 21:21:07 2009 -0500
@@ -121,8 +121,9 @@
* @param array Deprecated.
*/
- function setHook($name, $opts = Array()) {
- if(isset($this->hook_list[$name]) && is_array($this->hook_list[$name]))
+ function setHook($name, $opts = Array())
+ {
+ if ( !empty($this->hook_list[$name]) && is_array($this->hook_list[$name]) )
{
return array(implode("\n", $this->hook_list[$name]));
}
@@ -149,8 +150,9 @@
</code>
*/
- function attachHook($name, $code) {
- if(!isset($this->hook_list[$name]))
+ function attachHook($name, $code)
+ {
+ if ( !isset($this->hook_list[$name]) )
{
$this->hook_list[$name] = Array();
}
--- a/includes/sessions.php Sun Jan 25 20:35:32 2009 -0500
+++ b/includes/sessions.php Sun Jan 25 21:21:07 2009 -0500
@@ -3716,7 +3716,7 @@
// Fetch private key
$dh_public = $_POST['dh_public_key'];
- if ( !preg_match('/^[0-9]+$/', $dh_public) )
+ if ( !ctype_digit($dh_public) )
{
throw new Exception('ERR_DH_KEY_NOT_INTEGER');
}
@@ -3888,7 +3888,7 @@
$dh_hash = $req['dh_secret_hash'];
// Check the key
- if ( !preg_match('/^[0-9]+$/', $dh_public) || !preg_match('/^[0-9]+$/', $req['dh_client_key']) )
+ if ( !ctype_digit($dh_public) || !ctype_digit($req['dh_client_key']) )
{
return array(
'mode' => 'error',
--- a/includes/template.php Sun Jan 25 20:35:32 2009 -0500
+++ b/includes/template.php Sun Jan 25 21:21:07 2009 -0500
@@ -611,36 +611,30 @@
{
$db->_die();
}
- $nc = $db->numrows();
- $nu = 0;
- $na = 0;
+ $num_comments = $db->numrows();
+ $approval_counts = array(COMMENT_UNAPPROVED => 0, COMMENT_APPROVED => 0, COMMENT_SPAM => 0);
while ( $r = $db->fetchrow() )
{
- if ( !$r['approved'] )
- {
- $nu++;
- }
- else
- {
- $na++;
- }
+ $approval_counts[$r['approved']]++;
}
$db->free_result();
- $n = ( $session->check_acl_scope('mod_comments', $local_namespace) && $perms->get_permissions('mod_comments') ) ? (string)$nc : (string)$na;
- if ( $session->check_acl_scope('mod_comments', $local_namespace) && $perms->get_permissions('mod_comments') && $nu > 0 )
+ // $n = ( $session->check_acl_scope('mod_comments', $local_namespace) && $perms->get_permissions('mod_comments') ) ? (string)$num_comments : (string)$na;
+ if ( $session->check_acl_scope('mod_comments', $local_namespace) && $perms->get_permissions('mod_comments') && ( $approval_counts[COMMENT_UNAPPROVED] + $approval_counts[COMMENT_SPAM] ) > 0 )
{
$subst = array(
- 'num_comments' => $nc,
- 'num_unapp' => $nu
+ 'num_comments' => $num_comments,
+ 'num_app' => $approval_counts[COMMENT_APPROVED],
+ 'num_unapp' => $approval_counts[COMMENT_UNAPPROVED],
+ 'num_spam' => $approval_counts[COMMENT_SPAM]
);
$btn_text = $lang->get('onpage_btn_discussion_unapp', $subst);
}
else
{
$subst = array(
- 'num_comments' => $nc
+ 'num_comments' => $num_comments
);
$btn_text = $lang->get('onpage_btn_discussion', $subst);
}
--- a/index.php Sun Jan 25 20:35:32 2009 -0500
+++ b/index.php Sun Jan 25 21:21:07 2009 -0500
@@ -281,7 +281,7 @@
break;
case 'rollback':
$id = (isset($_GET['id'])) ? $_GET['id'] : false;
- if(!$id || !preg_match('#^([0-9]+)$#', $id)) die_friendly('Invalid action ID', '<p>The URL parameter "id" is not an integer. Exiting to prevent nasties like SQL injection, etc.</p>');
+ if(!$id || !ctype_digit($id)) die_friendly('Invalid action ID', '<p>The URL parameter "id" is not an integer. Exiting to prevent nasties like SQL injection, etc.</p>');
$id = intval($id);
--- a/language/english/admin.json Sun Jan 25 20:35:32 2009 -0500
+++ b/language/english/admin.json Sun Jan 25 21:21:07 2009 -0500
@@ -279,6 +279,11 @@
field_comment_allow_guests_yes: 'Yes',
field_comment_allow_guests_captcha: 'Require visual confirmation',
field_comment_allow_guests_no: 'No (require login)',
+ field_comment_spam_policy: 'Spam policy:',
+ field_comment_spam_policy_hint: 'This requres a <a href="http://enanocms.org/Category:Spam_filtering">spam filtering plugin</a> to be installed.',
+ field_comment_spam_policy_moderate: 'Moderate comments (default)',
+ field_comment_spam_policy_reject: 'Reject post',
+ field_comment_spam_policy_accept: 'Ignore and accept posts',
// Section: disable site
heading_disablesite: 'Disable all site access',
--- a/language/english/core.json Sun Jan 25 20:35:32 2009 -0500
+++ b/language/english/core.json Sun Jan 25 21:21:07 2009 -0500
@@ -211,12 +211,21 @@
msg_count_unapp_one: 'However, there is <span id="comment_count_unapp_inner">1</span> additional comment awaiting approval.',
msg_count_unapp_plural: 'However, there are <span id="comment_count_unapp_inner">%num_unapp%</span> additional comments awaiting approval.',
+ msg_foe_comment_hidden: 'Post from foe hidden.',
+ btn_display_foe_comment: 'Display post',
+
msg_note_unapp: '(Unapproved)',
+ msg_note_spam: '(Spam)',
msg_ip_address: 'IP address:',
msg_delete_confirm: 'Do you really want to delete this comment?',
+ err_captcha_wrong: 'The confirmation code you entered was incorrect.',
+ err_spamcheck_failed_rejected: 'Your comment was rejected because it appears to be spam.',
+ err_spamcheck_failed_flagged: 'Your comment was posted, but it appears to be spam and has been flagged as such for a moderator to review.',
+ ajax_err_generic_title: 'Error fetching comment data',
+
postform_title: 'Got something to say?',
postform_blurb: 'If you have comments or suggestions on this article, you can shout it out here.',
postform_blurb_unapp: 'Before your post will be visible to the public, a moderator will have to approve it.',
@@ -250,7 +259,7 @@
lbl_page_external: 'external page',
btn_discussion: 'discussion (%num_comments%)',
- btn_discussion_unapp: 'discussion (%num_comments% total, %num_unapp% unapp.)',
+ btn_discussion_unapp: '<span title="Approved: %num_app% | Unapproved: %num_unapp% | Spam: %num_spam%">discussion (%num_comments%) [!]</span>',
btn_edit: 'edit this page',
btn_viewsource: 'view source',
btn_history: 'history',
@@ -332,6 +341,7 @@
err_no_permission: 'You do not have permission to edit this page.',
err_page_protected: 'This page is protected, and you do not have permission to edit protected pages.',
err_captcha_wrong: 'The confirmation code you entered is incorrect.',
+ err_spamcheck_failed: 'Your edit was rejected because it looks like spam.',
msg_editor_heading: 'Editing page',
msg_saved: 'Your changes to this page have been saved.',
--- a/plugins/PrivateMessages.php Sun Jan 25 20:35:32 2009 -0500
+++ b/plugins/PrivateMessages.php Sun Jan 25 21:21:07 2009 -0500
@@ -60,7 +60,7 @@
break;
case 'View':
$id = $argv[1];
- if ( !preg_match('#^([0-9]+)$#', $id) )
+ if ( !ctype_digit($id) )
{
die_friendly('Message error', '<p>Invalid message ID</p>');
}
@@ -106,7 +106,7 @@
break;
case 'Move':
$id = $argv[1];
- if ( !preg_match('#^([0-9]+)$#', $id) )
+ if ( !ctype_digit($id) )
{
die_friendly('Message error', '<p>Invalid message ID</p>');
}
@@ -136,7 +136,7 @@
break;
case 'Delete':
$id = $argv[1];
- if ( !preg_match('#^([0-9]+)$#', $id) )
+ if ( !ctype_digit($id) )
{
die_friendly('Message error', '<p>Invalid message ID</p>');
}
@@ -365,7 +365,7 @@
break;
case 'Edit':
$id = $argv[1];
- if ( !preg_match('#^([0-9]+)$#', $id) )
+ if ( !ctype_digit($id) )
{
die_friendly('Message error', '<p>Invalid message ID</p>');
}
--- a/plugins/SpecialAdmin.php Sun Jan 25 20:35:32 2009 -0500
+++ b/plugins/SpecialAdmin.php Sun Jan 25 21:21:07 2009 -0500
@@ -304,6 +304,10 @@
if(isset($_POST['enable-comments'])) setConfig('enable_comments', '1');
else setConfig('enable_comments', '0');
setConfig('comments_need_login', $_POST['comments_need_login']);
+ if ( in_array($_POST['comment_spam_policy'], array('moderate', 'reject', 'accept')) )
+ {
+ setConfig('comment_spam_policy', $_POST['comment_spam_policy']);
+ }
// Powered by link
if ( isset($_POST['enano_powered_link']) ) setConfig('powered_btn', '1');
@@ -350,10 +354,10 @@
setConfig('register_tou', RenderMan::preprocess_text($_POST['register_tou'], true, false));
// Account lockout policy
- if ( preg_match('/^[0-9]+$/', $_POST['lockout_threshold']) )
+ if ( ctype_digit($_POST['lockout_threshold']) )
setConfig('lockout_threshold', $_POST['lockout_threshold']);
- if ( preg_match('/^[0-9]+$/', $_POST['lockout_duration']) )
+ if ( ctype_digit($_POST['lockout_duration']) )
setConfig('lockout_duration', $_POST['lockout_duration']);
if ( in_array($_POST['lockout_policy'], array('disable', 'captcha', 'lockout')) )
@@ -604,6 +608,27 @@
</label>
</td>
</tr>
+
+ <tr>
+ <td class="row2">
+ <?php echo $lang->get('acpgc_field_comment_spam_policy'); ?><br />
+ <small><?php echo $lang->get('acpgc_field_comment_spam_policy_hint'); ?></small>
+ </td>
+ <td class="row2">
+ <label>
+ <input name="comment_spam_policy" type="radio" value="moderate" <?php if ( getConfig('comment_spam_policy', 'moderate') == 'moderate' ) echo 'checked="checked"'; ?>/>
+ <?php echo $lang->get('acpgc_field_comment_spam_policy_moderate'); ?>
+ </label>
+ <label>
+ <input name="comment_spam_policy" type="radio" value="reject" <?php if ( getConfig('comment_spam_policy', 'moderate') == 'reject' ) echo 'checked="checked"'; ?>/>
+ <?php echo $lang->get('acpgc_field_comment_spam_policy_reject'); ?>
+ </label>
+ <label>
+ <input name="comment_spam_policy" type="radio" value="accept" <?php if ( getConfig('comment_spam_policy', 'moderate') == 'accept' ) echo 'checked="checked"'; ?>/>
+ <?php echo $lang->get('acpgc_field_comment_spam_policy_accept'); ?>
+ </label>
+ </td>
+ </tr>
<!-- Site disablement -->
--- a/plugins/admin/LangManager.php Sun Jan 25 20:35:32 2009 -0500
+++ b/plugins/admin/LangManager.php Sun Jan 25 21:21:07 2009 -0500
@@ -47,7 +47,7 @@
// Is this parameter in the form of an integer?
// (designed to ease validation later)
- if ( preg_match('/^[0-9]+$/', $parm) )
+ if ( ctype_digit($parm) )
// Yes, run intval(), this enabling is_int()-ish checks
$parm = intval($parm);