Click here or hit escape to close this.

Funny Internet Spam for eMail and Websites is Spicy
FluxBB: anti-spam alternative to CAPTCHAs and HoneyPots

Notice: this post is 3 years old, its content may be outdated.

Prior to anything this should be very clear from the start: CAPTCHAs and/or HoneyPots are commonly used, really efficient techniques to counter spam on forums. The point here is not to encourage not using these techniques, but to provide a quick and simple technique I’ve been using for years and that proved to be working on a lot of forums I own and/or use. If you have a FluxBB forum that is subject to heavy spamming attacks, you may want to take a look at better and stronger solutions like SpamBarrier or StopForumSpam.

The idea

The idea behind CAPTCHAs and similar antispam systems is that robots can’t guess every question or operation a human may have to proceed through to register on a forum/website. One thing you need to register is most generally an email address; that’s a good place to check whether you’re dealing with a human or not. If he’s a human, he will most likely notice the information right above the email fields stating that he must alter the first email field to include a magic keyword.

How we do it

Long story short, we store a short string in the database that users will need to add somewhere in one of the email address fields; when a registration is attempted, we check the email submitted for the magic word. If the mail contains it, then it should be human; if it doesn’t, most likely not human, throw an error. You may ask, what if users didn’t read carefully enough and didn’t notice they had to alter their email? Once they bump into an error they’ll should be more attentive to anything the error says, so we just inform them they may have missed something and that should be enough. For the record, I’ve never had any feedback about users unable to register because of this. Some take a few tries to register because they indeed didn’t read what they should have, but nothing more.

Note that all of this depends on thing: you need to activate the registration verification option in FluxBB, which is a good thing anyway. It sends a random password to the registering user, testing if the email address he provided is actually his and is working. The reg verify option also add the need to input the email address twice, which is exactly what we need.

register.php

The main modification is, unsurprisingly, done to register.php. Three different blocks are required.

Language

Around line 23, find:

// Load the register.php/profile.php language file
require PUN_ROOT.'lang/'.$pun_user['language'].'/prof_reg.php';

Right after, add:

// Antispam MailTrick
if ( file_exists( PUN_ROOT . 'lang/'.$admin_language.'/asmt.php' ) )
	require PUN_ROOT . 'lang/'.$admin_language.'/asmt.php';
else 
	require PUN_ROOT . 'lang/English/asmt.php';

Main part: mail checking

Right after the username verification around line 98:

	// Validate username and passwords
	check_username($username);

Add this block:

	// Antispam MailTrick
	$mail_field = $pun_config['o_asmt_mail_field'];
	$magick     = $pun_config['o_asmt_magic'];
	$position   = $pun_config['o_asmt_magic_position'];
	$separator  = $pun_config['o_asmt_magic_separator'];

	// Which mail to check
	$asmt_email = ( $mail_field == 1 ? $email1 : $email2 );

	// magic + separator
	if ( in_array( $position, array( 1, 3 ) ) )
		$magick = $magick . $separator;
	else if ( in_array( $position, array( 2, 4 ) ) )
		$magick = $separator . $magick;

	// total magic length
	$length = strlen( $magick );

	// where to look for the magic?
	// before email
	// CODE_john.doe@johndoe.com
	if ( $position == 1 )
		$valid = ( substr( $asmt_email, 0, $length ) === $magick );
	// after email
	// john.doe@johndoe.com_CODE
	else if ( $position == 2 )
		$valid = ( substr( $asmt_email, ( 0 - $length ) ) === $magick );
	// before @
	// john.doeCODE_@johndoe.com
	else if ( $position == 3 )
		$valid = ( substr( $asmt_email, strpos( $asmt_email, '@' ) - $length, $length ) === $magick );
	// after @
	// john.doe@_CODEjohndoe.com
	else if ( $position == 4 )
		$valid = ( substr( $asmt_email, strpos( $asmt_email, '@' ) + 1, $length ) === $magick );

	// Not valid? 404 error.
	if ( false === $valid )
		message( $lang_asmt['Invalid email'], false, '404 Not Found' );

	// Remove the magic from the email 
	$asmt_email = str_replace( $magick, '', $asmt_email );
	if ( $mail_field == 1 )
		$email1 = $asmt_email;
	else
		$email2 = $asmt_email;
	// /Antispam MailTrick

