Log in

No account? Create an account
rejecting residential zombies with qpsmtpd - brad's life — LiveJournal [entries|archive|friends|userinfo]
Brad Fitzpatrick

[ website | bradfitz.com ]
[ userinfo | livejournal userinfo ]
[ archive | journal archive ]

rejecting residential zombies with qpsmtpd [Aug. 14th, 2007|03:29 pm]
Brad Fitzpatrick
[Tags|, , , ]

Qpsmtpd continues to be fun. For the past week or so, danga.com's mail has been running with qpsmtpd in front, instead of Postfix, and qpsmtpd now handles:

-- normal logging to syslog
-- logging all connections in structured/indexed way to mysql, many columns per connection
-- SMTP AUTH, letting me/friends use danga.com as our outgoing mail server
-- check_earlytalker (reject clients who speak before spoken to, as lot of spammers do)
-- counting bad commands (reject clients who HTTP POST to port 25 via open web proxy)
-- DNS RBL checks
-- rejecting non-members from posting to GNU Mailman lists
-- not letting people send mail as @danga.com, @bradfitz.com, etc, etc without SMTP-AUTH
-- rejecting mails to non-existent users (no bounces later, 5xx immediately)
-- spam checks via spamassassin
-- virus (and phishing) checks via ClamAV's clamd
-- queuing to postfix (which then delivers to local users via .forward/.procmailrc/Maildir, whatever)

My Postfix config, meanwhile, has shrunk to barely anything.

But after analyzing my logs in MySQL, I still wasn't happy. I saw that almost all my spam (~98%) came from residential DHCP/DSL/Cable connections, with stupid-looking, easily-recognizable hostnames, like:
| remotehost                                        |
| pool-71-187-1-147.nwrknj.fios.verizon.net         | 
| APointe-a-Pitre-103-1-66-27.w80-8.abo.wanadoo.fr  | 
| 80-219-140-217.dclient.hispeed.ch                 | 
| dsl-189-133-79-229.prod-infinitum.com.mx          | 
| c-71-196-65-212.hsd1.fl.comcast.net               | 
| dsl-189-174-204-42.prod-infinitum.com.mx          | 
|              | 
| 201-25-211-207.fnsce702.dsl.brasiltelecom.net.br  | 
| d83-186-127-150.cust.tele2.be                     | 
| 24-159-245-214.dhcp.mdsn.wi.charter.com           | 
| aolclient-68-202-241-155.aol.cfl.res.rr.com       | 
| c-71-61-26-51.hsd1.pa.comcast.net                 | 
| i05v-87-90-179-109.d4.club-internet.fr            | 
|                    | 
| c-76-100-225-155.hsd1.md.comcast.net              | 
| dslb-088-074-000-186.pools.arcor-ip.net           | 
| pD955ED51.dip.t-dialin.net                        | 
|                       | 
| 82-42-29-1.cable.ubr02.knor.blueyonder.co.uk      | 
| client-               | 
| c-68-58-108-131.hsd1.in.comcast.net               | 
| host81-154-148-241.range81-154.btcentralplus.com  | 
| 216-199-78-234.atl.fdn.com                        | 
| 218.206.98-84.rev.gaoland.net                     | 
| cpe-65-28-157-135.bak.res.rr.com                  | 
| 82-45-185-97.cable.ubr01.chel.blueyonder.co.uk    | 
| KH222-156-64-178.adsl.dynamic.apol.com.tw         | 
| dslb-084-056-113-198.pools.arcor-ip.net           | 
|                      | 
| dsl-189-179-129-154.prod-infinitum.com.mx         | 
| cpe-24-175-188-97.stx.res.rr.com                  | 
| 78-56-100-93.ip.zebra.lt                          | 
| 75-131-161-195.dhcp.spbg.sc.charter.com           | 
| host-81-190-121-194.gdynia.mm.pl                  | 
| c9112907.rjo.virtua.com.br                        | 
| ppp-          | 
| dsl-189-144-11-85.prod-infinitum.com.mx           | 
| 201-95-47-232.dsl.telesp.net.br                   | 
| 202.31.101-84.rev.gaoland.net                     | 
| i05v-212-194-122-1.d4.club-internet.fr            | 
| 201-88-68-211.cbace700.dsl.brasiltelecom.net.br   | 
| dslb-082-083-049-211.pools.arcor-ip.net           | 
|                  | 
| cpe-76-170-82-98.socal.res.rr.com                 | 
| | 
| S01060015e97b2781.du.shawcable.net                | 
| c-24-8-151-233.hsd1.co.comcast.net                | 
| 20150032076.user.veloxzone.com.br                 | 
| host-69-221-111-24.midco.net                      | 
| 67-130-61-6.dia.static.qwest.net                  | 

So I wrote a little Perl module (to be released) that incorporates a few dozen tests and regexps, looking for the IP address (or part of it) encoded in the hostname any number of totally f'ed up ways, and also double-checking that the user isn't some home Linux user running their own mail server... if their HELO hostname resolves to their source IP, I allow an ugly reverse DNS hostname. You often can't control your reverse. And forward DNS is free, so it's not an unreasonable barrier to entry.

Check out this table:

In conclusion, a lot can be learned from the (IP, reverse DNS of that IP, declared HELO host) tuple. I'm now rejecting 50% of incoming connections at the MAIL FROM stage (have to give them a chance to AUTH after HELO), based on the (IP, reverse DNS, HELO host) alone. I reject the remaining 50% (25% of total) via spamassassin, check_earlytalker, and dsnbl, and then I queue/deliver 25% of the total email to users on danga.com.

