As some of you might have noticed (and some even pointed out) I have had some trouble with spam lately. After dismissing the notion to disable comments altogether, I set out to find a solution to fight the spam.
First I checked out the Mollom and Akismet anti-spam services. They are both great at what they do and if you are in the same position I recommend looking them up. It was more hassle than I was willing to deal with however and so I started thinking about a simpler solution.
I currently require three fields to be filled out when commenting. Name, email and the actual message. Mollom and Akismet both target the message part, by analyzing its content. The name alone is not that much data to make a decision on, so we are left with the email address.
What makes an email address valid? The obvious answer is that it has a recipient in the other end. However, since we are looking for an easy solution we do not want to go about connecting to the SMTP server to see if our given user is valid there. Especially since not all SMTP servers will give up this information freely.
So what does a lazy developer do? We use cheap tricks!
Resolving the hostname
I took a first stab at this anti-spam approach by using
gethostbyname(). It does exactly what I wanted – resolves the hostname to an IP if there are proper A pointers configured. If not, it returns the unresolved hostname.
After a couple of minutes hacking I had a validator for the email widget. It tried resolving the hostname and raised an error if that did not yield an IP address. I tried a couple of the email addresses that had previously spammed me and it worked like a charm.
Then I deployed my solution to the production server and went about testing it there as well, just to make sure before I called it a victory. But it did not work. Whatever I threw at it passed through.
I picked a domain from a spam email address and tried running it from PHP CLI.
$ php -r 'echo gethostbyname("qeilfcyw.com")."\n";' 126.96.36.199 $ php -r 'echo gethostbyname("qeilfcyw.com")."\n";' 188.8.131.52
It looked up different IP addresses each time! Very odd. So I tried running dig to see what pointers it had configured.
$ dig qeilfcyw.com ; <<>> DiG 9.7.0-P1 <<>> qeilfcyw.com ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 12590 ;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 0 ;; QUESTION SECTION: ;qeilfcyw.com. IN A ;; AUTHORITY SECTION: com. 411 IN SOA a.gtld-servers.net. nstld.verisign-grs.com. 1300285512 1800 900 604800 86400 ;; Query time: 0 msec ;; SERVER: 184.108.40.206#53(220.127.116.11) ;; WHEN: Wed Mar 16 14:34:06 2011 ;; MSG SIZE rcvd: 103
Nothing. Could it be that my resolv.conf had a search directive?
$ cat /etc/resolv.conf nameserver 18.104.22.168 nameserver 22.214.171.124
Nope, no luck there neither. Maybe a traceroute could help find out where the traffic goes (and why)?
$ traceroute qeilfcyw.com traceroute to qeilfcyw.com (126.96.36.199), 30 hops max, 60 byte packets 1 109-74-0-112-static.serverhotell.net (188.8.131.52) 0.053 ms 0.032 ms 0.029 ms 2 79-99-4-2.serverhotell.net (184.108.40.206) 1.362 ms 1.336 ms 1.332 ms 3 195-20-206-2.serverhotell.net (220.127.116.11) 9.789 ms 9.769 ms 9.746 ms 4 te7-2.228.ccr01.sto01.atlas.cogentco.com (18.104.22.168) 10.953 ms 10.991 ms 11.143 ms 5 te0-3-0-5.ccr21.ham01.atlas.cogentco.com (22.214.171.124) 29.288 ms 29.416 ms te0-2-0-5.ccr21.ham01.atlas.cogentco.com (126.96.36.199) 29.129 ms 6 te0-0-0-3.mpd21.fra03.atlas.cogentco.com (188.8.131.52) 34.932 ms 34.392 ms 34.533 ms 7 tiscali.fra03.atlas.cogentco.com (184.108.40.206) 41.831 ms xe-4-3-0.fra23.ip4.tinet.net (220.127.116.11) 41.690 ms 37.861 ms 8 xe-7-2-0.nyc20.ip4.tinet.net (18.104.22.168) 123.139 ms 127.031 ms 126.938 ms 9 netaccess-gw.ip4.tinet.net (22.214.171.124) 136.085 ms 135.549 ms 138.901 ms 10 0.e1-4.tbr2.mmu.nac.net (126.96.36.199) 136.521 ms 141.461 ms 138.447 ms 11 vlan804.esd2.mmu.nac.net (188.8.131.52) 145.733 ms vlan805.esd1.mmu.nac.net (184.108.40.206) 142.128 ms 142.070 ms 12 220.127.116.11 (18.104.22.168) 142.012 ms 22.214.171.124 (126.96.36.199) 143.203 ms 188.8.131.52 (184.108.40.206) 136.314 ms 13 search9.infoweb.net (220.127.116.11) 140.275 ms 140.552 ms 140.109 ms
Traces left off at search9.infoweb.net. After some more testing it seemed every fake domain would resolve to an IP that would reverse resolve to searchX.infoweb.net. I shot an email to my server provider but they were as clueless as me. When they wanted to charge me for starting to look into the problem I digressed.
This was obviously not the lazy solution I was looking for.
Resolving the relevant
What it does is that it checks for a specified DNS pointer type only. I had previously been trying to resolve A pointers for the domains but when I thought about it that was completely irrelevant. What I really wanted to know was whether the domain had MX records or not. And the
checkdnsrr() function defaults to MX. Bingo!
The code has been adjusted, deployed and tested. It looks like it is working and I am crossing my thumbs that it does. Apologies to anyone coming here looking for cheap viagra.
I hope this helps someone else! Please let me know if it does. Just be sure to provide a real email address if you leave a comment. ;)