User notification

Around line 380, find the Email Info text:

<?php if ($pun_config['o_regs_verify'] == '1'): ?>						<p><?php echo $lang_register['Email info'] ?></p>

Right after, add:

<?php
// Antispam MailTrick
$position  = $pun_config['o_asmt_magic_position'];
$magick    = $pun_config['o_asmt_magic'];
$separator = $pun_config['o_asmt_magic_separator'];
$mail_field = ( $pun_config['o_asmt_mail_field'] == 1 ? $lang_asmt['First'] : $lang_asmt['Second'] );

// Magic word have to be before anything? Separator goes after magic word.
if ( in_array( $position, array( '1', '3' ) ) )
	$magick = $magick . $separator;
// Magic word have to be after anything? Separator goes before.
else if ( in_array( $position, array( '2', '4' ) ) )
	$magick = $separator . $magick;

// This is messy, but it works.
$pos = array( '1' => $lang_asmt['Before email'], '2' => $lang_asmt['After email'], '3' => $lang_asmt['Before @'], '4' => $lang_asmt['After @'] );
$position = $pos[ $position ];
?>
						<p><?php printf( $lang_asmt['Antispam info'], $mail_field, $position, strlen( $magick ), $magick ); ?></p>

Plugin Setting page

In your forum’s plugin directory create a AP_AntispamMailTrick.php file:

<?php
/**
 * 
 * 
 * @package AntispamMailTrick
 * @author Charlie MERLAND
 * @author_uri http://www.caercam.org
 * @version 1.0.0
 * @copyright Copyright (C)2014 Charlie MERLAND
 * @license: GPL version 2 or higher
 * @license_uri http://www.gnu.org/licenses/gpl.html
 */

// Make sure no one attempts to run this script "directly"
if ( ! defined( 'PUN' ) )
    exit;

// Tell admin_loader.php that this is indeed a plugin and that it is loaded
define( 'PUN_PLUGIN_LOADED', 1 );
define( 'PLUGIN_VERSION', '1.0.0' );
define( 'PLUGIN_URL', 'admin_loader.php?plugin=AP_AntispamMailTrick.php' );

if ( file_exists( PUN_ROOT . 'lang/'.$admin_language.'/asmt.php' ) )
	require PUN_ROOT . 'lang/'.$admin_language.'/asmt.php';
else 
	require PUN_ROOT . 'lang/English/asmt.php';