Yay qpsmtpd!

[User Picture]From: leahculver
2007-08-14 10:54 pm (UTC)


Regular expressions to the rescue!
(Reply) (Thread)
[User Picture]From: brad
2007-08-14 10:57 pm (UTC)

Re: Hooray!

You could at least link it. :-)
(Reply) (Parent) (Thread)
[User Picture]From: leahculver
2007-08-14 11:08 pm (UTC)

Re: Hooray!

I didn't tell you, but the whole reason I made Pownce was because I couldn't figure out how to format comments (look I made a link this time).
(Reply) (Parent) (Thread)
[User Picture]From: brad
2007-08-14 11:13 pm (UTC)

Re: Hooray!

You should learn HTML ... it makes creating websites easier. :P
(Reply) (Parent) (Thread)
From: jmason
2007-08-14 11:37 pm (UTC)

some previous wheels

Of course, SA has rules that do that ;) Feel free to extract bits. You could also use SA 3.2.x's "shortcircuiting" support to run RDNS_DYNAMIC first of all, and stop SA evaluation immediately if it hits, to save the load of running the rest of the SA tests.

also, it unfortunately will hit a bit of nonspam. The current version of the RDNS_DYNAMIC rule hits about 34% of spam, and 0.4% of nonspam (so is 98% accurate).
(Reply) (Thread)
[User Picture]From: brad
2007-08-14 11:48 pm (UTC)

Re: some previous wheels

Oh, fancy!

Props on how scientific SA has gotten lately. (where lately might be last 3 years, since I haven't paid much attention in ages...)

I think it's time to learn SA a bit more.

(Reply) (Parent) (Thread)
From: jmason
2007-08-15 11:26 am (UTC)

Re: some previous wheels

Props on how scientific SA has gotten lately. (where lately might be last 3 years, since I haven't paid much attention in ages...)

Cheers! yep, it's the only way to do this stuff... I'm still working on ways to collect and analyse rule performance, and how to act on the results, but we're getting there.

I think it's time to learn SA a bit more.

that would be cool ;)
(Reply) (Parent) (Thread)
[User Picture]From: johno
2007-08-14 11:46 pm (UTC)
What are you doing in regards to the HELO domain?

Many solutions I've seen try to match the HELO domain to the MAIL FROM domain and often that is a real mismatch.

ie: My ISP opens with
HELO mail.ispname.net
MAIL FROM: johno@mydomain.org

(Reply) (Thread)
[User Picture]From: brad
2007-08-14 11:50 pm (UTC)
I explained in the post what I'm doing with it.

I only even look at it if the reverse DNS hostname looks suspicious, at which point I use the HELO hostname as a "second chance" to accept your mail... if you then have a non-suspect HELO host, and that HELO host resolves to the IP you connected from, then I'll figure you're real and let you through.
(Reply) (Parent) (Thread)
[User Picture]From: fanf
2007-08-15 07:19 am (UTC)
A really cheap way to reject junk is to look for HELO domains that match ^[0-9.-]$, or contain .. or are longer than 55 characters, or are any of your domains.
(Reply) (Parent) (Thread)
[User Picture]From: ydna
2007-08-15 05:26 pm (UTC)
Um, that RE will not match any string longer than one character.
(Reply) (Parent) (Thread)
[User Picture]From: fanf
2007-08-15 05:29 pm (UTC)
Oops, yes - insert a * in the obvious place.
(Reply) (Parent) (Thread)
From: evan
2007-08-14 11:52 pm (UTC)
| | bugs.haskell.org | www.haskell.org | queue | NULL |

Wonder who that is...
(Reply) (Thread)
[User Picture]From: brad
2007-08-14 11:56 pm (UTC)
For you, apparently....

mysql> select rcptto, count(*) from connlog where
    remotehost like '%haskell%' group by 1;
| rcptto              | count(*) |
| NULL                |        1 |
| <martine@danga.com> |       87 |
(Reply) (Parent) (Thread)
[User Picture]From: ydna
2007-08-15 01:39 am (UTC)
namosi: Maybe Brad should make some packages to make it easier for other people to setup the same thing for themselves.

ydna: Right. Because that's why Brad's here; to make everyone else's life easier.

namosi: ioctl(fd, SETDUMBBLOND, 1); Yes. Of course.
(Reply) (Thread)
[User Picture]From: brad
2007-08-15 01:53 am (UTC)
Don't worry, I'll be releasing it. Just needs some polish.
(Reply) (Parent) (Thread)
From: jamesd
2007-08-15 07:50 am (UTC)
You're right about that being handy. I wrote a direct to MX blocker for the Windows spam filter SpamPal and found it helpful, though I used a range of DNS servers that return residential or not status information.
(Reply) (Thread)
[User Picture]From: ghewgill
2007-08-15 07:55 am (UTC)
I use the zen.spamhaus.org list which includes the PBL list, which is a self-opt-out blacklist of dynamic consumer addresses. The zen list blocks about 90% of my incoming connections, which is awesome. You can see my daily stats since 00:00Z for its effect.
(Reply) (Thread)
[User Picture]From: wcu
2007-08-15 11:28 pm (UTC)
that is too funny

ive been doing practically the same thing for the past 3 days
(Reply) (Thread)
[User Picture]From: grumpy_sysadmin
2007-08-17 01:22 am (UTC)

This is a good heuristic, but...

... I already reject more than 50% of incoming SMTP connections at SMTP time by way of "that's not a valid user".

(I trust Postfix was already doing that for you and you've moved on, of course.)

PS, yes, my SMTP backup relays have my current valid-user list.
(Reply) (Thread)