apf 把垃圾给我挡在门外,但别的千万别挡呀 _-!!

#!/usr/bin/perl -w
# vim: set cindent expandtab tabstop=4 shiftwidth=4:
# apf-qmail.pl: An APF client client script to query APF Server
# using TCP/UDP protocol, written in pure perl.
#
# Feature: for qmail+apf_patch only, TCP/UDP protocol.
# Author: He zhiqiang <hzqbbc@damail.cn>
#
# Written in: 2004-05-08
# Rewrite in: 2005-06-14
#
# Usage: $0 [ protocol_state ] [ protocol_name ] [ helo_name ] [ sender ] [ recipient ] [ client_address]# Example: $0 RCPT SMTP mta.domain.tld foo@domain.tld bar@recpdomain.com 10.1.1.1
#
# Caution: under UDP mode, TIMEOUT should set high enough, to wait for the
# reply from server, WHERE blogid=151 higher will benefit for problematic
# network connection.
# ————————————————————————-
# Under TCP mode, please keep your active concurrency less than 5,
# or APF Server may refuse your connection.
#
# License: GPL v2
# /usr/local/bin/use.perl port <- ʹϵͳʹÓÃÐÂ×°µÄperl Ò»¶¨ÒªÊ¹Óà perl 5.6+ FreeBSD °æ±¾µÄ package Òª¶ÔÓ¦ÏÂÔØ°æ±¾
use strict;
use IO:Socket qw(DEFAULT);
use constant MAX_MSG_LEN => 1000;
use constant TIMEOUT => 30;
use constant MAX_RETRIES => 3;
use constant VERSION => 0.20;
use POSIX qw(strftime);
my $apf_ok =0;
my $apf_err =1;
my $log_file =’/tmp/apf_log_’;

# unbuffer output
select((select(STDOUT), $| = 1)[0]);
select((select(STDIN), $| =1)[0]);

# command line parameters and praser
use vars qw($proto $host $port $verbose);
$proto = ‘tcp’; # change to udp if you impact some tcp problem.
$host = ‘ns.apf.org.cn’;
$port = ’10030′;
$verbose = 1; # set to 1 if you want some debug

my $msg_out = undef;
my $action = undef;
$0 =~ s/.*/(S+)/$1/g;