// Default settings
$default = array(
	// Magic word in the first email field
	'mail_field'         => 1,
	// Random 4 chars long magic word
	'magic'              => substr( str_shuffle( str_repeat( 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', 4 ) ), 0, 4 ),
	// Underscore to separate magic word from email
	'magic_separator'    => '_',
	// Magic word before the email
	'magic_position'     => 1
);

if ( isset( $_POST['form_sent'] ) ) {

	confirm_referrer( 'admin_loader.php?plugin=AP_AntispamMailTrick.php', $lang_common['Bad referer'] );

	$o_asmt = array();

	// Parse submitted settings
	$o_asmt['mail_field']         = ( isset( $_POST['form']['mail_field'] ) && in_array( $_POST['form']['mail_field'], array( 1, 2 ) ) ? $_POST['form']['mail_field'] : $default['mail_field'] );
	$o_asmt['magic']              = ( isset( $_POST['form']['magic'] ) && $_POST['form']['magic'] != '' ? pun_htmlspecialchars( $_POST['form']['magic'] ) : $default['magic'] );
	$o_asmt['magic_separator']    = ( isset( $_POST['form']['magic_separator'] ) && $_POST['form']['magic_separator'] != '' ? pun_htmlspecialchars( $_POST['form']['magic_separator'] ) : $default['magic_separator'] );
	$o_asmt['magic_position']     = ( isset( $_POST['form']['magic_position'] ) && in_array( $_POST['form']['magic_position'], array( 1, 2, 3, 4 ) ) ? $_POST['form']['magic_position'] : $default['magic_position'] );

	// Update settings
	foreach ( $o_asmt as $name => $value ) {
		$db->query( 'UPDATE ' . $db->prefix . 'config SET conf_value="' . $value . '" WHERE conf_name="o_asmt_' . $name . '"' ) or error( 'Unable to update the configuration', __FILE__, __LINE__, $db->error() );
	}

	// Regenerate the config cache
	require_once PUN_ROOT . 'include/cache.php';
	generate_config_cache();

	redirect( PLUGIN_URL, $lang_asmt['Settings updated redirect'] );
}
else {

	// Check if settings exist
	$sql = 'SELECT * FROM ' . $db->prefix . 'config WHERE conf_name IN ("o_asmt_mail_field", "o_asmt_magic", "o_asmt_magic_separator", "o_asmt_magic_position")';
	$result = $db->query( $sql ) or error( 'Unable to get config', __FILE__, __LINE__, $db->error() );

	// Settings not found, set default
	if ( ! $db->fetch_assoc( $result ) ) {
		foreach ( $default as $name => $value ) {
			$db->query( 'INSERT INTO '.$db->prefix.'config (conf_name, conf_value) VALUES ("o_asmt_'.pun_htmlspecialchars( $name ).'", "'.pun_htmlspecialchars( $value ).'")' ) or error( 'Unable to set default config', __FILE__, __LINE__, $db->error() );
		}

		// Regenerate the config cache
		require_once PUN_ROOT . 'include/cache.php';
		generate_config_cache();
		
	}

	// Display the admin navigation menu
	generate_admin_menu( $plugin );
	
?>
	<div id="exampleplugin" class="plugin blockform">
		<h2><span>Spam MailTrick v<?php echo PLUGIN_VERSION ?></span></h2>
		<div class="box">
			<div class="inbox">
				<p><?php echo $lang_asmt['Antispam intro'] ?></p>
<?php if ( $pun_config['o_regs_verify'] == '0' ) : ?>
				<p><?php echo $lang_asmt['Antispam reg verify'] ?></p>
<?php endif; ?>
			</div>
		</div>
	</div>
	<div class="blockform">
		<h2 class="block2"><span><?php echo $lang_asmt['Settings'] ?></span></h2>
		<div class="box">
			<form method="post" action="<?php echo PLUGIN_URL; ?>">
				<div class="inform">
					<input type="hidden" name="form_sent" value="1" />
					<fieldset>
						<legend><?php echo $lang_asmt['Options'] ?></span></legend>
						<div class="infldset">
							<table class="aligntop">
								<tr>
									<th scope="row"><?php echo $lang_asmt['Email field'] ?></th>
									<td>
										<label><input type="radio" name="form[mail_field]" value="1"<?php if ( $pun_config['o_asmt_mail_field'] == '1' ) echo ' checked="checked"' ?> />&nbsp;Email #1</label>
										<label><input type="radio" name="form[mail_field]" value="2"<?php if ( $pun_config['o_asmt_mail_field'] == '2' ) echo ' checked="checked"' ?> />&nbsp;Email #2</label>
									</td>
								</tr>
								<tr>
									<th scope="row"><?php echo $lang_asmt['Magic word'] ?></th>
									<td>
										<label><input type="text" name="form[magic]" value="<?php if ( $pun_config['o_asmt_magic'] != '' ) echo $pun_config['o_asmt_magic'] ?>" /><br /><?php echo $lang_asmt['Magic word info'] ?></label>
									</td>
								</tr>
								<tr>
									<th scope="row"><?php echo $lang_asmt['Separator'] ?></th>
									<td>
										<label><input type="text" name="form[magic_separator]" value="<?php if ( $pun_config['o_asmt_magic_separator'] != '' ) echo $pun_config['o_asmt_magic_separator'] ?>" /><br /><?php echo $lang_asmt['Separator info'] ?></label>
									</td>
								</tr>
								<tr>
									<th scope="row"><?php echo $lang_asmt['Position'] ?></th>
									<td>
										<label><input type="radio" name="form[magic_position]" value="1"<?php if ( $pun_config['o_asmt_magic_position'] == '1' ) echo ' checked="checked"' ?> />&nbsp;<?php echo $lang_asmt['Before email'] ?></label>
										<label><input type="radio" name="form[magic_position]" value="2"<?php if ( $pun_config['o_asmt_magic_position'] == '2' ) echo ' checked="checked"' ?> />&nbsp;<?php echo $lang_asmt['After email'] ?></label>
										<label><input type="radio" name="form[magic_position]" value="3"<?php if ( $pun_config['o_asmt_magic_position'] == '3' ) echo ' checked="checked"' ?> />&nbsp;<?php echo $lang_asmt['Before @'] ?></label>
										<label><input type="radio" name="form[magic_position]" value="4"<?php if ( $pun_config['o_asmt_magic_position'] == '4' ) echo ' checked="checked"' ?> />&nbsp;<?php echo $lang_asmt['After @'] ?></label>
									</td>
								</tr>
							</table>
						</div>
					</fieldset>
				</div>
			<p class="submitend"><input type="submit" name="save" value="<?php echo $lang_admin_common['Update'] ?>" /></p>
			</form>
		</div>
	</div>

<?php
}
?>

i18n

Last step, in your lang/English directory, create a asmt.php file:

<?php

// Language definitions used in Antispam MailTrick
$lang_asmt = array(

'Settings updated redirect'		=>  'Settings updated. Redirecting …',

'Antispam intro'			=>  '<strong>AntiSpam Mail Trick</strong> is an antispam alternative to CAPTCHAs and other HoneyPots solution. It uses a simple trick to detect non-human registration by check the submitted email for a specific keyword the user need to enter.',
'Antispam reg verify'			=>  'You don\'t have the registration verification option activated, you need it for the antispam to work properly. Activate it in the <a href="/admin_options.php">Admin Options registration section</a>.',
'Settings'				=>  'Settings',
'Options'				=>  'Options',

'Email field'				=>  'Email field to trick',
'Magic word'				=>  'Magic word',
'Magic word info'			=>  'Find something short, like four or five random letters and numbers.',
'Separator'				=>  'Separator',
'Separator info'			=>  'Optional: use a character to separate the magic from the email, like "_" or "+".',
'Position'				=>  'Position',

'Antispam info'				=>  'To prevent robots from registering automatically, <strong>your registration will not be valid unless the <u>%s</u> email contains, %s, the following %d characters code</strong>: "%s".',
'First'					=>  'first',
'Second'				=>  'second',
'Before email'				=>  'before the email',
'After email'				=>  'after the email',
'Before @'				=>  'before the "@"',
'After @'				=>  'after the "@"',
'Invalid email'				=>  'The email address you entered is invalid. It is most likely that you failed the anti-spam test: you need to add a magic word in your email address… Read the comments carefully ;-)',

);

Conclusion

And that’s about it. The great advantage of this technique is that it can be done in only two lines of code if you strip it to the minimal: if on of the email doesn’t contain a special code, throw an error. This version is more elaborate and comes with a bunch of settings to change easily the magic code, chose where it should be placed, chose a separator, which email field to check… It doesn’t bother users with unreadable images like CAPTCHAs, and doesn’t come with the heavy artillery a honeypot represents; it isn’t bullet-proof either, but have proved to be very efficient on forums I know that were literally under constant assault from spambots and seldom see one anymore.

What it looks like in action:

I plan to put all this in a clean and well tested package to be released as a FluxBB Mod; will keep track of the updates here.

Photo: Funny Internet Spam for eMail and Websites is Spicy by epSos.de