Half-Elf on Tech

Thoughts From a Professional Lesbian

Tag: spam

  • Stopping Jerks in Gravity Forms

    Stopping Jerks in Gravity Forms

    “Hang on,” I hear you say. “Didn’t you already do this?”

    Well, kind of. I did with Ninja Forms (and actually that resulted in me removing Ninja Forms and going back to Jetpack, and isn’t that a pill?). And I’ve mentioned Gravity Forms before with disallowed keys.

    This is really an extension of the disallowed keys and some very specific complex things I’ve changed since those posts.

    Yeah, tech changes.

    What’s a “Disallowed Key”?

    Disallowed keys are basically all those terms you put in your Disallowed Comment list (formerly the Blacklist):

    A screenshot of the disallowed comment keys field from Wordpress, with a string of examples like 'foo.bar@gmail' and so on

    Now if you look at my example above, I have a bunch of terms in there. Each line is a ‘key,’ meaning @example.net is a key.

    WordPress built this in for handling comments, specifically, but I’ve long advocated for things to be global, and since this is saved in my site, I can easily re-use it by telling Jetpack or anything else “If this email is on the list of disallowed keys, I don’t want to see it.”

    The reality of doing that is, of course, harder than it looks.

    For example, if you use Gmail, then foobar@gmail.com and foo.bar@gmail.com and foobar+ihatemika@gmail.com are all the same bloody thing! And if I block generaljerkyperson do I then want to block generaljerkyperson@nonjerky.com ?

    You have to make some decisions. Now solving the email one with periods and plus signs was easy. By comparison.

    Jerky People Aren’t Spammers

    This is the other thing to get into our heads. Jerky people ain’t spammers. Spammers are, weirdly, easier to spot and handle. Someone posts about viagra? Spam. Someone posts about SEO? Probably spam (though not always on a tech site). But those are all pretty self evident once you look at them!

    What is not self-evident is something like the world I have.

    Over on a site I run, we have a group instance (Slack, Discord kind of thing) and we let people sign up via Google Sheets for a while. But we ended up with one person in the group who was an antagonist. Let’s call her Bertie.

    Bertie isn’t a terrible human, but she has “Opinions” and they are the right ones. She likes a very specific thing, and does not like variations or changes. And if you don’t like what she does, you’re wrong, she’s right, and damn you. She picks fights, constantly, she disrespects anything anyone else likes… To put it how my friend Tracy might say “She yucks on other peoples’ yums.”

    We attempted to negotiate with her. Point out “Hey you’re in a GROUP and if you cannot respect people enough to tolerate their opinions when they differ, then this ain’t the group for you.”

    Bertie didn’t. And she didn’t change. So we banned her. Fine.

    She tried to sign up via Google Sheets again. We said no. And again. No. Then she tried new emails, new bios, new fake names. One actually got in. But as soon as we learned it was her, we kicked her.

    And at this point, we were fucking tired of playing whack-a-mole. So I decided to make something better.

    Catch and Release

    My overall thought process boils down to this: Every single jerky person has tells.

    They just do. And a human can spot them and go ‘wait a second…’ because we’re actually really good at recognizing patterns that are similar. A computer has to be taught that “If the submission is from this region or has this kind of email, it’s probably them.”

    So I started to build out some logic that would check all my flagged emails and IPs. Then I took advantage of Gravity Forms’ API to make a note in the entry so I could have a record of why someone was flagged.

    Here’s what it looks like for someone who’s email and IP was on the naughty list:

    Seeing that come in overnight, by the way, delighted me. It shunted the annoying Bertie to spam for two reasons, and none of us had to deal with her.

    How it Works

    This is the code part. The down and dirty here is I have two classes, one for finding spammers, and then the Gravity Forms that calls it. I did this because at the start, I had both Jetpack and Gravity Forms AND Google Forms. Obviously I can’t block someone hitting the Google Form directly, but I used to have this prevent that from loading the form. It was easy to get around. I know I know.

    Even though I now exclusively use Gravity Forms on the site, I left these separate to be future friendly. Also it means you can steal that to put it into whatever you’re doing. It’s GPLv2.

    By the way, everything goes to spam for a simple reason: It lets me clear up false positives.

    Find Spammers

    This class is what hooks into disallowed keys and checks if the email is banned, the domain is banned, or the IP used it banned. It also has an option to check for people who are moderated! That means if you wanted to flag people who might be jerks, you can do it.

    class Find_Spammers {
    
    	/**
    	 * List of disallowed Keys
    	 *
    	 * We check for emails, domains, and IPs.
    	 *
    	 * @return array the list
    	 */
    	public static function list( $keys = 'disallowed_keys' ) {
    
    		// Preflight check:
    		$valid_keys = array( 'disallowed_keys', 'moderation_keys' );
    		$keys       = ( in_array( $keys, $valid_keys, true ) ) ? $keys : 'disallowed_keys';
    
    		// Time for the show!
    		$disallowed_keys  = array();
    		$disallowed_array = explode( "\n", get_option( $keys ) );
    
    		// Make a list of spammer emails and domains.
    		foreach ( $disallowed_array as $spammer ) {
    			if ( is_email( $spammer ) ) {
    				// This is an email address, so it's valid.
    				$disallowed_keys[] = $spammer;
    			} elseif ( strpos( $spammer, '@' ) !== false ) {
    				// This contains an @ so it's probably a whole domain.
    				$disallowed_keys[] = $spammer;
    			} elseif ( rest_is_ip_address( $spammer ) ) {
    				// IP adresses are also spammery people.
    				$disallowed_keys[] = $spammer;
    			}
    		}
    
    		return $disallowed_keys;
    	}
    
    	/**
    	 * Is someone a spammer...
    	 * @param  string  $email_address The email address
    	 * @param  string  $plugin        The plugin we're checking (default FALSE)
    	 * @return boolean                True/False spammer
    	 */
    	public static function is_spammer( $to_check, $type = 'email', $keys = 'disallowed_keys' ) {
    
    		// Default assume good people.
    		$return = false;
    
    		// Get disallowed keys & convert to array
    		$disallowed = self::list( $keys );
    
    		if ( 'email' === $type ) {
    			$email_address = $to_check;
    
    			// Break apart email into parts
    			$emailparts = explode( '@', $email_address );
    			$username   = $emailparts[0];       // i.e. foobar
    			$domain     = '@' . $emailparts[1]; // i.e. @example.com
    
    			// Remove all periods (i.e. foo.bar > foobar )
    			$clean_username = str_replace( '.', '', $username );
    
    			// Remove everything AFTER a + sign (i.e. foobar+spamavoid > foobar )
    			$clean_username = strstr( $clean_username, '+', true ) ? strstr( $clean_username, '+', true ) : $clean_username;
    
    			// rebuild email now that it's clean.
    			$email = $clean_username . '@' . $emailparts[1];
    
    			// If the email OR the domain is an exact match in the array, then it's a spammer
    			if ( in_array( $email, $disallowed, true ) || in_array( $domain, $disallowed, true ) ) {
    				$return = true;
    			}
    		}
    
    		if ( 'ip' === $type ) {
    			$ip      = $to_check;
    			$bad_ips = false;
    			foreach ( $disallowed as $nope ) {
    				if ( rest_is_ip_address( $nope ) ) {
    					if ( ( strpos( $ip, $nope ) !== false ) || $ip === $nope ) {
    						$bad_ips = true;
    					}
    				}
    			}
    
    			// If they're a bad IP, then they're a bad IP and we flag.
    			if ( false !== $bad_ips ) {
    				$return = true;
    			}
    		}
    
    		return $return;
    	}
    
    }
    
    new Find_Spammers();
    

    Gravity Forms Check

    You may have noticed that the spammer checker is really just that, a checker. You have to call it. How I call it is via a Gravity Forms function. This does a couple kind of redundant things, and I know it can be optimized.

    The IP checker has some extra stuff to help me record where an IP is from when people submit, in order to try and catch other ‘common traits.’ It’s using ip-info, and amusingly I’ve found it’s mostly right. For some reason, it got the IP location of the same IP as being from 3 separate locations. I suspect it’s Bertie trying to be smarter and use a VPN. The fact that the Location is not the only measuring stick I use though means she can change her IP and email a bunch of times, but I have other checks.

    One improvement on my list is that if someone has a certain number of red-flags, it treats it like a jerk and sends to spam.

    class My_Gravity_Forms {
    
    	public function __construct() {
    		// Check all Gravity Forms ... forms for spammers.
    		add_action( 'gform_entry_is_spam', array( $this, 'gform_entry_is_spam' ), 10, 3 );
    	}
    
    	/**
    	 * Mark as spam
    	 *
    	 * If someone on our block-list emails, auto-mark as spam becuase we do
    	 * not want to hear from them, but we don't want them to know they were rejected
    	 * and thus encourage them to try other methods. Aren't assholes fun?
    	 *
    	 * @param  boolean  $is_spam  -- Is this already spam or not?
    	 * @param  array    $form     -- All the form info
    	 * @param  array    $entry    -- All info from the entry
    	 * @return boolean            true/false if it's "spam"
    	 */
    	public function gform_entry_is_spam( $is_spam, $form, $entry ) {
    
    		// If this is already spam, we're gonna return and be done.
    		if ( $is_spam ) {
    			return $is_spam;
    		}
    
    		$spam_message = 'Failed internal spam checks';
    		$warn_message = '';
    		$is_spammer   = false;
    		$is_moderated = false;
    		$is_bot       = false;
    		$is_vpn       = false;
    
    		// Loop and find the email:
    		foreach ( $entry as $value => $key ) {
    			if ( is_email( $key ) && ! $is_spammer ) {
    				$email        = $key;
    				$is_spammer   = Find_Spammers::is_spammer( $email, 'email', 'disallowed_keys' );
    				$is_moderated = Find_Spammers::is_spammer( $email, 'email', 'moderated_keys' );
    			}
    
    			if ( rest_is_ip_address( $key ) && ! $is_spammer ) {
    				$ip           = $key;
    				$is_spammer   = Find_Spammers::is_spammer( $ip, 'ip', 'disallowed_keys' );
    				$is_moderated = Find_Spammers::is_spammer( $ip, 'ip', 'moderated_keys' );
    				$is_bot       = self::check_ip_location( $ip, 'hosting' );
    				$is_vpn       = self::check_ip_location( $ip, 'proxy' );
    			}
    		}
    
    		// If this was a bot...
    		if ( true === $is_bot ) {
    			$warn_message .= 'Likely submitted by a bot or someone scripting. ';
    		}
    
    		// If a VPN...
    		if ( true === $is_vpn ) {
    			$warn_message .= 'Using a VPN. This may be harmless, but it\'s also how people evade bans. ';
    		}
    
    		// And if it's a spammer...
    		if ( $is_spammer ) {
    			$message = $spam_message;
    
    			if ( isset( $email ) ) {
    				$message .= ' - Email ( ' . $email . ' )';
    			}
    			if ( isset( $ip ) ) {
    				$message .= ' - IP Address ( ' . $ip . ' )';
    			}
    
    			$result = GFAPI::add_note( $entry['id'], 0, 'My Robot', $message, 'error', 'spam' );
    			return true;
    		} else {
    			if ( ! empty( $warn_message ) ) {
    				$add_note = GFAPI::add_note( $entry['id'], 0, 'My Robot', $warn_message, 'warning', 'spam' );
    			}
    		}
    
    		// If we got all the way down here, we're not spam!
    		return false;
    	}
    
    	/**
    	 * IP Checker
    	 */
    	public function check_ip_location( $ip, $format = 'full' ) {
    		$return    = $ip;
    		$localhost = array( '127.0.0.1', '::1', 'localhost' );
    
    		if ( in_array( $ip, $localhost, true ) ) {
    			$return = 'localhost';
    		} else {
    			$api     = 'http://ip-api.com/json/' . $ip;
    			$request = wp_remote_get( $api );
    
    			if ( is_wp_error( $request ) ) {
    				return $ip; // Bail early
    			}
    
    			$body = wp_remote_retrieve_body( $request );
    			$data = json_decode( $body );
    
    			switch ( $format ) {
    				case 'full':
    					// Return: US - Chicago
    					$return .= ( isset( $data->countryCode ) ) ? ' ' . $data->countryCode : ''; // phpcs:ignore
    					$return .= ( isset( $data->countryCode ) ) ? ' - ' . $data->city : ''; // phpcs:ignore
    					$return .= ( isset( $data->proxy ) && true === $data->proxy ) ? ' (VPN)' : '';
    					break;
    				case 'hosting':
    					$return = ( isset( $data->hosting ) && true === $data->hosting ) ? true : false;
    					break;
    				case 'proxy':
    					$return .= ( isset( $data->proxy ) && true === $data->proxy ) ? true : false;
    					break;
    			}
    		}
    
    		return $return;
    	}
    }
    
    new My_Gravity_Forms();
    

    Any Issues?

    Two.

    1. I use an ‘approval’ feature (forked from “Gravity Forms Approvals” to allow for multiple approvers optional but only one has to approve to be a go – the original requires all approvers to approve) – for some reason this is not properly moving anything in spam or trash to a ‘rejected’ status
    2. IP-Info got a ‘different’ IP location than I see from the IP in two cases. I believe that’s due to the individual trying to juke the system and being caught anyway, but it needs some debugging.

    Oh and clearly I have some optimization I could stand to work on, but that’s for another day.

    This code is live, in production, and has been merrily blocking Bertie for some time.

  • Stopping Jerks in Ninja Forms

    Stopping Jerks in Ninja Forms

    I don’t have a spam problem, I have a jerky people problem. I have people who, no matter how many times I explain I cannot help them, or I don’t want to talk to them, will continue to email.

    Right now, I have some absolute weirdo in Europe who emails me every day via a contact form. I don’t know what the heck he’s thinking, but I do not need advice about how to live my life nor can I help him talk to a celebrity. The problem though is I can’t delete the form. I can (and did) set his email to auto-bin via my mail server, but he still fills the form in and I am just tired of cleaning this up.

    This site happens to use Ninja Forms, and really what I want to do is auto-cycle his emails to the bin so he can rant all he wants and never knows I don’t see a thing.

    (Note: This is not the same person as my serial harasser.)

    Warning: Their Documentation is Rough

    The biggest headache to all this is the fact that Ninja Forms’ documentation kinda sucks. For example, you cannot search their ‘codex‘! That’s just basic level for a documentation service, and on top of that if you try googling, it wants to send you to the non-developer pages.

    Now to their credit they know this:

    Admin note: we have not been able to give this site the attention it needs or deserves for a while. Most of the Codex documentation is still applicable, but please be aware that you will find some outdated material here that will need to be adapted for Ninja Forms in its current, more modern, state. 

    But that doesn’t make it really any better for me today, and it’s been like that for a while.

    Which means thinking “I can search for how to auto-flag a submission as spam/trash!” is impossible. It doesn’t work, it doesn’t exist in current NF format, and it’s a pain to the point that I seriously considered dumping the whole plugin over this!

    Folks. I know documentation is incredibly hard, but if you want people to make plugins to extend yours, and thus help make you even more popular, hire someone to do this. It’s only gonna get harder as time goes on.

    The Initial Code

    The first step is, of course, can I even do this, and of course I can:

    <?php
    /**
     * Prevent anyone from my blocklist from spamming me.
     */
    
    // Exit if accessed directly.
    defined( 'ABSPATH' ) || exit;
    
    
    class FLF_NinjaForms {
    
    	/**
    	 * List of disallowed emails
    	 *
    	 * We omit anything that isn't an email address or has an @ in the string.
    	 *
    	 * @return array the list
    	 */
    	public static function list() {
    		$disallowed_emails = array();
    		$disallowed_array  = explode( "\n", get_option( 'disallowed_keys' ) );
    
    		// Make a list of spammer emails and domains.
    		foreach ( $disallowed_array as $spammer ) {
    			if ( is_email( $spammer ) || ( strpos( $spammer, '@' ) !== false ) ) {
    				// Anything with an @-symbol is probably an email, so let's trust it.
    				$disallowed_emails[] = trim( $spammer );
    			}
    		}
    
    		return $disallowed_emails;
    	}
    
    	/**
    	 * On load.
    	 */
    	public function __construct() {
    		add_filter( 'ninja_forms_submit_data', array( $this, 'comment_blocklist' ) );
    	}
    
    	/**
    	 * Ninja Forms: Server side email protection using WordPress comment blocklist
    	 * https://developer.ninjaforms.com/codex/custom-server-side-validation
    	 *
    	 * @param array $form_data Form data array.
    	 * @return array $form_data email checked form data array.
    	 */
    	public function comment_blocklist( $form_data ) {
    		$disallowed = self::list();
    
    		foreach ( $form_data['fields'] as $field ) {
    			// If this is email, we will do some playing.
    			if ( 'email' === $field['key'] ) {
    				$email_address = sanitize_email( strtolower( $field['value'] ) );
    
    				// Break apart email into parts
    				$emailparts = explode( '@', $email_address );
    				$username   = $emailparts[0];       // i.e. foobar
    				$domain     = '@' . $emailparts[1]; // i.e. @example.com
    
    				// Remove all periods (i.e. foo.bar > foobar )
    				$clean_username = str_replace( '.', '', $username );
    
    				// Remove everything AFTER a + sign (i.e. foobar+spamavoid > foobar )
    				$clean_username = ( false !== strpos( $clean_username, '+' ) ) ? strstr( $clean_username, '+', true ) : $clean_username;
    
    				// rebuild email now that it's clean.
    				$email = $clean_username . '@' . $emailparts[1];
    
    				// If the email OR the domain is an exact match in the array, then it's a spammer
    				if ( in_array( $email, $disallowed, true ) || in_array( $domain, $disallowed, true ) ) {
    					$form_data['errors']['fields'][ $field['id'] ] = 'Error: Invalid data.';
    				}
    			}
    		}
    		return $form_data;
    	}
    
    }
    
    new FLF_NinjaForms();
    
    

    This code takes the email, strips out any periods (since Google allows you to put those in anywhere in your username) and then also removes anything after a + sign (since… Google lets you add in random whatever after a + sign) and builds a sanitized email. Then it checks that email on my block list. It also checks if I banned the domain.

    The Problem

    The only problem?

    				// If the email OR the domain is an exact match in the array, then it's a spammer
    				if ( in_array( $email, $disallowed, true ) || in_array( $domain, $disallowed, true ) ) {
    					$form_data['errors']['fields'][ $field['id'] ] = 'Error: Invalid data.';
    				}
    

    That tells them “Error: Invalid Data” for the email. Which will suggest to them to try something else.

    I don’t want that!

    So I thought what if I changed that error to this:

    $form_data['fields']['is_spam'] = true;
    

    Which sets a new field for me! Is spam.

    The only problem? Well I thought I could use that with ninja_forms_after_submission to then say “Submission is in but we are going to treat it as trash and not email it.” Ever tried to look up ‘don’t email Ninja Forms’? Or any form? Yeah, all you get it help if Ninja Forms isn’t sending email.

    Then I thought I could set it as actual spam, but Ninja Forms has the most useless advice:

    If you’ve used all the methods above and you still receive spam submissions, maybe it’s time to change your hosting provider. Ideally, they can help you minimize spam and provide you a web application firewall to keep those spambots off your website.

    My host isn’t the issue. This jerk is. And both Jetpack and Gravity Forms have an _is_spam filter/action you can hook. I find it very odd that native mark-as-spam isn’t a think in Ninja Forms, and honestly it’s putting more fuel to the ‘change tools’ fire. This is some basic stuff, ain’t it? If you can hook into Aksimet and have it catch spam, why can’t you mark as spam and send that data back to make everyone’s life better?

    Is this the end?

    Well. For now it is. For next it’s not. I think the real answer will be to create an action like they have for Akismet, and in there rebuild my spam tool.

    Maybe as a plugin for all.

    But for right now, I actually turned off the forms entirely. No more contact form. The only person really using it was that yahoo, and instead I have the email address up there for now. Likely I’ll go back to Jetpack for a while, or maybe write a whole, complex, add on plugin. Later.

  • Gravity Forms and Disallowed Keys

    Gravity Forms and Disallowed Keys

    Recently Gravity Forms was added to a site I work on. Now, I’ve never used it before, so I was hands off (except for changing the email it sent to) and I know pretty much nothing at all about it. But what I do know is that there’s a real jerk out there who’ll spam it, given a chance.

    Unlike other contact form plugins out there, Gravity Forms comes with built in free integration with Akismet! But, like pretty much every other plugin out there, it does not integrate with my disallowed keys.

    I’m a big proponent of not reinventing the wheel, and I strongly feel that being able to block someone from comments and contact forms should be a done deal. I opted to mark people who do this as spam, instead of a rejection, so they will never know if I ever saw their email or not. This is a questionable use of the spam settings, but at the same time, it’s been a rough couple of years.

    The Process

    Since the disallowed_keys list contains emails and words, the first thing I wanted to do was strip out everything that wasn’t an email or an @-domain — that means foobar@example.com is a valid entry, and @spammers-r-us.com is a valid entry, but foobar on it’s own is not. I run through my disallowed list, add everything valid to an array in a new variable.

    Before I can pass through the email, though, I need to remove any periods from the username. You see, Gmail allows you to use foobar and foo.bar and fo.o.b.a.r all as the same valid username on your email. Yes. all those would go to the same person. To get around this, I remove all periods and make a clean username.

    Also I have to consider the reality of jerks, who do things like foobar+cheater@example.com — Gmail allows you to use the + sign to get clever and isolate emails, which I use myself to track what sign-up spams me. At the same time, I don’t want people to get around my blocks, so I have to strip everything following the plus-sign from the email.

    While I’m doing this, I’ll save the domain as it’s own variable, because that will allow me to check if @spammers-r-us.com is on my list or not.

    Once I’ve got it all sorted, I do an in-array: if either the exact (clean) email is in the array, or the exact @-domain is in the array, it’s spam and I reject.

    The Code

    add_action( 'gform_entry_is_spam_1', 'my_spam_filter_gform_entry_is_spam_1', 10, 3 );
    
    function my_spam_filter_gform_entry_is_spam_1( $is_spam, $form, $entry ) {
    
    	// If this is already spam, we're gonna return and be done.
    	if ( $is_spam ) {
    		return $is_spam;
    	}
    
    	// Email is field 2.
    	$email = rgar( $entry, '2' );
    
    	// Build a list of valid emails & domains from disallowed_keys
    	$disallowed_emails = array();
    	$disallowed_array  = explode( "\n", get_option( 'disallowed_keys' ) );
    
    	// Make a list of spammer emails and domains.
    	foreach ( $disallowed_array as $spammer ) {
    		if ( is_email( $spammer ) ) {
    			// This is an email address, so it's valid.
    			$disallowed_emails[] = $spammer;
    		} elseif ( strpos( $spammer, '@' ) !== false ) {
    			// This contains an @ so it's probably a whole domain.
    			$disallowed_emails[] = $spammer;
    		}
    	}
    
    	// Break apart email into parts
    	$emailparts = explode( '@', $email );
    	$username   = $emailparts[0];       // i.e. foobar
    	$domain     = '@' . $emailparts[1]; // i.e. @example.com
    
    	// Remove all periods (i.e. foo.bar > foobar )
    	$clean_username = str_replace( '.', '', $username );
    
    	// Remove everything AFTER a + sign (i.e. foobar+spamavoid > foobar )
    	$clean_username = strstr( $clean_username, '+', true ) ? strstr( $clean_username, '+', true ) : $clean_username;
    
    	// rebuild email now that it's clean.
    	$clean_email = $clean_username . '@' . $emailparts[1];
    	
    	// If the email OR the domain is an exact match in the array, then we know this is a spammer.
    	if ( in_array( $clean_email, $disallowed, true ) || in_array( $domain, $disallowed, true ) ) {
    		$return = true;
    	}
    
    	// If we got all the way down here, we're not spam!
    	return false;
    }
    

    Of Note…

    Before you use this yourself, you will need to customize two things!

    1. gform_entry_is_spam_1 is actually the specific form I’m checking. Form ID 1. Customize that to match your form ID.
    2. $email = rgar( $entry, '2' ); — you may have noticed I put ’email is field 2′ as a note above it. That’s because email is the second field on form 1, so I hard grabbed it. If yours is different, change that.

    Also … I actually broke this out into two files, one that just checks “Is this a spammer?” and the Gravity Forms file, so the latter calls spammers.php and checks the email against the is_spammer() function. The reason I did that is because I need to run this same check on Jetpack’s contact form. Both call the same function to know if someone is evil.

  • I’ll Know A Duck When I See It

    I’ll Know A Duck When I See It

    After I complained about the new SEO scam, someone pointedly argued it wasn’t spam. And it wasn’t a scam.

    It is and it’s both.

    What Is Spam?

    By it’s most basic definition, spam is an irrelevant or otherwise inappropriate message, sent on the internet, to a large group of people.

    With that definition in hand, someone who interrupts a Slack or IRC meeting to tell a joke is spamming. At the same time, Tweeting inanities is not unless you cut into a conversation thread. And the different being that Twitter is always irrelevant so any comment there is expected to be appropriately inappropriate.

    Spam is More Than Spam

    The issue is that spam has expanded to be more than just that simple blast of junk you didn’t care about. Spam now includes things like being added to an email list you didn’t want to join. And it includes people trying to rip you off.

    A scam is an attempt to get something from you. The end goal of a lot of spam is to scam you out of money, so the intersection there is pretty high. It always has been. The result of a spambot is to convince you to do something you didn’t want, in order to get something you have. But the target of scams is to out and out separate you from your money.

    If It Looks Like a Duck …

    You’ve probably heard of the duck test.

    If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.

    When people read an email from some Nigerian prince, they know it’s spam because they’ve seen things like it before. But they also know it’s a scam because they’ve been taught that no one offers something for nothing.

    Unsolicited Emails Are Ducks

    If you get an email you didn’t ask for, from someone you’ve never heard of, offering something that’s too good to be true (like ‘free backlinks’), it’s a duck.

    If you look at the emails from these people who offer to help you fix your site and improve your links to broken locations, it’s a duck. It’s a scam, it’s spam, and you should delete it. Don’t even ask.

  • The New SEO Scam

    The New SEO Scam

    The email looked innocuous.

    I was just browsing Ipstenu.Org and saw in this post (link from 2002) you were interested in tech, and so I thought you might also be interested in linking to a resource we put together on the ways technology is improving health.

    It went on to tell me about how they were comprehensive, up to date, etc etc. I admit, I wondered how they got my email in the first place, since it’s not listed on my site on purpose. But barring anything nefarious, I assumed they guessed, and since it wasn’t important to me that a personal blog post from 2002 get updated, I deleted the email.

    Every five days after then I got a ‘follow up’ email from this person, Camilla Hathaway, and it was strange. I didn’t reply so why would they keep doing that?

    But then I got an email from another company about a different post, asking me if I wanted to link to their article about ddos protection. And another from a third company for a post about cPanel.

    They all sent follow up emails and they all were very ‘nice’ about it, praising my writing and telling me about broken links.

    Spam by any other name…

    If the email was about running a banner ad on CNN for $725, you’d know it was spam.

    If it was from the FBI telling you the corrupt government owed you millions, you’d know it was spam.

    This appeared to be from a real person, a real reader. Except for the fact that there was no way they should have been able to find that particular email address. Except for the fact that they kept email. Except for the fact that who the heck reads old posts on a personal site from as far back as 2001 (I’ve been blogging a long time) and tell me that a link is broken or the information is out of date.

    It’s weird, isn’t it?

    Well, it’s spam.

    The New Spam Game

    The old SEO spam was a lot more overt.

    We are a Leading SEO & Web Development Company and one of the very few companies which offer organic SEO Services with a full range of supporting services such as one way themed text links, blog submissions, directory submissions, article writing and postings, etc.

    or

    I was doing some research on [Subject] and landed on your website.

    You know the obvious ones. These new ones are more clever. They sound more like people. And the worst part is they aren’t all fake people.

    You see … A real company, a legit company, run by real people in the UK, spammed the hell out of me with offers like this. Every day for almost two weeks before I blocked the accounts. This was after I pinged them on Twitter and asked them to leave me alone.

    I shouldn’t have to.

    If I don’t reply, I’m probably not going to. But I surely am not going to reply within a day if you email me daily. The new spam game, the new scam game is to be nice and hammer you with a request over and over and over.

    If It Looks Too Good To Be True, It Is

    The bottom line is that if it looks too good to be true, it is. No probably about it.

  • Greylist, RBLs, and Spam

    Greylist, RBLs, and Spam

    Recently I noticed I had 13 spam emails all from the same ‘company.’ The content was incredibly similar, though subtly different. The from email was always different, but you could tell by looking at it that it was the same. And even more damming, it all had ‘junk’ content and 100+ recipients. But for some reason, SpamAssassin wasn’t catching it!

    After 5 emails came in back to back, I decided to do something about it.

    At first I was trying to find a way to tell Spamassassin or Exim how to auto-turf the emails with 100+ people listed in the ‘To’ field. This proved to be a little more difficult and complicated than I wanted, and I was sure that these spammers would catch on to that sooner or later.

    What I really wanted was for Spamcop to pick up on this, but I’ve been sending them in to no avail for a while. That got me looking into how cPanel handles Spamcop in the first place.

    Real-Time Blackhole Lists

    cPanel uses RBLs, Real-time Blackhole Lists, to determine if an email sent to you is spam or not. By default, it comes with SpamCop and Spamhaus. That means it will reject mail at SMTP time if the sender host is in the bl.spamcop.net or zen.spamhaus.org RBL. Well that was well and good, but could I add more to that list?

    Of course. I pulled up cPanel’s documentation on RBLs and determined I could add as many as I wanted. On the top of the Basic EXIM Editor is a link to Manage Custom RBLs which is what I wanted. All I had to do was figure out what to add.

    After reading through WikiPedia’s comparison of DNS blacklists, I picked a few and tested the latest emails that had come through, looking for ones that caught them. Then I tested known good emails and made sure they weren’t caught. I ended up adding Barracudacentral and IPRange.

    Greylisting

    The next thing I did was introduce Greylisting to my email. They way Greylisting works is that if it doesn’t recognize the email, it will temporarily reject it and tell it to resend. If the email is real, the server tries to send it again after a little while. There are some downsides to this, as it’s possible for a legit email to be trapped for a few hours (or days) if someone’s set up their server poorly. On the other hand, within half an hour, I blocked 11 emails.

    I mean. I’m pretty sure monica@getoffherpes.com is spam. You know what I mean?

    This was super easy to do, too. I turned on Greylisting, I restarted Exim, I walked away.

    Okay no, I didn’t. I sat and watched it to see if anyone legit got caught (one did, it passed itself through properly).

    Result?

    A little less spam. I don’t expect this to work for everything, but it had an immediate impact on many of the spam emails that were annoying me.