#! /usr/local/bin/perl5 -w # Postfix POLICY DAEMON for anti-Verisign-wildcarding # Jason Fesler Sep 15 2003 # This REQUIRES postfix-2.0.14-20030717 # This will look at the sender address (not the connecting host IP, # but the "MAIL FROM: <...>" field). It will then compare the resolved # IP address against an intentionally bad domain name lookup. # If they resolve to the same IP (or set of IP's) then the mail # will be rejected. # Usage: Put this in your master.cf: #mfpitgdav unix - n n - - spawn # user=nobody argv=/etc/postfix/extras/mfpitgdav.pl # And in your smtpd_recipient_restrictions, add # check_policy_service unix:private/verisign # Parts of this code are based on Weitse's greylist filter. # Don't hold him responsible for my code however. use Sys::Syslog qw(:DEFAULT setlogsock); use Socket; use Data::Dumper; use strict; use vars qw(%attr); # Current request use vars qw($verbose); use vars qw(%BANNED); my ($syslog_socktype,$syslog_facility,$syslog_options,$syslog_priority) = ("unix","mail","pid","info"); # Setup/Config on what to block # Mark $BANNED{ipaddress}="your reject message" to configure. my $fakename = get_fake_domain(); foreach (get_ip($fakename)) { $BANNED{$_} = "Not a real domain name {domain} (resolved to $_, verisign)"; } sub smtpd_access_policy { my $domain; my $ip; my @ip; if ($attr{"recipient"} =~ m/postmaster\@/i) { return "dunno"; } if ($attr{'sender'} =~ m/@([^@]+)$/) { $domain = $1; } else { # We don't have a domain name on the sender address. # There are other ways to check this sort of problem - # this script is designed to test for one specific type of # problem (where DNS resolves even on bad domain names). return "dunno"; } @ip = get_ip($domain); foreach $ip (@ip) { if ($BANNED{$ip}) { my $message = $BANNED{$ip}; $message =~ s/{domain}/$domain/g; return "REJECT $message"; }; }; return "dunno"; } # # Log an error and abort. # sub fatal_exit { my($first) = shift(@_); ########################################################## # In my opinion, it would be better to say "dunno" # # instead of cause errors to postfix. To that end, we # # say so, before exiting. # ########################################################## print "action=dunno\n\n"; syslog "err", "fatal: $first", @_; if (-t STDOUT) { print STDOUT "fatal: $first\n"; # if ran interactively.. lets see it! } exit 1; } # # This process runs as a daemon, so it can't log to a terminal. Use # syslog so that people can actually see our messages. # setlogsock $syslog_socktype; openlog $0, $syslog_options, $syslog_facility; # # We don't need getopt() for now. # foreach (@ARGV) { if ($_ eq "-v") { $verbose=1; } else { fatal_exit("Invalid option: $_, usage: $0 [-v]"); } } # # Unbuffer standard output. # select((select(STDOUT), $| = 1)[0]); # # Receive a bunch of attributes, evaluate the policy, send the result. # while () { if (/([^=]+)=(.*)\n/) { $attr{substr($1, 0, 512)} = substr($2, 0, 127); } elsif ($_ eq "\n") { if ($verbose) { for (keys %attr) { syslog $syslog_priority, "Attribute: %s=%s", $_, $attr{$_}; } } $attr{'request'} ||= 'missing'; fatal_exit "unrecognized request type: $attr{request}" unless $attr{"request"} eq "smtpd_access_policy"; my $action = smtpd_access_policy(); syslog $syslog_priority, "Action: %s", $action if $verbose; print STDOUT "action=$action\n\n"; %attr = (); } else { chop; syslog $syslog_priority, "warning: ignoring garbage: %.100s", $_; } } sub breakpoint { $DB::single = 2; $DB::single = 2; # Avoid warnings } sub get_ip { my ($lookup) = @_; my ($name,$aliases,$addrtype,$length,@addrs) = gethostbyname($lookup); my (@ascii) = map { inet_ntoa($_) } @addrs; return @ascii; } sub get_fake_domain { my($string) = "fake.net"; foreach (1..30) { $string = (chr(65+int(rand(26)))) . "$string"; } return $string; }