&usage() if ( $#ARGV < 5 );

sub usage {
print “$0 [ protocol_state ] [ protocol_name ] [ helo_name ] [ sender ] [ recipient ] [ client_address]n”;
exit(255);
}

sub dolog {
my $hostname = `hostname`;
chomp($hostname);
open(FD, “>> $log_file”.(strftime “%Y%m”, localtime));
print FD (strftime “%b %e %H:%M:%S”, localtime) ,” $hostname $0[$$]: @_n”;
close(FD);
}

$msg_out =”request=smtpd_access_policyn”;
$msg_out .=”protocol_state=$ARGV[0]n”;
$msg_out .=”protocol_name=$ARGV[1]n”;
$msg_out .=”client_address=$ARGV[5]n”;
$msg_out .=”client_name=unknown”;
$msg_out .=”helo_name=$ARGV[2]n”;
$msg_out .=”sender=$ARGV[3]n”;
$msg_out .=”recipient=$ARGV[4]n”;
$msg_out .=”queue_id=nn”;

# XXX APF customize code runs here
$action=customize(map { /([^=]+)=(.*)/ } split(/n/, $msg_out));
if ($action eq ‘DUNNO’) {
# XXX APF PROC here
$action=smtpd_access_policy($msg_out);
}

dolog(“sent=(state=$ARGV[0] name=$ARGV[1] helo_name=$ARGV[2] sender=<$ARGV[3]> recipient=<$ARGV[4]> client_address=[$ARGV[5]]) recv=($action)”);
$_ = $action;

#在此加上 WHITE 返回的判断
if ( /^OK/i or /^DUNNO/i or /^WHITE/i ) {
exit($apf_ok);
} else {
# XXX return error msg to smtp client via stdout
print $action, “rn”;
exit($apf_err);
}

sub customize {
my %attr = @_;
# XXX do some job here and return some action
# if something match some rule

#自己加上的白名单查询
my $blfile = “/var/qmail/control/ip_white_list.txt”;
if( -r $blfile) {
open(FD, “< $blfile”) or die “Can’t open $blfile:$! n”;
my $line;
while ($line = <FD>) {
$line =~ s/r//g;# strip r if exists
chomp($line);
# if(/^$attr{client_address}$/) {
if(check_ip($line, $attr{client_address})==0){
return “WHITE”;
}
}
close FD;
}

# a simple ip blacklist implemention
$blfile = “/etc/apf/ip_black_list.txt”;
if( -r $blfile) {
open(FD, “< $blfile”.strftime “%Y%m%d”, localtime) or die “Can’t open $blfile:$! n”;
while (<FD>) {
s/r//g;# strip r if exists
chomp;
if(/^$attr{client_address}$/) {
return “554 Access denied, reason: $attr{client_address} blocked by local black list”;
}
}
close FD;
}

return ‘DUNNO’;
}

sub smtpd_access_policy {
my $data;
my ($msg_out) = @_;
my $loop = 0;
my $sock;
my $srvip;
# XXX simple DNS balance mechanism
my @addr = gethostbyname($host);
@addr = map { inet_ntoa($_) } splice(@addr, 4);
my $arrlen = scalar @addr;
my $random = int rand($arrlen-1);
do {
$loop++;
$srvip = $addr[$random++ % $arrlen];
dolog(“starting query to $proto:[$srvip:$port] ($loop)”) if $verbose;

$sock = IO:Socket::INET->new(
Proto => $proto,
PeerHost => $srvip,
PeerPort => $port,
Type => $proto eq ‘tcp’ ? SOCK_STREAM :
($proto eq ‘udp’ ? SOCK_DGRAM : undef),
Timeout => ’120′);
} until ($loop >= $arrlen-1 || $sock);
dolog(“Error on exec $0: $@”) and exit(254) if(!$sock);

# check proto
if($proto eq ‘tcp’) {
print $sock “$msg_outn”;
while(<$sock>) {
$data = (defined $data ? $data : “”) . $_ if ($_ ne “n”);
last if ($_ eq “n”);
}
close($sock);
# now $data contain only one n at the end.
$data .=”n”;# append one
}elsif($proto eq ‘udp’) {
my $retries = 0;
do {
$sock->send(“$msg_outn”) or die “send() failed: $!n”;
eval {
local $SIG{ALRM} = sub { ++$retries and die “timeoutn” };
alarm(TIMEOUT);
$sock->recv($data, MAX_MSG_LEN) or die “recv() failed: $!n”;
alarm(0);
};
dolog(“Retrying…$retriesn”) if $retries;
} while $@ eq “timeoutn” and $retries < MAX_RETRIES;
if ($data eq ”) {# means all retries failed
$data = ‘DUNNO’;# return DUNNO to let mail goes
}
}

# XXX fix a bug here, should strip all newlines
$data =~ s/^action=(.*)nn/$1/g;
return ‘DUNNO’ if not defined $data;
return “$data”;
}
#自己加的 IP 查询,支持 *,1-255 两种通配符
sub check_ip{
my $ip = shift;
my $cip = shift;
my $limit = 0;
my $max = 0;
my $cp;
if($cip =~ /^s*(d{0,3}).(d{0,3}).(d{0,3}).(d{0,3}|d{0,3}-d{0,3})s*$/){
$cip = $1*1000000000 + $2*1000000 + $3*1000 + $4;
}else{ return -1; }
if ($ip =~ /^s*(d{0,3}).(d{0,3}).(d{0,3}).(d{0,3}|d{0,3}-d{0,3}|*)s*$/){
$cp = $4;
$max = $limit = $1*1000000000 + $2*1000000 + $3*1000;
if($cp eq “*”){ $cp = “1-255″; }
if($cp =~ /^(d{0,3})-(d{0,3})/){
$limit += $1;
$max += $2;
if($cip > $limit && $cip < $max){ return 0; } else { return -1; }
return -3;
}else{
$max += $4;
if($cip == $max){ return 0; } else { return -1; }
}
}else{return -2;}
}
__END__

APF-qmail, a patch/addon client for qmail to query APF Server. Please
visit http://apf.org.cn/ for detail and welcome to join our development.