--- policyd-weight-0.1.14.2.orig Mon Feb 26 20:26:42 2007 +++ policyd-weight-0.1.14.2 Wed Feb 28 20:09:17 2007 @@ -74,7 +74,7 @@ use Config; use POSIX; -use vars qw($csock $s $tcp_socket $sock $new_sock $old_mtime); +use vars qw($csock $s $listening_socket $sock $new_sock $old_mtime); our $VERSION = "0.1.14 beta2"; our $CVERSION = 5; # cache interface version @@ -307,7 +307,25 @@ my $DEBUG = 0; # 1 or 0 - don't comment -my $REJECTMSG = "550 Mail appeared to be SPAM or forged. Ask your Mail/DNS-Administrator to correct HELO and DNS MX settings or to get removed from DNSBLs"; +my $OUTPUT_FORMAT = "Postfix"; # Output format type. Possible values: + # + # "Postfix": + # Write output in format understood by + # Postfix. This is the default. + # + # "Exim": + # Write output in format easily parsed by + # Exim's ${extract{}} function. You will of + # course have to feed the data in proper format + # to policyd-weight. + # Rate is multiplied by 10, as Exim's ${eval{}} + # supports only integer numbers. Fields for + # ${extract{}} are named accordingly: + # "rate", "action" and "message". + +my $REJECT_ACTION = '550'; + +my $REJECTMSG = "Mail appeared to be SPAM or forged. Ask your Mail/DNS-Administrator to correct HELO and DNS MX settings or to get removed from DNSBLs"; my $REJECTLEVEL = 1; # Mails with scores which exceed this # REJECTLEVEL will be rejected @@ -332,7 +350,9 @@ # rejected # DEFAULT: 5 -my $DNSERRMSG = '450 No DNS entries for your MTA, HELO and Domain. Contact YOUR administrator'; +my $ACCEPT_ACTION = 'DUNNO'; + +my $DNSERRMSG = 'No DNS entries for your MTA, HELO and Domain. Contact YOUR administrator'; my $dnsbl_checks_only = 0; # 1: ON, 0: OFF (default) @@ -357,7 +377,7 @@ # DNSBLs is ABOVE this # level, reject immediately -my $MAXDNSBLMSG = '550 Your MTA is listed in too many DNSBLs'; +my $MAXDNSBLMSG = 'Your MTA is listed in too many DNSBLs'; ## RHSBL settings my @rhsbl_score = ( @@ -396,7 +416,7 @@ my $CACHEMAXSIZE = 4000; # at this number of entries cleanup takes place -my $CACHEREJECTMSG = '550 temporarily blocked because of previous errors'; +my $CACHEREJECTMSG = 'Temporarily blocked because of previous errors'; my $NTTL = 1; # after NTTL retries the cache entry is deleted @@ -411,7 +431,7 @@ my $POSCACHEMAXSIZE = 2000; # at this number of entries cleanup takes place -my $POSCACHEMSG = 'using cached result'; +my $POSCACHEMSG = 'Using cached result'; my $PTTL = 60; # after PTTL requests the HAM entry must # succeed one time the RBL checks again @@ -435,7 +455,7 @@ my $MAXDNSERR = 3; # max error count for unresponded queries # in a complete policy query -my $MAXDNSERRMSG = 'passed - too many local DNS-errors'; +my $MAXDNSERRMSG = 'Passed - too many local DNS-errors'; my $PUDP = 0; # persistent udp connection for DNS queries. # broken in Net::DNS version 0.51. Works with @@ -477,7 +497,7 @@ # DEFAULT: on -my $DEFAULT_RESPONSE = 'DUNNO default'; # Fallback response in case +my $DEFAULT_RESPONSE = 'Default'; # Fallback response in case # the weighted check didn't # return any response (should never # appear). @@ -520,6 +540,11 @@ # here. Default is '127.0.0.1'. # You need to restart policyd-weight if you # change this. + # + # You can also specify a path to an UNIX + # domain socket here - in this case, make sure it + # starts with a slash ('/'). $TCP_PORT is not + # used for UNIX sockets. my $SOMAXCONN = 1024; # Maximum of client connections # policyd-weight accepts @@ -626,7 +651,15 @@ $PIDFILE .= ".debug"; } - print "debug: using port ".++$TCP_PORT."\n"; + if ( $BIND_ADDRESS && $BIND_ADDRESS =~ m!^/! ) + { + $BIND_ADDRESS .= ".debug"; + print "debug: using socket path ".$BIND_ADDRESS."\n"; + } + else + { + print "debug: using port ".++$TCP_PORT."\n"; + } print "debug: USER: $USER\n"; print "debug: GROUP: $GROUP\n"; print "debug: issuing user: ".getpwuid($<)."\n"; @@ -700,22 +733,10 @@ mylog(debug=>"startup: using $conf"); } -my $RETANSW; - -if($ADD_X_HEADER == 1) -{ - $RETANSW = "PREPEND X-policyd-weight:"; -} -else -{ - $RETANSW = "DUNNO "; -} - if($conf_err) { mylog(warning=>"conf-err: ".$conf_err); mylog(warning=>"conf-err: falling back to builtin defaults"); - $RETANSW = $RETANSW." using builtin defaults due to config-error"; } @@ -872,9 +893,16 @@ create_lockpath("daemon"); - if($BIND_ADDRESS && $BIND_ADDRESS !~ /^[ \t]*all[ \t]*$/i) + if($BIND_ADDRESS && $BIND_ADDRESS =~ m!^/!) + { + $listening_socket = IO::Socket::UNIX->new( Type => SOCK_STREAM, + Listen => $SOMAXCONN, + Local => $BIND_ADDRESS) or + die "master: bind $BIND_ADDRESS: $@ $!"; + } + elsif($BIND_ADDRESS && $BIND_ADDRESS !~ /^[ \t]*all[ \t]*$/i) { - $tcp_socket = IO::Socket::INET->new( Proto => 'tcp', + $listening_socket = IO::Socket::INET->new( Proto => 'tcp', LocalHost => $BIND_ADDRESS, LocalPort => $TCP_PORT, Listen => $SOMAXCONN, @@ -884,7 +912,7 @@ } else { - $tcp_socket = IO::Socket::INET->new( Proto => 'tcp', + $listening_socket = IO::Socket::INET->new( Proto => 'tcp', LocalPort => $TCP_PORT, Listen => $SOMAXCONN, Reuse => 1, @@ -894,8 +922,9 @@ # XXX: do we really need that? I used it for a chance of closing # sockets when spawning caches and the like. - fcntl($tcp_socket, F_SETFD, FD_CLOEXEC); + fcntl($listening_socket, F_SETFD, FD_CLOEXEC); + fcntl($listening_socket, F_SETFD, O_NONBLOCK); open(PF, ">".$PIDFILE) or die $!; @@ -977,8 +1006,8 @@ my $readable_handles = new IO::Select(); my $new_tcp_readable; - $tcp_socket->autoflush(1); - $readable_handles->add($tcp_socket); + $listening_socket->autoflush(1); + $readable_handles->add($listening_socket); my $waitedpid; my $parentpid = $$; @@ -1041,12 +1070,12 @@ # process socket data foreach my $sock (@$new_tcp_readable) { - if($sock == $tcp_socket) + if($sock == $listening_socket) { # let childs handle it if they are available if (keys(%avail) > 0) { - $readable_handles->remove($tcp_socket); + $readable_handles->remove($listening_socket); next; } @@ -1059,7 +1088,7 @@ } $max_proc_msg = 1; - $readable_handles->remove($tcp_socket); + $readable_handles->remove($listening_socket); next; } @@ -1083,7 +1112,7 @@ { $pipes{$pid} = $child; $readable_handles->add($pipes{$pid}); - $readable_handles->add($tcp_socket); + $readable_handles->add($listening_socket); $parent->close; $childs{$pid} = 1; $avail{$pid} = 1; @@ -1148,7 +1177,7 @@ my $readable_handles = new IO::Select(); $readable_handles->add($parent); - $readable_handles->add($tcp_socket); + $readable_handles->add($listening_socket); close $child; my $tout = $CHILDIDLE; @@ -1193,9 +1222,9 @@ { $select_to = 0; my $ans; # define for the "for"-scope - if($sock == $tcp_socket) + if($sock == $listening_socket) { - my $new_sock = $tcp_socket->accept(); + my $new_sock = $listening_socket->accept(); if(!($new_sock) || (!($new_sock->connected))) { @@ -1275,7 +1304,7 @@ for($readable_handles->handles) { - next if $_ == $tcp_socket or $_ == $parent; + next if $_ == $listening_socket or $_ == $parent; $connected = 1; } @@ -1292,7 +1321,7 @@ } } - $readable_handles->add($tcp_socket); + $readable_handles->add($listening_socket); print $parent ("$$ 1\n"); $parent->recv($ans, 1024); $tout = $CHILDIDLE; @@ -1324,7 +1353,7 @@ print $sock ("y\n"); delete $childs{$cpid}; delete $avail{$cpid}; - $readable_handles->add($tcp_socket); + $readable_handles->add($listening_socket); } else { @@ -1344,11 +1373,11 @@ } if(keys(%avail) > 0) { - $readable_handles->remove($tcp_socket); + $readable_handles->remove($listening_socket); } elsif(keys(%childs) < $MAX_PROC) { - $readable_handles->add($tcp_socket); + $readable_handles->add($listening_socket); } print $sock ("1\n"); next; @@ -1451,7 +1480,7 @@ if(index($ip,":") != -1) { - return ('DUNNO IPv6'); # we have no IPv6 support for now + return (format_output('ACCEPT', 'IPv6')); # we have no IPv6 support for now } my $client_name = $attr{client_name} || ''; @@ -1466,25 +1495,27 @@ } if($from eq '') { - return('DUNNO NULL (<>) Sender'); + return(format_output('ACCEPT', 'NULL (<>) Sender')); } my $orig_from = $from; if($attr{recipient} && $attr{recipient} =~ /^(postmaster|abuse)\@/) { - return('DUNNO mail for '.$attr{recipient}); + return(format_output('ACCEPT', 'mail for '.$attr{recipient})); } if(($attr{instance}) && ($attr{instance} eq $accepted)) { - return ('DUNNO multirecipient-mail - already accepted by previous query'); + return (format_output('ACCEPT', 'Multirecipient-mail - already accepted by previous query')); } elsif(($attr{instance}) && ($attr{instance} eq $blocked)) { - return ($my_REJECTMSG.' (multirecipient mail)'); + return (format_output('REJECT', $my_REJECTMSG.' (multirecipient mail)')); } ## cache check + + # @TODO@ - get rate from cache if( ($CACHESIZE > 0) || ($POSCACHESIZE > 0) ) { $cansw = cache_query('ask', $ip, '0', $orig_from, $from_domain); @@ -1494,14 +1525,14 @@ if($cansw && index($cansw, 'rate') != 0) { $blocked = $instance; - $my_REJECTMSG = $cansw; + $my_REJECTMSG = $cansw; - return($my_REJECTMSG); + return(format_output('REJECT', $cansw)); } elsif($cansw && index($cansw, 'rate:hard:') == 0) { $accepted = $instance; - return("$RETANSW $POSCACHEMSG; $cansw"); + return('ACCEPT', "$POSCACHEMSG; $cansw"); } ## startup checks and preparing ############################################### @@ -1569,7 +1600,7 @@ if($maxdnserr-- <= 1) { $accepted = $instance; - return "$RETANSW $MAXDNSERRMSG in ".$dnsbl_score[$i].' lookups'; + return (format_output('ACCEPT', "$MAXDNSERRMSG in ".$dnsbl_score[$i].' lookups')); } $RET .= ' '.$dnsbl_score[$i+3].'=ERR('.$dnsbl_score[$i+2].')'; $rate += $dnsbl_score[$i+2]; @@ -1636,22 +1667,23 @@ } $blocked = $instance; mylog(info=>"weighted check: $RET, rate: $rate"); - return($MAXDNSBLMSG.'; check http://rbls.org/?q='.$ip); + return(format_output('REJECT', $MAXDNSBLMSG.'; check http://rbls.org/?q='.$ip, $rate)); } } } if($dnsbl_checks_only == 1) { - return("DUNNO only DNSBL check requested"); + return(format_output('ACCEPT', "only DNSBL check requested", $rate)); } ## postive cache check + # @TODO@ - get rate from cache if($cansw && ($POSCACHESIZE > 0) && ($dnsbl_hits < 1)) { $accepted = $instance; - return("$RETANSW $POSCACHEMSG; $cansw"); + return(format_output('ACCEPT', "$POSCACHEMSG; $cansw")); } @@ -1700,7 +1732,7 @@ if($maxdnserr-- <= 1) { $accepted = $instance; - return("$RETANSW $MAXDNSERRMSG in $MATCH_TYPE MX lookups for $testhelo"); + return(format_output('ACCEPT', "$MAXDNSERRMSG in $MATCH_TYPE MX lookups for $testhelo")); } next; @@ -1721,7 +1753,7 @@ if($maxdnserr-- <= 1) { $accepted = $instance; - return("$RETANSW $MAXDNSERRMSG in $MATCH_TYPE MX -> A lookups"); + return(format_output('ACCEPT', "$MAXDNSERRMSG in $MATCH_TYPE MX -> A lookups")); } next; } @@ -1783,7 +1815,7 @@ if($maxdnserr-- <= 1) { $accepted = $instance; - return("$RETANSW $MAXDNSERRMSG in $MATCH_TYPE A lookup for $testhelo"); + return(format_output('ACCEPT', "$MAXDNSERRMSG in $MATCH_TYPE A lookup for $testhelo")); } next; } @@ -2286,8 +2318,8 @@ if($maxdnserr-- <= 1) { $accepted = $instance; - return ("$RETANSW $MAXDNSERRMSG in " . - $rhsbl_score[$i].' lookups'); + return (format_output('ACCEPT', "$MAXDNSERRMSG in " . + $rhsbl_score[$i].' lookups')); } next; } @@ -2328,7 +2360,7 @@ if(($dnserr == 1) && ($dnsbl_hits < 2)) # applies if not too { # much dnsbl listed $DNSERRMSG .= ' Your HELO: '.$helo.', IP: '.$ip; - return($DNSERRMSG); + return(format_output('DEFER', $DNSERRMSG)); } if($rate >= $REJECTLEVEL) @@ -2375,9 +2407,9 @@ '; MTA helo: '.$helo.', MTA hostname: ' . $client_name.'['.$ip.'] (helo/hostname mismatch)'; - return($EREJECTMSG.$RHSBLMSG.$RELAYMSG.$DYN_DNS_MSG); + return(format_output('REJECT', $EREJECTMSG.$RHSBLMSG.$RELAYMSG.$DYN_DNS_MSG, $rate)); } - return($my_REJECTMSG.$RHSBLMSG.$RELAYMSG.$DYN_DNS_MSG); + return(format_output('REJECT', $my_REJECTMSG.$RHSBLMSG.$RELAYMSG.$DYN_DNS_MSG, $rate)); } else { @@ -2386,11 +2418,85 @@ cache_query('padd', $ip, $rate, $orig_from, $from_domain); } $accepted = $instance; - return("$RETANSW $RET, rate: $rate"); + return(format_output('ACCEPT', "$RET, rate: $rate", $rate)); } } +# +# format_output (ACTION, MESSAGE_STRING, RATE) +# +# Format the output according to output type setting. +# ACTION can be one of: ACCEPT, REJECT, DEFER. +# +# MESSAGE_STRING +# +# If rate is not defined, a "NULL" string or undef +# should be passed as an argument +sub format_output +{ + my @wtf = ( 'ACCEPT', 'Good heavens, what happened?!?' ); + + my $action = shift(@_) or return (format_output(@wtf)); + my $message = shift(@_) or return (format_output(@wtf)); + my $rate = shift(@_); + + if ( $OUTPUT_FORMAT eq 'Exim' ) + { + my $rate_out; + + if ( not defined $rate or $rate eq "NULL" ) + { + $rate_out = ""; + } + else + { + # Exim is only capable of integer calculations, + # so we need to output the rate in a form it + # can understand. + $rate_out = sprintf ' rate="%.f"', $rate*10; + } + + # Sanitize and format: + return sprintf ('action="%s" message="%s"%s', + quotemeta($action), + quotemeta($message), + $rate_out ); + } + elsif ( $OUTPUT_FORMAT eq 'Postfix' ) + { + my $action_out; + + if ( $action eq 'ACCEPT' ) + { + if($ADD_X_HEADER == 1) + { + $action_out = "PREPEND X-policyd-weight:"; + } + else + { + $action_out = "DUNNO "; + } + } + elsif ( $action eq 'REJECT' ) + { + $action_out = $REJECT_ACTION; + } + elsif ( $action eq 'DEFER' ) + { + $action_out = $DEFER_ACTION; + } + else + { + return (format_output(@wtf)); + } + return (sprintf('%s %s', $action_out, $message)); + } + else + { + return undef; + } +} # @@ -2586,7 +2692,7 @@ $res = '' if $res; $sock->close if $sock; $new_sock->close if $new_sock; - $tcp_socket->close if $tcp_socket; + $listening_socket->close if $listening_socket; $SIG{__DIE__} = sub { die @_ if index($_[0], 'ETIMEOUT') == 0;