grinch logo

What is Grinch?

Grinch is a small script written in Perl which can be used to check whether a given address is an open mail relay host. It can operate as a daemon and speaks the tcp_map protocol implemented in Postfix in that case. It's also possible to use it in arbitrary shell script environments where the hostname is read from stdin and the error code is written to stdout.
If you got to work it with another MTA than Postfix, please let me know.
The goal is to check "on the fly" whether a host accepts mail to a given destination it actually may not relay for. If it does, Grinch waits for the sent mail to return to you for a configurable period and meanwhile returns a soft error (450) for that host.
If the mail returns, the suspected host was verified as an open open relay and will be rejected until its cache entry times out.

Prerequisites

Grinch needs the following components installed on your system to work:

Perl, the glue of the internet
Net::Server::PreForkSimple
Net::SMTP
Sys::Syslog
Cache::Cache

Download

You can download the latest version of Grinch here.
Grinch is released under the terms of the GNU public licence.

Configuration

In the uppermost lines of grinch.pl, there is a number of things to configure before you start.

$port The port Grinch listens to in daemon mode. Should go with your MTA config, of course.
$max_servers The number of server instances Grinch preforks in daemon mode. Find a suitable value for your setup.
$helo When checking a mail host, send this as HELO string.
$smtp_timeout specify a useful timeout value for external MTA communication.
$addr When checking a mail host, try to send a mail to this destination and also use this address as sender identification. This should of course be a string a resonably configured mail servers rejects as recipient address.
Please note that mail to this address always needs to get through your mailserver rules, without asking Grinch about it. Otherwise, evidences for open relays will never reach Grinch which makes the whole thing useless.
$exceptions Important! Don't forget to add hosts to this regular expression which should never be checked at all.
Please note that you might deadlock your complete mail server setup by for example not adding your backup MX servers here!
$spooldir Grinch needs a spool directory for putting its cache database files and other stuff. Give it one.
$cache_timeout Timeout for cached values, in seconds.
$open_relay_timeout Once an open relay was found, store it in the cache for this time period. This may be a significantly bigger value than $cache_timeout
$mail_timeout Timeout for relay check mail, in seconds. Please note that open relays are often discovered by spammers quickly and therefore mostly suffer a pretty high load which means the have long latency for mail delivery. Thus, don't be too moderate here.
$user Specify a local user here. Grinch will setreuid() to this user for obvious reasons. Please see the debug section as well.
$group Specify a group here. Grinch will setregid() to this group.
$background When called in daemon mode, the server process backgrounds itself if set to "1"
$bindhost For daemon mode, specify the local address to bind to. You should know what you do if you change this to something different than the default value.
$code In case a host is not an open relay, return this as error code. Please note that leaving this on its default "OK" requires this check to be the last one in your chain in Postfix, since this always makes an "decision" then. Otherwise, set it to "DUNNO".

When used with Postfix, the simplest way to use Grinch is to add this to your main.cf:
smtpd_client_restrictions =
        [...]
        tcp:localhost:port
or, in any rule, add
check_client_access tcp:localhost:port
Where port is the port number you specified before.
Put this line after a rule in which you explicitly allow the relay check mails to arrive in any case. Please note that it makes sense to add some RBL services above since lookups to them take less time than using Grinch.
To let Grinch know about returned mails, somehow pipe the whole mail to "grinch.pl report".
The easiest way to do this is probably by adding the following line to your aliases file:
relaycheck: "|/path/to/grinch.pl report"
Where relaycheck is the local part of the email address you specified in $to.
See command line options for details.

The control channel

When started in daemon mode, Grinch listens on a configurable tcp port to serve requests. The protocol spoken on this port is postfix' tcp_map lookup protocol and is as simple as effective. There's always one line for a request which always has the syntax get hostname.
When started as one timer script, it simply reads the hostname or ip address to check from stdin. However, the response it the same in both cases.
Each request is followed by an answer from the server an can be one of the following:

200 OK (error in request) The hostname in the request was malformed. This should never happen.
200 OK (whitelisted) The hostname given in the request matches the $exception expression.
200 OK (no connection) Connection to the given host could not be established. Either the hostname is unresolvable or there is no MTA running.
200 OK (checked) Grinch confirmed that the requested host is NOT an open relay, so the MTA may continue serving the external request.
200 REJECT (open relay) Host was checked bad before and is stored in the cache.
200 OK (avoding deadlocks) Two servers both doing relay checks are inclined to dead lock each other. This is Grinch's circumvention.
200 450 DEFER The host accepted a relay check mail before and Grinch still waits for it to arrive.
500 UNKNOWN Postfix always tries to lookup the DNS name first. If there is no DNS name, postfix sends the string "unknown".
In that case, Grinch answers with 500, which makes postfix send the IP address as request. /* no comment */

Checking mail hosts

When checking a mail host, Grinch tries to establish a connection on port 25, then sends a common EHLO/HELO greeting and tells the counterpart that the mail comes from $addr, so Grinch also gets the bounces. Next, the RCPT TO command is sent with the value you specified in $addr, followed by a DATA including the hostname and a unique token in the body to avoid cache poisoning.
If any of these steps failed, the checked host is considered to be "safe", otherwise, it's suspected to be an open relay.
In any case, there is a cache entry beeing made to avoid checking hosts too often.

Command line options

Grinch accepts the following command line arguments:
start Makes Grinch to start up in daemon mode. It backgrounds itself it you told it to do so.
stop Stop a running Grinch daemon.
report In this mode, Grinch accepts a mail formerly sent for relay check reasons on stdin. If Grinch finds a line including a hostname with a valid token which was given out formerly, this host is confirmed to be an open relay and is stored as such in the cache.
lookup When using Grinch as one time checker without daemon functionality, this is for you. It reads a hostname from stdin an returns the error code on stdout in that mode.

Debugging

Please note that when using the daemon mode together with Postfix, there must be tcp_map support for your Postfix package. You can find out which map types your setup knows about by calling postconf -m.
Another thing that frequently causes trouble is the permission issue: When using grinch as daemon, it writes its cache files with the user and group permission to $spooldir. The process called later to report received mails mostly runs with different permissions. Please make sure that they both have permissions to write to the cache repository. The easiest way is probably to make Grinch run as the same user than the one which does local delivery.

Squealing

Once you found an open relay, please let the RBL services know. Here are two that accept contributions:
DSBL
ORDB

Contact

There is no mailing list yet and I'm not sure whether there will ever be one.
So, in case of any questions, suggestions, improvements or other contributions, don't hesitate to mail me:

grinch (at) zonque (dot) org


Last change: 31.1.2004

Valid XHTML 1.0!