From 229f91b71f4b4c5bda5b4a56b3ee120ba835d0d3 Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Mon, 14 Apr 2008 08:30:20 +0000 Subject: [PATCH 001/159] adjust copyright year --- debian/README.Debian | 5 ++ debian/changelog | 5 ++ debian/compat | 1 + debian/control | 22 ++++++ debian/copyright | 36 +++++++++ debian/default | 15 ++++ debian/docs | 2 + debian/example-cfg.txt | 108 +++++++++++++++++++++++++++ debian/example-cfg2.txt | 103 +++++++++++++++++++++++++ debian/init.d | 101 +++++++++++++++++++++++++ debian/patches/00list | 1 + debian/patches/10_fix_manpage.dpatch | 21 ++++++ debian/postinst | 57 ++++++++++++++ debian/postrm | 53 +++++++++++++ debian/rules | 47 ++++++++++++ debian/watch | 2 + 16 files changed, 579 insertions(+) create mode 100644 debian/README.Debian create mode 100644 debian/changelog create mode 100644 debian/compat create mode 100644 debian/control create mode 100644 debian/copyright create mode 100644 debian/default create mode 100644 debian/docs create mode 100644 debian/example-cfg.txt create mode 100644 debian/example-cfg2.txt create mode 100644 debian/init.d create mode 100644 debian/patches/00list create mode 100755 debian/patches/10_fix_manpage.dpatch create mode 100644 debian/postinst create mode 100644 debian/postrm create mode 100755 debian/rules create mode 100644 debian/watch diff --git a/debian/README.Debian b/debian/README.Debian new file mode 100644 index 0000000..10a50f2 --- /dev/null +++ b/debian/README.Debian @@ -0,0 +1,5 @@ +postfwd for Debian +------------------ + + + -- Jan Wagner Mon, 10 Mar 2008 22:37:44 +0100 diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..d22c65f --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +postfwd (1.03-1) unstable; urgency=low + + * Initial release (Closes: #470356). + + -- Jan Wagner Mon, 10 Mar 2008 22:37:44 +0100 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..7ed6ff8 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +5 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..ab0b294 --- /dev/null +++ b/debian/control @@ -0,0 +1,22 @@ +Source: postfwd +Section: mail +Priority: optional +Maintainer: Jan Wagner +Build-Depends: debhelper (>= 5), dpatch +Homepage: http://www.postfwd.org/ +Vcs-Browser: https://trac.cyconet.org/debian/browser/debian/postfwd +Vcs-Svn: https://trac.cyconet.org/svn/debian/postfwd +Standards-Version: 3.7.3 + +Package: postfwd +Architecture: all +Depends: ${perl:Depends}, adduser, libnet-cidr-lite-perl, libnet-server-perl +Description: a Postfix policyd to combine complex restrictions in a ruleset + Postfwd is written in perl to combine complex postfix restrictions in a + ruleset similar to those of the most firewalls. The program uses the postfix + policy delegation protocol to control access to the mail system before a + message has been accepted (please visit + http://www.postfix.org/SMTPD_POLICY_README.html for more information). It + allows you to choose an action (e.g. reject, dunno) for a combination of + several smtp parameters (like sender and recipient address, size or the + client's TLS fingerprint). diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..0a4df50 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,36 @@ +This package was debianized by Jan Wagner on +Mon, 10 Mar 2008 22:37:44 +0100 + +It was downloaded from + +Upstream Author: Jan Peter Kessler + +Copyright: (c) 2007, Jan Peter Kessler, All rights reserved. + +License: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the authors nor the names of his contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY ME ``AS IS'' AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + EVENT SHALL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The Debian packaging is (C) 2008 Jan Wagner and +is licensed under the GPL, see `/usr/share/common-licenses/GPL'. diff --git a/debian/default b/debian/default new file mode 100644 index 0000000..70ae908 --- /dev/null +++ b/debian/default @@ -0,0 +1,15 @@ +# Global options for postfwd(8). + +# Set to '1' to enable startup (daemon mode) +#STARTUP=1 + +# Config file +CONF=/etc/postfix/postfwd.cf +# IP where listen to +INET=127.0.0.1 +# Port where listen to +PORT=10040 +# run as user postfwd +RUNAS="postfw" +# Arguments passed on start (--daemon implied) +ARGS="--summary=600 --cache=600 --cache-rdomain-only --cache-no-size" diff --git a/debian/docs b/debian/docs new file mode 100644 index 0000000..0646e19 --- /dev/null +++ b/debian/docs @@ -0,0 +1,2 @@ +doc/postfwd.html +doc/postfwd.txt diff --git a/debian/example-cfg.txt b/debian/example-cfg.txt new file mode 100644 index 0000000..49efb5f --- /dev/null +++ b/debian/example-cfg.txt @@ -0,0 +1,108 @@ +# downloaded from http://postfwd.org/example-cfg.txt +# check for more recent versions! + +## +## Definitions +## + +# Maintenance times +&&MAINTENANCE { \ + date=15.01.2007 ; \ + date=15.04.2007 ; \ + date=15.07.2007 ; \ + date=15.10.2007 ; \ + time=03:00:00-04:00:00 ; \ +}; + +# Whitelists +&&TRUSTED_NETS { \ + client_address=192.168.1.0/22 ; \ + client_address=172.16.128.32/27 ; \ +}; +&&TRUSTED_HOSTS { \ + client_name~=\.domain1\.net$ ; \ + client_name~=\.domain2\.de$ ; \ +}; +&&TRUSTED_USERS { \ + sasl_username==bob ; \ + sasl_username==alice ; \ +}; +&&TRUSTED_TLS { \ + ccert_fingerprint==11:22:33:44:55:66:AA:BB:CC:DD:EE:FF ; \ + ccert_fingerprint==AA:BB:CC:DD:EE:FF:11:22:33:44:55:66 ; \ + encryption_keysize>=64 ; \ +}; +&&FREEMAIL { \ + client_name~=\.gmx\.net$ ; \ + client_name~=\.web\.de$ ; \ + client_name~=\.(aol|yahoo|h(ush|ot)mail)\.com$ ; \ +}; +&&STATIC { \ + # contains freemailers + &&FREEMAIL ; \ + client_name~=[\.\-]static[[\.\-] ; \ + client_name~=^(mail|smtp|mout|mx)[\-]*[0-9]*\. ; \ +}; + +# Spamchecks +&&BADHELO { \ + client_name~=!!($$(helo_name)) ; \ +}; +&&DYNAMIC { \ + client_name~=^unknown$ ; \ + client_name~=(\-.+){4} ; \ + client_name~=\d{5} ; \ + client_name~=[_\.\-]([axt]{0,1}dsl|br(e|oa)dband|ppp|pppoe|dynamic|dynip|ADSL|dial(up|in)|pool|dhcp|leased)[_\.\-] ; \ +}; +&&RBLS { \ + rbl=zen.spamhaus.org ; \ + rbl=list.dsbl.org ; \ + rbl=bl.spamcop.net ; \ + rbl=dnsbl.sorbs.net ; \ + rbl=ix.dnsbl.manitu.net ; \ +}; +&&RHSBLS { \ + rhsbl=rddn.dnsbl.net.au ; \ + rhsbl=rhsbl.ahbl.org ; \ + rhsbl=rhsbl.sorbs.net ; \ +}; + + +## +## Ruleset +## + +# temporary reject and drop connection during maintenance window +id=M_001 ; &&MAINTENANCE ; action=421 maintenance - please try again later + +# stress-friendly behaviour (will not match on postfix version pre 2.5) +id=STRESS ; stress==yes ; action=dunno + +# Whitelists +id=WL_001 ; &&TRUSTED_NETS ; action=dunno +id=WL_002 ; &&TRUSTED_HOSTS ; action=dunno +id=WL_003 ; &&TRUSTED_USERS ; action=dunno +id=WL_004 ; &&TRUSTED_TLS ; action=dunno + +# DNSBL checks +id=RBL_001 ; &&RHSBLS ; &&RBLS ; \ + rhsblcount=all ; rblcount=all ; \ + action=set(HIT_rhls=$$rhsblcount,HIT_rbls=$$rblcount) +id=RBL_002 ; HIT_rhls>=1 ; HIT_rbls>=1 ; action=554 5.7.1 blocked using $$HIT_rhls RHSBLs and $$HIT_rbls RBLs +id=RBL_003 ; HIT_rhls>=2 ; action=554 5.7.1 blocked using $$HIT_rhls RHSBLs +id=RBL_004 ; HIT_rbls>=2 ; action=554 5.7.1 blocked using $$HIT_rbls RBLs +id=RBL_005 ; HIT_rbls>=1 ; &&DYNAMIC ; action=REJECT listed on RBL and $$client_name looks like dynip +id=RBL_006 ; HIT_rhls>=1 ; &&DYNAMIC ; action=REJECT listed on RHSBL and $$client_name looks like dynip +id=RBL_007 ; HIT_rbls>=1 ; &&BADHELO ; action=REJECT listed on RBL and $$helo_name does not match $$client_name +id=RBL_008 ; HIT_rhls>=1 ; &&BADHELO ; action=REJECT listed on RHSBL and $$helo_name does not match $$client_name + +# Selective greylisting +id=GREY_001 ; action=dunno ; &&STATIC +id=GREY_002 ; action=dunno ; $$client_name~=$$(sender_domain)$ +id=GREY_003 ; action=greylisting ; &&DYNAMIC +id=GREY_004 ; action=greylisting ; HIT_rhls>=1 +id=GREY_005 ; action=greylisting ; HIT_rbls>=1 +# greylisting should be safe during out-of-office times +id=GREY_006 ; action=greylisting ; days=Sat-Sun +id=GREY_007 ; action=greylisting ; days=Mon-Fri ; time=!!06:00:00-20:00:00 + diff --git a/debian/example-cfg2.txt b/debian/example-cfg2.txt new file mode 100644 index 0000000..9b0d203 --- /dev/null +++ b/debian/example-cfg2.txt @@ -0,0 +1,103 @@ +# downloaded from http://hege.li/howto/spam/etc/postfwd/postfwd.conf +# check for more recent versions! + +## Check DNS Whitelisting + +id=OK_DNSWL; \ + rbl=list.dnswl.org/127/43200; \ + rbl=hostkarma.junkemailfilter.com/127.0.0.[13]; \ + action=OK + +## Check HELO and reverse DNS + +id=SET_HELO; \ + helo_name=^(\[|[^.]+$|.*?[0-9.-]{8}); \ + action=set(HIT_helo=1) + +id=SET_NODNS; \ + client_name=^unknown$; \ + action=set(HIT_nodns=1) + +id=REJECT_HELO_NODNS; \ + HIT_helo==1; HIT_nodns==1; \ + action=REJECT Your HELO is suspicious and no reverse DNS + +## Check ZEN for immediate blocking + +id=REJECT_RBL_ZEN; \ + rbl=zen.spamhaus.org; \ + action=REJECT You are listed in zen.spamhaus.org DNSBL + +## Check DNSBLs + +&&DNSBLS { \ + rbl=bl.spamcop.net; \ + rbl=dnsbl-1.uceprotect.net; \ + rbl=psbl.surriel.org; \ + rbl=dnsbl.ahbl.org; \ + rbl=dnsbl.njabl.org; \ + rbl=list.dsbl.org; \ + rbl=dnsbl.sorbs.net; \ + rbl=ix.dnsbl.manitu.net; \ + rbl=hostkarma.junkemailfilter.com/127.0.0.2; \ +}; + +id=EVAL_DNSBLS; \ + &&DNSBLS; rblcount=all; \ + action=set(HIT_rbls=$$rblcount) + +id=REJECT_RBL_MULTI; \ + HIT_rbls>=2; \ + action=REJECT You are listed in several DNSBLs + +## Check RHSBLs + +&&RHSBLS_REVERSE { \ + rhsbl_reverse_client=l1.apews.org; \ + rhsbl_reverse_client=rddn.dnsbl.net.au; \ + rhsbl_reverse_client=dynamic.rhs.mailpolice.com; \ +}; + +&&RHSBLS_SENDER { \ + rhsbl_sender=multi.uribl.com; \ + rhsbl_sender=multi.surbl.org; \ + rhsbl_sender=rhsbl.ahbl.org; \ + rhsbl_sender=rhsbl.sorbs.net; \ + rhsbl_sender=dsn.rfc-ignorant.org; \ +}; + +id=EVAL_RHSBLS; \ + &&RHSBLS_REVERSE; &&RHSBLS_SENDER; rhsblcount=all; \ + action=set(HIT_rhsbls=$$rhsblcount) + +id=REJECT_RHSBL_MULTI; \ + HIT_rhsbls>=2; \ + action=REJECT You are listed in several RHSBLs + +## Combined checks + +id=REJECT_RBL_RHSBL; \ + HIT_rbls>=1; HIT_rhsbls>=1; \ + action=REJECT You are DNSBL and RHSBL listed + +id=REJECT_RBL_HELO; \ + HIT_rbls>=1; HIT_helo==1; \ + action=REJECT You are DNSBL listed and HELO is suspicious + +id=REJECT_RBL_NODNS; \ + HIT_rbls>=1; HIT_nodns==1; \ + action=REJECT You are DNSBL listed and no reverse DNS + +id=REJECT_RHSBL_HELO; \ + HIT_rhsbls>=1; HIT_helo==1; \ + action=REJECT You are RHSBL listed and HELO is suspicious + +id=REJECT_RHSBL_NODNS; \ + HIT_rhsbls>=1; HIT_nodns==1; \ + action=REJECT You are RHSBL listed and no reverse DNS + +## Greylist suspicious + +id=GREY_RBL; HIT_rbls>=1; action=check_postgrey +id=GREY_RHSBL; HIT_rhsbls>=1; action=check_postgrey + diff --git a/debian/init.d b/debian/init.d new file mode 100644 index 0000000..2672648 --- /dev/null +++ b/debian/init.d @@ -0,0 +1,101 @@ +#! /bin/sh +# Written by Miquel van Smoorenburg . +# Modified for Debian +# by Ian Murdock . +# +# Version: @(#)skeleton 1.9 26-Feb-2001 miquels@cistron.nl +# /etc/init.d/postfwd: v1 2008/03/12 Jan Wagner + +### BEGIN INIT INFO +# Provides: postfwd +# Required-Start: $local_fs $network $remote_fs $syslog +# Required-Stop: $local_fs $network $remote_fs $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: start and stop the postfw daemon +# Description: a Perl policy daemon for the Postfix MTA +### END INIT INFO + +PATH=/sbin:/bin:/usr/sbin:/usr/bin +NAME=postfwd +DAEMON=/usr/sbin/${NAME} +DESC=postfwd + +test -x $DAEMON || exit 0 + +not_configured () { + echo "#### WARNING ####" + echo "${NAME} won't be started/stopped unless it is configured." + echo "If you want to start ${NAME} as daemon, see /etc/default/${NAME}." + echo "#################" + exit 0 +} + +no_configfile () { + echo "#### WARNING ####" + echo "${NAME} won't be started/stopped unless a rules file is provided at $CONF." + echo "#################" + exit 0 +} + +# check if postfwd is configured or not +if [ -f "/etc/default/$NAME" ] +then + . /etc/default/$NAME + if [ "$STARTUP" != "1" ] + then + not_configured + fi +else + not_configured +fi + +# check if rules file is there +if [ ! -f $CONF ] +then + no_configfile +fi + +# Check whether we have to drop privileges. +if [ -n "$RUNAS" ]; then + if ! getent passwd "$RUNAS" >/dev/null; then + RUNAS="" + fi +fi + +set -e + +case "$1" in + start) + echo -n "Starting $DESC: " + start-stop-daemon --start --quiet \ + --name ${RUNAS} \ + --exec $DAEMON -- ${ARGS} --daemon --file=${CONF} --interface=${INET} --port=${PORT} --user=${RUNAS} --group=${RUNAS} + echo "$NAME." + ;; + stop) + echo -n "Stopping $DESC: " + start-stop-daemon --stop --quiet --oknodo \ + --exec $DAEMON + echo "$NAME." + rm -f /var/run/$NAME.pid + ;; + reload) + echo "Reloading $DESC configuration files." + for pid in `pidof ${NAME}`; do kill -HUP ${pid}; done ; + ;; + restart|force-reload) + echo -n "Restarting $DESC (incl. cache): " + $0 stop + sleep 1 + $0 start + echo "$NAME." + ;; + *) + N=/etc/init.d/$NAME + echo "Usage: $N {start|stop|restart|reload|force-reload}" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/debian/patches/00list b/debian/patches/00list new file mode 100644 index 0000000..324347e --- /dev/null +++ b/debian/patches/00list @@ -0,0 +1 @@ +10_fix_manpage.dpatch diff --git a/debian/patches/10_fix_manpage.dpatch b/debian/patches/10_fix_manpage.dpatch new file mode 100755 index 0000000..76c020c --- /dev/null +++ b/debian/patches/10_fix_manpage.dpatch @@ -0,0 +1,21 @@ +#!/bin/sh /usr/share/dpatch/dpatch-run +## 10_fix_manpage.dpatch by Jan Wagner +## +## DP: Fix manpage section + +@DPATCH@ + +diff -Nur postfwd-1.03.orig/man/man1/postfwd.1 postfwd-1.03/man/man1/postfwd.1 +--- postfwd-1.03.orig/man/man1/postfwd.1 2007-10-29 09:29:15.000000000 +0100 ++++ postfwd-1.03/man/man1/postfwd.1 2008-03-12 01:10:48.000000000 +0100 +@@ -128,8 +128,8 @@ + .rm #[ #] #H #V #F C + .\" ======================================================================== + .\" +-.IX Title "POSTFWD 1" +-.TH POSTFWD 1 "2007-10-29" "perl v5.8.5" "User Contributed Perl Documentation" ++.IX Title "POSTFWD 8" ++.TH POSTFWD 8 "2007-10-29" "perl v5.8.5" "User Contributed Perl Documentation" + .SH "NAME" + postfwd \- postfix firewall daemon + .SH "SYNOPSIS" diff --git a/debian/postinst b/debian/postinst new file mode 100644 index 0000000..8d4160d --- /dev/null +++ b/debian/postinst @@ -0,0 +1,57 @@ +#!/bin/sh +# based on arpwatch.postinst: v11 2004/09/15 KELEMEN Peter +# postinst: v1 2006/01/12 Jan Wagner + +set -e + +NUSER="postfw" +NGROUP="postfw" +NHOME="/var/lib/$NUSER" +NGECOS="postfwd user" + +case "$1" in + configure) + # Take care of group. + if NGROUP_ENTRY=`getent group $NGROUP`; then + # group exists + : + else + # group does not exist yet + addgroup --quiet --system $NGROUP + fi + + # Take care of user. + if NUSER_ENTRY=`getent passwd $NUSER`; then + # user exists + adduser --quiet $NUSER $NGROUP + # + else + # user does not exist yet + adduser --quiet --system \ + --ingroup $NGROUP \ + --gecos "$NGECOS" \ + --home $NHOME \ + --no-create-home \ + --shell /bin/sh \ + --disabled-login \ + --disabled-password \ + --shell /bin/false \ + $NUSER + fi + + # Set up home directory. + if [ -d $NHOME ]; then + chown -R ${NUSER}:${NGROUP} $NHOME + chmod -R o-rwX $NHOME + fi + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + ;; + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +#DEBHELPER# diff --git a/debian/postrm b/debian/postrm new file mode 100644 index 0000000..3e7be22 --- /dev/null +++ b/debian/postrm @@ -0,0 +1,53 @@ +#!/bin/sh +# based on arpwatch.postrm: v2 2004/09/15 KELEMEN Peter +# postrm: v1 2006/10/12 Jan Wagner + +NUSER="postfw" +NGROUP="postfw" + +set -e + +case "$1" in + purge) + # find first and last SYSTEM_UID numbers + for LINE in `grep SYSTEM_UID /etc/adduser.conf | grep -v "^#"`; do + case $LINE in + FIRST_SYSTEM_UID*) + FIST_SYSTEM_UID=`echo $LINE | cut -f2 -d '='` + ;; + LAST_SYSTEM_UID*) + LAST_SYSTEM_UID=`echo $LINE | cut -f2 -d '='` + ;; + *) + ;; + esac + done + # remove system account if necessary + if [ -n "$FIST_SYSTEM_UID" ] && [ -n "$LAST_SYSTEM_UID" ]; then + if USERID=`getent passwd $NUSER | cut -f 3 -d ':'`; then + if [ -n "$USERID" ]; then + if [ "$FIST_SYSTEM_UID" -le "$USERID" ] && \ + [ "$USERID" -le "$LAST_SYSTEM_UID" ]; then + + deluser --quiet $NUSER || true + # And then remove the group + GROUPID=`getent group $NGROUP | cut -f 3 -d ':'` + if [ -n "$GROUPID" ] ; then + delgroup --quiet $NGROUP || true + fi + fi + fi + fi + fi + ;; + + remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) + ;; + + *) + echo "postrm called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +#DEBHELPER# diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..293fe7f --- /dev/null +++ b/debian/rules @@ -0,0 +1,47 @@ +#!/usr/bin/make -f +# written by Jan Wagner +# +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +include /usr/share/dpatch/dpatch.make + +build: patch-stamp + +clean: unpatch + dh_testdir + dh_testroot + dh_clean + +install: build + dh_testdir + dh_testroot + dh_clean -k + dh_installdirs + + install -D -m 644 sbin/postfwd debian/postfwd/usr/sbin/postfwd + install -d -m 0755 debian/postfwd/usr/share/doc/postfwd/examples/ + cp debian/example-cfg*.txt debian/postfwd/usr/share/doc/postfwd/examples/ + +# Build architecture-independent files here. +binary-indep: build install + dh_testdir + dh_testroot + dh_installchangelogs doc/CHANGELOG + dh_installdocs tools + dh_installinit -- defaults 19 21 + dh_installman man/man1/postfwd.1 + dh_link + dh_compress + dh_fixperms + dh_perl + dh_installdeb + dh_gencontrol + dh_md5sums + dh_builddeb + +# Build architecture-dependent files here. +binary-arch: build install + +binary: binary-indep binary-arch +.PHONY: build clean binary-indep binary-arch binary install diff --git a/debian/watch b/debian/watch new file mode 100644 index 0000000..5a512b0 --- /dev/null +++ b/debian/watch @@ -0,0 +1,2 @@ +version=3 +http://www.postfwd.org/postfwd-(.*)\.tar\.gz From cdf2b02425ffa0682dc646a7e3cd2bf557df5345 Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Mon, 14 Apr 2008 08:30:45 +0000 Subject: [PATCH 002/159] dont startup --- debian/default | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/default b/debian/default index 70ae908..af87842 100644 --- a/debian/default +++ b/debian/default @@ -1,7 +1,7 @@ # Global options for postfwd(8). # Set to '1' to enable startup (daemon mode) -#STARTUP=1 +STARTUP=0 # Config file CONF=/etc/postfix/postfwd.cf From f31cd74f745f6b7ee5d485104372ea76c3cdae7d Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Mon, 14 Apr 2008 09:11:27 +0000 Subject: [PATCH 003/159] add to depends --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index ab0b294..fe399e2 100644 --- a/debian/control +++ b/debian/control @@ -10,7 +10,7 @@ Standards-Version: 3.7.3 Package: postfwd Architecture: all -Depends: ${perl:Depends}, adduser, libnet-cidr-lite-perl, libnet-server-perl +Depends: ${perl:Depends}, ${misc:Depends}, adduser, libnet-cidr-lite-perl, libnet-server-perl Description: a Postfix policyd to combine complex restrictions in a ruleset Postfwd is written in perl to combine complex postfix restrictions in a ruleset similar to those of the most firewalls. The program uses the postfix From 863763f6a091e5ae601ba716b2844e9438814dae Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Mon, 14 Apr 2008 09:12:19 +0000 Subject: [PATCH 004/159] conditional use of deluser and delgroup --- debian/postrm | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/debian/postrm b/debian/postrm index 3e7be22..5b95704 100644 --- a/debian/postrm +++ b/debian/postrm @@ -27,14 +27,14 @@ case "$1" in if USERID=`getent passwd $NUSER | cut -f 3 -d ':'`; then if [ -n "$USERID" ]; then if [ "$FIST_SYSTEM_UID" -le "$USERID" ] && \ - [ "$USERID" -le "$LAST_SYSTEM_UID" ]; then - - deluser --quiet $NUSER || true - # And then remove the group - GROUPID=`getent group $NGROUP | cut -f 3 -d ':'` - if [ -n "$GROUPID" ] ; then - delgroup --quiet $NGROUP || true - fi + [ "$USERID" -le "$LAST_SYSTEM_UID" ] && \ + [ -x /usr/sbin/deluser ]; then + deluser --quiet $NUSER || true + # And then remove the group + GROUPID=`getent group $NGROUP | cut -f 3 -d ':'` + if [ -n "$GROUPID" ] && [ -x /usr/sbin/delgroup ] ; then + delgroup --quiet $NGROUP || true + fi fi fi fi From e1a89b693e5a9f545d71419f1e3a7ea7730a3eb0 Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Mon, 14 Apr 2008 13:56:11 +0000 Subject: [PATCH 005/159] add conditional deluser and delgroup --- debian/postrm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/debian/postrm b/debian/postrm index 5b95704..eb50815 100644 --- a/debian/postrm +++ b/debian/postrm @@ -30,11 +30,11 @@ case "$1" in [ "$USERID" -le "$LAST_SYSTEM_UID" ] && \ [ -x /usr/sbin/deluser ]; then deluser --quiet $NUSER || true - # And then remove the group - GROUPID=`getent group $NGROUP | cut -f 3 -d ':'` - if [ -n "$GROUPID" ] && [ -x /usr/sbin/delgroup ] ; then - delgroup --quiet $NGROUP || true - fi + # And then remove the group + GROUPID=`getent group $NGROUP | cut -f 3 -d ':'` + if [ -n "$GROUPID" ] && [ -x /usr/sbin/delgroup ] ; then + delgroup --quiet $NGROUP || true + fi fi fi fi From eb1fef3fe65204ff25d908f4cea9b266a19ce17b Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Mon, 14 Apr 2008 19:30:30 +0000 Subject: [PATCH 006/159] provide content in README.Debian --- debian/README.Debian | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/debian/README.Debian b/debian/README.Debian index 10a50f2..4561613 100644 --- a/debian/README.Debian +++ b/debian/README.Debian @@ -1,5 +1,17 @@ postfwd for Debian ------------------ +1. PROVIDE A CONFIGFILE +----------------------- + +Please provide a config file, usualy /etc/postfix/postfwd.cf. Examples are +located in /usr/share/doc/postfwd/examples/. + +2. AUTOMATIC STARTUP +-------------------- + +In order to avoid the startup of the daemon on an unconfigured machine, +automatic startup, on boot, is disabled by default. To enable it just edit the +file /etc/default/postfwd and set the "startup" variable to 1. -- Jan Wagner Mon, 10 Mar 2008 22:37:44 +0100 From 219ae7d9a64e398aab6d39e6a6fef6eb0ce0f630 Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Mon, 12 May 2008 20:40:59 +0000 Subject: [PATCH 007/159] update to 1.10 upstream and drop dpatch --- debian/changelog | 4 ++-- debian/control | 6 +++--- debian/patches/00list | 1 - debian/patches/10_fix_manpage.dpatch | 21 --------------------- debian/rules | 9 ++++----- 5 files changed, 9 insertions(+), 32 deletions(-) delete mode 100644 debian/patches/00list delete mode 100755 debian/patches/10_fix_manpage.dpatch diff --git a/debian/changelog b/debian/changelog index d22c65f..58d6b46 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,5 +1,5 @@ -postfwd (1.03-1) unstable; urgency=low +postfwd (1.10pre7c-1) unstable; urgency=low * Initial release (Closes: #470356). - -- Jan Wagner Mon, 10 Mar 2008 22:37:44 +0100 + -- Jan Wagner Mon, 14 Apr 2008 22:37:44 +0100 diff --git a/debian/control b/debian/control index fe399e2..1781bff 100644 --- a/debian/control +++ b/debian/control @@ -2,7 +2,7 @@ Source: postfwd Section: mail Priority: optional Maintainer: Jan Wagner -Build-Depends: debhelper (>= 5), dpatch +Build-Depends: debhelper (>= 5) Homepage: http://www.postfwd.org/ Vcs-Browser: https://trac.cyconet.org/debian/browser/debian/postfwd Vcs-Svn: https://trac.cyconet.org/svn/debian/postfwd @@ -10,8 +10,8 @@ Standards-Version: 3.7.3 Package: postfwd Architecture: all -Depends: ${perl:Depends}, ${misc:Depends}, adduser, libnet-cidr-lite-perl, libnet-server-perl -Description: a Postfix policyd to combine complex restrictions in a ruleset +Depends: ${perl:Depends}, ${misc:Depends}, adduser, libnet-cidr-lite-perl, libnet-dns-async-perl, libnet-server-perl +Description: Postfix policyd to combine complex restrictions in a ruleset Postfwd is written in perl to combine complex postfix restrictions in a ruleset similar to those of the most firewalls. The program uses the postfix policy delegation protocol to control access to the mail system before a diff --git a/debian/patches/00list b/debian/patches/00list deleted file mode 100644 index 324347e..0000000 --- a/debian/patches/00list +++ /dev/null @@ -1 +0,0 @@ -10_fix_manpage.dpatch diff --git a/debian/patches/10_fix_manpage.dpatch b/debian/patches/10_fix_manpage.dpatch deleted file mode 100755 index 76c020c..0000000 --- a/debian/patches/10_fix_manpage.dpatch +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh /usr/share/dpatch/dpatch-run -## 10_fix_manpage.dpatch by Jan Wagner -## -## DP: Fix manpage section - -@DPATCH@ - -diff -Nur postfwd-1.03.orig/man/man1/postfwd.1 postfwd-1.03/man/man1/postfwd.1 ---- postfwd-1.03.orig/man/man1/postfwd.1 2007-10-29 09:29:15.000000000 +0100 -+++ postfwd-1.03/man/man1/postfwd.1 2008-03-12 01:10:48.000000000 +0100 -@@ -128,8 +128,8 @@ - .rm #[ #] #H #V #F C - .\" ======================================================================== - .\" --.IX Title "POSTFWD 1" --.TH POSTFWD 1 "2007-10-29" "perl v5.8.5" "User Contributed Perl Documentation" -+.IX Title "POSTFWD 8" -+.TH POSTFWD 8 "2007-10-29" "perl v5.8.5" "User Contributed Perl Documentation" - .SH "NAME" - postfwd \- postfix firewall daemon - .SH "SYNOPSIS" diff --git a/debian/rules b/debian/rules index 293fe7f..11e0b20 100755 --- a/debian/rules +++ b/debian/rules @@ -4,11 +4,9 @@ # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 -include /usr/share/dpatch/dpatch.make +build: -build: patch-stamp - -clean: unpatch +clean: dh_testdir dh_testroot dh_clean @@ -22,6 +20,7 @@ install: build install -D -m 644 sbin/postfwd debian/postfwd/usr/sbin/postfwd install -d -m 0755 debian/postfwd/usr/share/doc/postfwd/examples/ cp debian/example-cfg*.txt debian/postfwd/usr/share/doc/postfwd/examples/ + cp etc/postfwd.cf debian/postfwd/usr/share/doc/postfwd/examples/example-cfg.txt # Build architecture-independent files here. binary-indep: build install @@ -30,7 +29,7 @@ binary-indep: build install dh_installchangelogs doc/CHANGELOG dh_installdocs tools dh_installinit -- defaults 19 21 - dh_installman man/man1/postfwd.1 + dh_installman man/man8/postfwd.8 dh_link dh_compress dh_fixperms From a13949794cf45d0745cefbcf0222fc063c425441 Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Mon, 12 May 2008 20:44:58 +0000 Subject: [PATCH 008/159] update timestamp --- debian/changelog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index 58d6b46..fa23309 100644 --- a/debian/changelog +++ b/debian/changelog @@ -2,4 +2,4 @@ postfwd (1.10pre7c-1) unstable; urgency=low * Initial release (Closes: #470356). - -- Jan Wagner Mon, 14 Apr 2008 22:37:44 +0100 + -- Jan Wagner Mon, 12 May 2008 22:43:55 +0200 From 44d7d35f56c76ae5587042b3b7948a4043c3cad8 Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Sat, 31 May 2008 20:10:16 +0000 Subject: [PATCH 009/159] remove shipped example config and removed 2nd cause unclear license --- debian/README.Debian | 1 + debian/changelog | 4 +- debian/example-cfg.txt | 108 ---------------------------------------- debian/example-cfg2.txt | 103 -------------------------------------- debian/rules | 2 +- 5 files changed, 4 insertions(+), 214 deletions(-) delete mode 100644 debian/example-cfg.txt delete mode 100644 debian/example-cfg2.txt diff --git a/debian/README.Debian b/debian/README.Debian index 4561613..5b90ed2 100644 --- a/debian/README.Debian +++ b/debian/README.Debian @@ -6,6 +6,7 @@ postfwd for Debian Please provide a config file, usualy /etc/postfix/postfwd.cf. Examples are located in /usr/share/doc/postfwd/examples/. +An other can be found at: http://hege.li/howto/spam/etc/postfwd/postfwd.conf 2. AUTOMATIC STARTUP -------------------- diff --git a/debian/changelog b/debian/changelog index fa23309..3e5bfc5 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,5 +1,5 @@ -postfwd (1.10pre7c-1) unstable; urgency=low +postfwd (1.10pre7c-1) experimental; urgency=low * Initial release (Closes: #470356). - -- Jan Wagner Mon, 12 May 2008 22:43:55 +0200 + -- Jan Wagner Sat, 31 May 2008 22:07:08 +0200 diff --git a/debian/example-cfg.txt b/debian/example-cfg.txt deleted file mode 100644 index 49efb5f..0000000 --- a/debian/example-cfg.txt +++ /dev/null @@ -1,108 +0,0 @@ -# downloaded from http://postfwd.org/example-cfg.txt -# check for more recent versions! - -## -## Definitions -## - -# Maintenance times -&&MAINTENANCE { \ - date=15.01.2007 ; \ - date=15.04.2007 ; \ - date=15.07.2007 ; \ - date=15.10.2007 ; \ - time=03:00:00-04:00:00 ; \ -}; - -# Whitelists -&&TRUSTED_NETS { \ - client_address=192.168.1.0/22 ; \ - client_address=172.16.128.32/27 ; \ -}; -&&TRUSTED_HOSTS { \ - client_name~=\.domain1\.net$ ; \ - client_name~=\.domain2\.de$ ; \ -}; -&&TRUSTED_USERS { \ - sasl_username==bob ; \ - sasl_username==alice ; \ -}; -&&TRUSTED_TLS { \ - ccert_fingerprint==11:22:33:44:55:66:AA:BB:CC:DD:EE:FF ; \ - ccert_fingerprint==AA:BB:CC:DD:EE:FF:11:22:33:44:55:66 ; \ - encryption_keysize>=64 ; \ -}; -&&FREEMAIL { \ - client_name~=\.gmx\.net$ ; \ - client_name~=\.web\.de$ ; \ - client_name~=\.(aol|yahoo|h(ush|ot)mail)\.com$ ; \ -}; -&&STATIC { \ - # contains freemailers - &&FREEMAIL ; \ - client_name~=[\.\-]static[[\.\-] ; \ - client_name~=^(mail|smtp|mout|mx)[\-]*[0-9]*\. ; \ -}; - -# Spamchecks -&&BADHELO { \ - client_name~=!!($$(helo_name)) ; \ -}; -&&DYNAMIC { \ - client_name~=^unknown$ ; \ - client_name~=(\-.+){4} ; \ - client_name~=\d{5} ; \ - client_name~=[_\.\-]([axt]{0,1}dsl|br(e|oa)dband|ppp|pppoe|dynamic|dynip|ADSL|dial(up|in)|pool|dhcp|leased)[_\.\-] ; \ -}; -&&RBLS { \ - rbl=zen.spamhaus.org ; \ - rbl=list.dsbl.org ; \ - rbl=bl.spamcop.net ; \ - rbl=dnsbl.sorbs.net ; \ - rbl=ix.dnsbl.manitu.net ; \ -}; -&&RHSBLS { \ - rhsbl=rddn.dnsbl.net.au ; \ - rhsbl=rhsbl.ahbl.org ; \ - rhsbl=rhsbl.sorbs.net ; \ -}; - - -## -## Ruleset -## - -# temporary reject and drop connection during maintenance window -id=M_001 ; &&MAINTENANCE ; action=421 maintenance - please try again later - -# stress-friendly behaviour (will not match on postfix version pre 2.5) -id=STRESS ; stress==yes ; action=dunno - -# Whitelists -id=WL_001 ; &&TRUSTED_NETS ; action=dunno -id=WL_002 ; &&TRUSTED_HOSTS ; action=dunno -id=WL_003 ; &&TRUSTED_USERS ; action=dunno -id=WL_004 ; &&TRUSTED_TLS ; action=dunno - -# DNSBL checks -id=RBL_001 ; &&RHSBLS ; &&RBLS ; \ - rhsblcount=all ; rblcount=all ; \ - action=set(HIT_rhls=$$rhsblcount,HIT_rbls=$$rblcount) -id=RBL_002 ; HIT_rhls>=1 ; HIT_rbls>=1 ; action=554 5.7.1 blocked using $$HIT_rhls RHSBLs and $$HIT_rbls RBLs -id=RBL_003 ; HIT_rhls>=2 ; action=554 5.7.1 blocked using $$HIT_rhls RHSBLs -id=RBL_004 ; HIT_rbls>=2 ; action=554 5.7.1 blocked using $$HIT_rbls RBLs -id=RBL_005 ; HIT_rbls>=1 ; &&DYNAMIC ; action=REJECT listed on RBL and $$client_name looks like dynip -id=RBL_006 ; HIT_rhls>=1 ; &&DYNAMIC ; action=REJECT listed on RHSBL and $$client_name looks like dynip -id=RBL_007 ; HIT_rbls>=1 ; &&BADHELO ; action=REJECT listed on RBL and $$helo_name does not match $$client_name -id=RBL_008 ; HIT_rhls>=1 ; &&BADHELO ; action=REJECT listed on RHSBL and $$helo_name does not match $$client_name - -# Selective greylisting -id=GREY_001 ; action=dunno ; &&STATIC -id=GREY_002 ; action=dunno ; $$client_name~=$$(sender_domain)$ -id=GREY_003 ; action=greylisting ; &&DYNAMIC -id=GREY_004 ; action=greylisting ; HIT_rhls>=1 -id=GREY_005 ; action=greylisting ; HIT_rbls>=1 -# greylisting should be safe during out-of-office times -id=GREY_006 ; action=greylisting ; days=Sat-Sun -id=GREY_007 ; action=greylisting ; days=Mon-Fri ; time=!!06:00:00-20:00:00 - diff --git a/debian/example-cfg2.txt b/debian/example-cfg2.txt deleted file mode 100644 index 9b0d203..0000000 --- a/debian/example-cfg2.txt +++ /dev/null @@ -1,103 +0,0 @@ -# downloaded from http://hege.li/howto/spam/etc/postfwd/postfwd.conf -# check for more recent versions! - -## Check DNS Whitelisting - -id=OK_DNSWL; \ - rbl=list.dnswl.org/127/43200; \ - rbl=hostkarma.junkemailfilter.com/127.0.0.[13]; \ - action=OK - -## Check HELO and reverse DNS - -id=SET_HELO; \ - helo_name=^(\[|[^.]+$|.*?[0-9.-]{8}); \ - action=set(HIT_helo=1) - -id=SET_NODNS; \ - client_name=^unknown$; \ - action=set(HIT_nodns=1) - -id=REJECT_HELO_NODNS; \ - HIT_helo==1; HIT_nodns==1; \ - action=REJECT Your HELO is suspicious and no reverse DNS - -## Check ZEN for immediate blocking - -id=REJECT_RBL_ZEN; \ - rbl=zen.spamhaus.org; \ - action=REJECT You are listed in zen.spamhaus.org DNSBL - -## Check DNSBLs - -&&DNSBLS { \ - rbl=bl.spamcop.net; \ - rbl=dnsbl-1.uceprotect.net; \ - rbl=psbl.surriel.org; \ - rbl=dnsbl.ahbl.org; \ - rbl=dnsbl.njabl.org; \ - rbl=list.dsbl.org; \ - rbl=dnsbl.sorbs.net; \ - rbl=ix.dnsbl.manitu.net; \ - rbl=hostkarma.junkemailfilter.com/127.0.0.2; \ -}; - -id=EVAL_DNSBLS; \ - &&DNSBLS; rblcount=all; \ - action=set(HIT_rbls=$$rblcount) - -id=REJECT_RBL_MULTI; \ - HIT_rbls>=2; \ - action=REJECT You are listed in several DNSBLs - -## Check RHSBLs - -&&RHSBLS_REVERSE { \ - rhsbl_reverse_client=l1.apews.org; \ - rhsbl_reverse_client=rddn.dnsbl.net.au; \ - rhsbl_reverse_client=dynamic.rhs.mailpolice.com; \ -}; - -&&RHSBLS_SENDER { \ - rhsbl_sender=multi.uribl.com; \ - rhsbl_sender=multi.surbl.org; \ - rhsbl_sender=rhsbl.ahbl.org; \ - rhsbl_sender=rhsbl.sorbs.net; \ - rhsbl_sender=dsn.rfc-ignorant.org; \ -}; - -id=EVAL_RHSBLS; \ - &&RHSBLS_REVERSE; &&RHSBLS_SENDER; rhsblcount=all; \ - action=set(HIT_rhsbls=$$rhsblcount) - -id=REJECT_RHSBL_MULTI; \ - HIT_rhsbls>=2; \ - action=REJECT You are listed in several RHSBLs - -## Combined checks - -id=REJECT_RBL_RHSBL; \ - HIT_rbls>=1; HIT_rhsbls>=1; \ - action=REJECT You are DNSBL and RHSBL listed - -id=REJECT_RBL_HELO; \ - HIT_rbls>=1; HIT_helo==1; \ - action=REJECT You are DNSBL listed and HELO is suspicious - -id=REJECT_RBL_NODNS; \ - HIT_rbls>=1; HIT_nodns==1; \ - action=REJECT You are DNSBL listed and no reverse DNS - -id=REJECT_RHSBL_HELO; \ - HIT_rhsbls>=1; HIT_helo==1; \ - action=REJECT You are RHSBL listed and HELO is suspicious - -id=REJECT_RHSBL_NODNS; \ - HIT_rhsbls>=1; HIT_nodns==1; \ - action=REJECT You are RHSBL listed and no reverse DNS - -## Greylist suspicious - -id=GREY_RBL; HIT_rbls>=1; action=check_postgrey -id=GREY_RHSBL; HIT_rhsbls>=1; action=check_postgrey - diff --git a/debian/rules b/debian/rules index 11e0b20..298e25b 100755 --- a/debian/rules +++ b/debian/rules @@ -19,7 +19,7 @@ install: build install -D -m 644 sbin/postfwd debian/postfwd/usr/sbin/postfwd install -d -m 0755 debian/postfwd/usr/share/doc/postfwd/examples/ - cp debian/example-cfg*.txt debian/postfwd/usr/share/doc/postfwd/examples/ + #cp debian/example-cfg*.txt debian/postfwd/usr/share/doc/postfwd/examples/ cp etc/postfwd.cf debian/postfwd/usr/share/doc/postfwd/examples/example-cfg.txt # Build architecture-independent files here. From 30f943a10fb26da52d739e416daa513b95b07536 Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Sat, 31 May 2008 20:59:09 +0000 Subject: [PATCH 010/159] remove unneeded dh_installdirs --- debian/rules | 1 - 1 file changed, 1 deletion(-) diff --git a/debian/rules b/debian/rules index 298e25b..80fc119 100755 --- a/debian/rules +++ b/debian/rules @@ -15,7 +15,6 @@ install: build dh_testdir dh_testroot dh_clean -k - dh_installdirs install -D -m 644 sbin/postfwd debian/postfwd/usr/sbin/postfwd install -d -m 0755 debian/postfwd/usr/share/doc/postfwd/examples/ From 5ae37bda31321724a743617fef9a2e3d7a2ab810 Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Sat, 31 May 2008 20:59:28 +0000 Subject: [PATCH 011/159] remove URL, too verbose --- debian/control | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/debian/control b/debian/control index 1781bff..7a66604 100644 --- a/debian/control +++ b/debian/control @@ -15,8 +15,6 @@ Description: Postfix policyd to combine complex restrictions in a ruleset Postfwd is written in perl to combine complex postfix restrictions in a ruleset similar to those of the most firewalls. The program uses the postfix policy delegation protocol to control access to the mail system before a - message has been accepted (please visit - http://www.postfix.org/SMTPD_POLICY_README.html for more information). It - allows you to choose an action (e.g. reject, dunno) for a combination of - several smtp parameters (like sender and recipient address, size or the - client's TLS fingerprint). + message has been accepted. It allows you to choose an action (e.g. reject, + dunno) for a combination of several smtp parameters (like sender and recipient + address, size or the client's TLS fingerprint). From 6eec16da5d748c396bdacf4e2244bf0ff21e2056 Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Sun, 1 Jun 2008 17:53:57 +0000 Subject: [PATCH 012/159] readd heges config --- debian/README.Debian | 3 +- debian/example2.cfg | 146 +++++++++++++++++++++++++++++++++++++++++++ debian/rules | 2 +- 3 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 debian/example2.cfg diff --git a/debian/README.Debian b/debian/README.Debian index 5b90ed2..fc03b89 100644 --- a/debian/README.Debian +++ b/debian/README.Debian @@ -6,7 +6,8 @@ postfwd for Debian Please provide a config file, usualy /etc/postfix/postfwd.cf. Examples are located in /usr/share/doc/postfwd/examples/. -An other can be found at: http://hege.li/howto/spam/etc/postfwd/postfwd.conf +An other can be found at http://hege.li/howto/spam/etc/postfwd/postfwd.conf +and is provided as example-cfg2.txt. 2. AUTOMATIC STARTUP -------------------- diff --git a/debian/example2.cfg b/debian/example2.cfg new file mode 100644 index 0000000..b8baf21 --- /dev/null +++ b/debian/example2.cfg @@ -0,0 +1,146 @@ +# downloaded from http://hege.li/howto/spam/etc/postfwd/postfwd.conf +# check for more recent versions! + +### +### Example config for postfwd 1.10pre8+ +### + +## Check DNS whitelists, maybe we don't need more checks + +id=OK_DNSWL; \ + rbl=list.dnswl.org/^127/43200; \ + action=DUNNO + +## Check (non-fqdn/ip/dynamic) HELO and (missing) reverse DNS + +id=SET_HELO; \ + helo_name=!!\.; \ + helo_name=[0-9.-]{7}; \ + action=set(HIT_helo=1) + +id=SET_NODNS; \ + client_name=^unknown$; \ + action=set(HIT_nodns=1) + +id=REJECT_HELO_NODNS; \ + HIT_helo==1; HIT_nodns==1; \ + action=REJECT Blocked - contact postmaster@example.net for help - Suspicious HELO [$$helo_name] and missing reverse DNS [$$client_address] + +## Check ZEN first for immediate blocking - less queries for other lists +## See usage policy: http://www.spamhaus.org/organization/dnsblusage.html + +id=REJECT_RBL_ZEN; \ + rbl=zen.spamhaus.org; \ + action=REJECT Blocked - contact postmaster@example.net for help - DNSBL [$$dnsbltext] + +## Check other DNSBLs in parallel + +&&DNSBLS { \ + rbl=bl.spamcop.net; \ + rbl=dnsbl-1.uceprotect.net; \ + rbl=dnsbl-2.uceprotect.net; \ + rbl=dnsbl-3.uceprotect.net; \ + rbl=psbl.surriel.com; \ + rbl=combined.njabl.org; \ + rbl=dnsbl.ahbl.org; \ + rbl=dnsbl.sorbs.net; \ + rbl=ix.dnsbl.manitu.net; \ + rbl=dyna.spamrats.com; \ +}; + +id=EVAL_DNSBLS; \ + &&DNSBLS; rblcount=all; \ + action=set(HIT_rbls=$$rblcount,HIT_dtxt=$$dnsbltext) + +id=REJECT_RBL_MULTI; \ + HIT_rbls>=2; \ + action=REJECT Blocked - contact postmaster@example.net for help - Multiple DNSBLs [$$HIT_dtxt] + +## Check RHSBLs if there wasn't enough DNSBLs hit + +&&RHSBLS_REVERSE { \ + rhsbl_reverse_client=dynamic.rhs.mailpolice.com; \ +}; + +&&RHSBLS_SENDER { \ + rhsbl_sender=multi.uribl.com; \ + rhsbl_sender=multi.surbl.org; \ + rhsbl_sender=bulk.rhs.mailpolice.com; \ + rhsbl_sender=rhsbl.ahbl.org; \ + rhsbl_sender=rhsbl.sorbs.net; \ + rhsbl_sender=dsn.rfc-ignorant.org; \ +}; + +id=EVAL_RHSBLS; \ + &&RHSBLS_REVERSE; &&RHSBLS_SENDER; rhsblcount=all; \ + action=set(HIT_rhsbls=$$rhsblcount,HIT_rtxt=$$dnsbltext) + +id=REJECT_RHSBL_MULTI; \ + HIT_rhsbls>=2; \ + action=REJECT Blocked - contact postmaster@example.net for help - Multiple RHSBLs [$$HIT_rtxt] + +## See if we get any combined hits from rules before + +id=REJECT_RBL_RHSBL; \ + HIT_rbls>=1; HIT_rhsbls>=1; \ + action=REJECT Blocked - contact postmaster@example.net for help - RHSBL and DNSBL [$$HIT_rtxt] [$$HIT_dtxt] + +id=REJECT_RBL_HELO; \ + HIT_rbls>=1; HIT_helo==1; \ + action=REJECT Blocked - contact postmaster@example.net for help - DNSBL [$$HIT_dtxt] and suspicious HELO [$$helo_name] + +id=REJECT_RBL_NODNS; \ + HIT_rbls>=1; HIT_nodns==1; \ + action=REJECT Blocked - contact postmaster@example.net for help - DNSBL [$$HIT_dtxt] and missing reverse DNS [$$client_address] + +id=REJECT_RHSBL_HELO; \ + HIT_rhsbls>=1; HIT_helo==1; \ + action=REJECT Blocked - contact postmaster@example.net for help - RHSBL [$$HIT_rtxt] and suspicious HELO [$$helo_name] + +id=REJECT_RHSBL_NODNS; \ + HIT_rhsbls>=1; HIT_nodns==1; \ + action=REJECT Blocked - contact postmaster@example.net for help - RHSBL [$$HIT_rtxt] and missing reverse DNS [$$client_address] + +## Finally greylist all lesser hits. +## +## A more DNSBL friendly way would be to greylist everything suspicious +## before DNS checks. Currently this requires you to setup some postfix +## tables before postfwd is called, since greylisting can be only done last +## in postfwd (action always exits processing). + +id=GREY_HELO; HIT_helo==1; action=check_postgrey +id=GREY_NODNS; HIT_nodns==1; action=check_postgrey +id=GREY_RBL; HIT_rbls>=1; action=check_postgrey +id=GREY_RHSBL; HIT_rhsbls>=1; action=check_postgrey + + +## +## This example is free to use as per BSD license: +## +## Copyright (c) 2008, Henrik Krohns +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without modification, +## are permitted provided that the following conditions are met: +## +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of the authors nor the names of his contributors +## may be used to endorse or promote products derived from this +## software without specific prior written permission. +## +## THIS SOFTWARE IS PROVIDED BY ME ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, +## INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +## FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY DIRECT, +## INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +## NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +## PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +## WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +## ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +## POSSIBILITY OF SUCH DAMAGE. +## + diff --git a/debian/rules b/debian/rules index 80fc119..ab8df3c 100755 --- a/debian/rules +++ b/debian/rules @@ -18,8 +18,8 @@ install: build install -D -m 644 sbin/postfwd debian/postfwd/usr/sbin/postfwd install -d -m 0755 debian/postfwd/usr/share/doc/postfwd/examples/ - #cp debian/example-cfg*.txt debian/postfwd/usr/share/doc/postfwd/examples/ cp etc/postfwd.cf debian/postfwd/usr/share/doc/postfwd/examples/example-cfg.txt + cp debian/example-cfg*.txt debian/postfwd/usr/share/doc/postfwd/examples/ # Build architecture-independent files here. binary-indep: build install From f4c44fc93d58223f9240d6f2259c3cbcc2c12b87 Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Sun, 1 Jun 2008 17:54:13 +0000 Subject: [PATCH 013/159] rename config file --- debian/{example2.cfg => example-cfg2.txt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename debian/{example2.cfg => example-cfg2.txt} (100%) diff --git a/debian/example2.cfg b/debian/example-cfg2.txt similarity index 100% rename from debian/example2.cfg rename to debian/example-cfg2.txt From 881441b12c27d730358eb413910fb899fa8309d5 Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Sun, 1 Jun 2008 17:55:55 +0000 Subject: [PATCH 014/159] adjust copyright --- debian/copyright | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/copyright b/debian/copyright index 0a4df50..9fcfa4b 100644 --- a/debian/copyright +++ b/debian/copyright @@ -6,7 +6,7 @@ It was downloaded from Upstream Author: Jan Peter Kessler Copyright: (c) 2007, Jan Peter Kessler, All rights reserved. - + (c) 2008, Henrik Krohns License: Redistribution and use in source and binary forms, with or without From 18a6f7d42eb110aef9b95a8de61373aac18aca1e Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Mon, 2 Jun 2008 20:39:07 +0000 Subject: [PATCH 015/159] just be kind --- debian/copyright | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/debian/copyright b/debian/copyright index 9fcfa4b..e239d41 100644 --- a/debian/copyright +++ b/debian/copyright @@ -6,7 +6,7 @@ It was downloaded from Upstream Author: Jan Peter Kessler Copyright: (c) 2007, Jan Peter Kessler, All rights reserved. - (c) 2008, Henrik Krohns + License: Redistribution and use in source and binary forms, with or without @@ -32,5 +32,9 @@ License: OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +Different copyrights/licenses: + + debian/example-cfg2.txt: (c) 2008, Henrik Krohns + The Debian packaging is (C) 2008 Jan Wagner and is licensed under the GPL, see `/usr/share/common-licenses/GPL'. From 4d5cd5d35494cdf9bacd0a4ab8ada6a991938f4f Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Mon, 2 Jun 2008 20:50:56 +0000 Subject: [PATCH 016/159] use dh_installexamples --- debian/rules | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/debian/rules b/debian/rules index ab8df3c..a318a73 100755 --- a/debian/rules +++ b/debian/rules @@ -17,9 +17,6 @@ install: build dh_clean -k install -D -m 644 sbin/postfwd debian/postfwd/usr/sbin/postfwd - install -d -m 0755 debian/postfwd/usr/share/doc/postfwd/examples/ - cp etc/postfwd.cf debian/postfwd/usr/share/doc/postfwd/examples/example-cfg.txt - cp debian/example-cfg*.txt debian/postfwd/usr/share/doc/postfwd/examples/ # Build architecture-independent files here. binary-indep: build install @@ -27,6 +24,7 @@ binary-indep: build install dh_testroot dh_installchangelogs doc/CHANGELOG dh_installdocs tools + dh_installexamples etc/postfwd.cf debian/example-cfg dh_installinit -- defaults 19 21 dh_installman man/man8/postfwd.8 dh_link From 94f34b3643d315bd770f0ff77307c9d3fda650b0 Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Mon, 2 Jun 2008 21:12:59 +0000 Subject: [PATCH 017/159] fix exmaples --- debian/rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/rules b/debian/rules index a318a73..4a71dc6 100755 --- a/debian/rules +++ b/debian/rules @@ -24,7 +24,7 @@ binary-indep: build install dh_testroot dh_installchangelogs doc/CHANGELOG dh_installdocs tools - dh_installexamples etc/postfwd.cf debian/example-cfg + dh_installexamples etc/postfwd.cf debian/example-cfg* dh_installinit -- defaults 19 21 dh_installman man/man8/postfwd.8 dh_link From cf8e227b7be0864cf537d68997f6bff17164963d Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Sat, 7 Jun 2008 10:01:23 +0000 Subject: [PATCH 018/159] Updating standards version to 3.8.0 --- debian/changelog | 6 ++++++ debian/control | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index 3e5bfc5..345f995 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +postfwd (1.10pre7c-2) UNRELEASED; urgency=low + + * Updating standards version to 3.8.0, no changes needed + + -- Jan Wagner Mon, 02 Jun 2008 23:13:53 +0200 + postfwd (1.10pre7c-1) experimental; urgency=low * Initial release (Closes: #470356). diff --git a/debian/control b/debian/control index 7a66604..9cb0079 100644 --- a/debian/control +++ b/debian/control @@ -6,7 +6,7 @@ Build-Depends: debhelper (>= 5) Homepage: http://www.postfwd.org/ Vcs-Browser: https://trac.cyconet.org/debian/browser/debian/postfwd Vcs-Svn: https://trac.cyconet.org/svn/debian/postfwd -Standards-Version: 3.7.3 +Standards-Version: 3.8.0 Package: postfwd Architecture: all From 12706723e0123d87effbc99eccaf7128b4465585 Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Tue, 15 Jul 2008 20:56:27 +0000 Subject: [PATCH 019/159] release --- debian/changelog | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/debian/changelog b/debian/changelog index 345f995..4c05adb 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,9 @@ -postfwd (1.10pre7c-2) UNRELEASED; urgency=low +postfwd (1.10pre7c-2) unstable; urgency=low + * Uploading to unstable. * Updating standards version to 3.8.0, no changes needed - -- Jan Wagner Mon, 02 Jun 2008 23:13:53 +0200 + -- Jan Wagner Tue, 15 Jul 2008 22:43:08 +0200 postfwd (1.10pre7c-1) experimental; urgency=low From 35e480b2c4aac4075f62481fd81e4c3925646c93 Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Sun, 20 Jul 2008 11:27:38 +0000 Subject: [PATCH 020/159] machine-interpretable copyright --- debian/changelog | 6 ++++++ debian/copyright | 42 +++++++++++++++++++++++++++++++----------- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/debian/changelog b/debian/changelog index 4c05adb..48380c8 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +postfwd (1.10pre7c-3) UNRELEASED; urgency=low + + * implement machine-interpretable copyright file + + -- Jan Wagner Tue, 15 Jul 2008 22:58:06 +0200 + postfwd (1.10pre7c-2) unstable; urgency=low * Uploading to unstable. diff --git a/debian/copyright b/debian/copyright index e239d41..7878293 100644 --- a/debian/copyright +++ b/debian/copyright @@ -1,14 +1,23 @@ -This package was debianized by Jan Wagner on -Mon, 10 Mar 2008 22:37:44 +0100 - -It was downloaded from - -Upstream Author: Jan Peter Kessler +Format-Specification: http://wiki.debian.org/Proposals/CopyrightFormat?action=recall&rev=196 +Packaged-By: Jan Wagner +Packaged-Date: Mon, 10 Mar 2008 22:37:44 +0100 +Upstream-Name: postfwd +Upstream-Maintainer: Jan Peter Kessler +Upstream-Source: http://www.postfwd.org +Files: * Copyright: (c) 2007, Jan Peter Kessler, All rights reserved. +License: BSD-3 -License: +Files: debian/* +Copyright: (C) 2006, 2008 Jan Wagner +License: GPL-2+ +Files: debian/example-cfg2.txt +Copyright: (c) 2008, Henrik Krohns +License: BSD-3 + +License: BSD-3 Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -32,9 +41,20 @@ License: OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -Different copyrights/licenses: +License: GPL-2+ + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. - debian/example-cfg2.txt: (c) 2008, Henrik Krohns + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. -The Debian packaging is (C) 2008 Jan Wagner and -is licensed under the GPL, see `/usr/share/common-licenses/GPL'. + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + On Debian systems, the complete text of the GNU General Public License can be + found in /usr/share/common-licenses/GPL-2 file. From d07b4c2eab6b1d76f4e4b8bc1c0db375a19ba40d Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Sun, 20 Jul 2008 11:27:55 +0000 Subject: [PATCH 021/159] release --- debian/changelog | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/debian/changelog b/debian/changelog index 48380c8..c7f61c8 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,8 @@ -postfwd (1.10pre7c-3) UNRELEASED; urgency=low +postfwd (1.10pre7c-3) unstable; urgency=low * implement machine-interpretable copyright file - -- Jan Wagner Tue, 15 Jul 2008 22:58:06 +0200 + -- Jan Wagner Sun, 20 Jul 2008 13:27:42 +0200 postfwd (1.10pre7c-2) unstable; urgency=low From 4d664293de9a57c9f3fe85a5923d4c6eb3f806ea Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Fri, 31 Oct 2008 08:25:06 +0000 Subject: [PATCH 022/159] fix init script and example-cfg2.txt --- debian/changelog | 5 +++++ debian/example-cfg2.txt | 22 +++++++++++----------- debian/init.d | 9 ++++----- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/debian/changelog b/debian/changelog index c7f61c8..eab8b89 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,6 +1,11 @@ postfwd (1.10pre7c-3) unstable; urgency=low * implement machine-interpretable copyright file + * fix init script + - let daemon write pid file for his own + - point start-stop daemon to pidfile when stoping + - fix reload by fixing the way how to get the pid + * fix example-cfg2.txt to work with 1.10pre7 -- Jan Wagner Sun, 20 Jul 2008 13:27:42 +0200 diff --git a/debian/example-cfg2.txt b/debian/example-cfg2.txt index b8baf21..e8ef10a 100644 --- a/debian/example-cfg2.txt +++ b/debian/example-cfg2.txt @@ -2,7 +2,7 @@ # check for more recent versions! ### -### Example config for postfwd 1.10pre8+ +### Example config for postfwd 1.10pre7+ ### ## Check DNS whitelists, maybe we don't need more checks @@ -31,7 +31,7 @@ id=REJECT_HELO_NODNS; \ id=REJECT_RBL_ZEN; \ rbl=zen.spamhaus.org; \ - action=REJECT Blocked - contact postmaster@example.net for help - DNSBL [$$dnsbltext] + action=REJECT Blocked - contact postmaster@example.net for help - zen.spamhaus.org RBL ## Check other DNSBLs in parallel @@ -50,11 +50,11 @@ id=REJECT_RBL_ZEN; \ id=EVAL_DNSBLS; \ &&DNSBLS; rblcount=all; \ - action=set(HIT_rbls=$$rblcount,HIT_dtxt=$$dnsbltext) + action=set(HIT_rbls=$$rblcount) id=REJECT_RBL_MULTI; \ HIT_rbls>=2; \ - action=REJECT Blocked - contact postmaster@example.net for help - Multiple DNSBLs [$$HIT_dtxt] + action=REJECT Blocked - contact postmaster@example.net for help - Multiple DNSBLs ## Check RHSBLs if there wasn't enough DNSBLs hit @@ -73,33 +73,33 @@ id=REJECT_RBL_MULTI; \ id=EVAL_RHSBLS; \ &&RHSBLS_REVERSE; &&RHSBLS_SENDER; rhsblcount=all; \ - action=set(HIT_rhsbls=$$rhsblcount,HIT_rtxt=$$dnsbltext) + action=set(HIT_rhsbls=$$rhsblcount) id=REJECT_RHSBL_MULTI; \ HIT_rhsbls>=2; \ - action=REJECT Blocked - contact postmaster@example.net for help - Multiple RHSBLs [$$HIT_rtxt] + action=REJECT Blocked - contact postmaster@example.net for help - Multiple RHSBLs ## See if we get any combined hits from rules before id=REJECT_RBL_RHSBL; \ HIT_rbls>=1; HIT_rhsbls>=1; \ - action=REJECT Blocked - contact postmaster@example.net for help - RHSBL and DNSBL [$$HIT_rtxt] [$$HIT_dtxt] + action=REJECT Blocked - contact postmaster@example.net for help - RHSBL and DNSBL id=REJECT_RBL_HELO; \ HIT_rbls>=1; HIT_helo==1; \ - action=REJECT Blocked - contact postmaster@example.net for help - DNSBL [$$HIT_dtxt] and suspicious HELO [$$helo_name] + action=REJECT Blocked - contact postmaster@example.net for help - DNSBL and suspicious HELO [$$helo_name] id=REJECT_RBL_NODNS; \ HIT_rbls>=1; HIT_nodns==1; \ - action=REJECT Blocked - contact postmaster@example.net for help - DNSBL [$$HIT_dtxt] and missing reverse DNS [$$client_address] + action=REJECT Blocked - contact postmaster@example.net for help - DNSBL and missing reverse DNS [$$client_address] id=REJECT_RHSBL_HELO; \ HIT_rhsbls>=1; HIT_helo==1; \ - action=REJECT Blocked - contact postmaster@example.net for help - RHSBL [$$HIT_rtxt] and suspicious HELO [$$helo_name] + action=REJECT Blocked - contact postmaster@example.net for help - RHSBL and suspicious HELO [$$helo_name] id=REJECT_RHSBL_NODNS; \ HIT_rhsbls>=1; HIT_nodns==1; \ - action=REJECT Blocked - contact postmaster@example.net for help - RHSBL [$$HIT_rtxt] and missing reverse DNS [$$client_address] + action=REJECT Blocked - contact postmaster@example.net for help - RHSBL and missing reverse DNS [$$client_address] ## Finally greylist all lesser hits. ## diff --git a/debian/init.d b/debian/init.d index 2672648..dcbd703 100644 --- a/debian/init.d +++ b/debian/init.d @@ -19,6 +19,7 @@ PATH=/sbin:/bin:/usr/sbin:/usr/bin NAME=postfwd DAEMON=/usr/sbin/${NAME} +PIDFILE=/var/run/$NAME.pid DESC=postfwd test -x $DAEMON || exit 0 @@ -70,19 +71,17 @@ case "$1" in echo -n "Starting $DESC: " start-stop-daemon --start --quiet \ --name ${RUNAS} \ - --exec $DAEMON -- ${ARGS} --daemon --file=${CONF} --interface=${INET} --port=${PORT} --user=${RUNAS} --group=${RUNAS} + --exec $DAEMON -- ${ARGS} --daemon --file=${CONF} --interface=${INET} --port=${PORT} --user=${RUNAS} --group=${RUNAS} --pidfile=$PIDFILE echo "$NAME." ;; stop) echo -n "Stopping $DESC: " - start-stop-daemon --stop --quiet --oknodo \ - --exec $DAEMON + start-stop-daemon --stop --quiet --oknodo --pidfile $PIDFILE && rm -rf $PIDFILE echo "$NAME." - rm -f /var/run/$NAME.pid ;; reload) echo "Reloading $DESC configuration files." - for pid in `pidof ${NAME}`; do kill -HUP ${pid}; done ; + kill -HUP $(cat $PIDFILE) ;; restart|force-reload) echo -n "Restarting $DESC (incl. cache): " From 6ea308e66e48a414c0f77704fa6240d3a6bcab6a Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Fri, 31 Oct 2008 08:56:16 +0000 Subject: [PATCH 023/159] provide bugreports --- debian/changelog | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/debian/changelog b/debian/changelog index eab8b89..509f52b 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,13 +1,13 @@ postfwd (1.10pre7c-3) unstable; urgency=low * implement machine-interpretable copyright file - * fix init script + * fix init script (Closes: #503597). - let daemon write pid file for his own - point start-stop daemon to pidfile when stoping - fix reload by fixing the way how to get the pid - * fix example-cfg2.txt to work with 1.10pre7 + * fix example-cfg2.txt to work with 1.10pre7 (Closes: #503596). - -- Jan Wagner Sun, 20 Jul 2008 13:27:42 +0200 + -- Jan Wagner Fri, 31 Oct 2008 09:55:52 +0100 postfwd (1.10pre7c-2) unstable; urgency=low From f51db916bbc6ea8c5498c12c53b70480b0d2b24b Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Thu, 19 Feb 2009 21:57:22 +0000 Subject: [PATCH 024/159] new upstream release --- debian/changelog | 19 +++++++++++++++++++ debian/control | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index 509f52b..6690556 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,22 @@ +postfwd (1.10pre8b-1) unstable; urgency=low + + * new upstream release + - Net::CIDR::Lite is not required any longer + - Net::DNS::Async is no longer used + - changed Net::Server behaviour to ignore syslog errors + - --shortlog is now default behaviour (use -v to see more) + - days=Wed now means exactly Wednesday + - disabled fallback to synchronous dns on timed out rbls + - new item "rhsbl_helo" allows to check helo against rhsbls + - the new variable $$request_hits contains a list of all matching ruleids + - the new variable $$dnsbltext allows access to txt records of rbls + - new options --no-rulestats and --nodnslog + - ttls of the dns responses override --cache-rbl-timeout when bigger + * drop dependency of libnet-cidr-lite-perl and libnet-dns-async-perl + * add dependency of libnet-dns-perl + + -- Jan Wagner Thu, 19 Feb 2009 22:39:09 +0100 + postfwd (1.10pre7c-3) unstable; urgency=low * implement machine-interpretable copyright file diff --git a/debian/control b/debian/control index 9cb0079..3117ed5 100644 --- a/debian/control +++ b/debian/control @@ -10,7 +10,7 @@ Standards-Version: 3.8.0 Package: postfwd Architecture: all -Depends: ${perl:Depends}, ${misc:Depends}, adduser, libnet-cidr-lite-perl, libnet-dns-async-perl, libnet-server-perl +Depends: ${perl:Depends}, ${misc:Depends}, adduser, libnet-dns-perl, libnet-server-perl Description: Postfix policyd to combine complex restrictions in a ruleset Postfwd is written in perl to combine complex postfix restrictions in a ruleset similar to those of the most firewalls. The program uses the postfix From 8adbccfd04747a91e0863e1922f7080194de98d7 Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Thu, 19 Feb 2009 21:58:10 +0000 Subject: [PATCH 025/159] new release --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index 6690556..e4e2240 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +postfwd (1.10pre8b-2) UNRELEASED; urgency=low + + * NOT RELEASED YET + + -- Jan Wagner Thu, 19 Feb 2009 22:57:47 +0100 + postfwd (1.10pre8b-1) unstable; urgency=low * new upstream release From 2874894fcae7173fdfe363c61cac5a679c447235 Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Sun, 28 Jun 2009 11:51:45 +0000 Subject: [PATCH 026/159] new upstream release --- debian/README.Debian | 4 ++++ debian/changelog | 19 ++++++++++++++++--- debian/rules | 2 +- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/debian/README.Debian b/debian/README.Debian index fc03b89..9003d76 100644 --- a/debian/README.Debian +++ b/debian/README.Debian @@ -9,6 +9,10 @@ located in /usr/share/doc/postfwd/examples/. An other can be found at http://hege.li/howto/spam/etc/postfwd/postfwd.conf and is provided as example-cfg2.txt. +A quickstart guide is available at http://www.postfwd.org/quick.html and the +online documentation at http://www.postfwd.org/doc.html, the offline version +can be viewed with 'postfix -m'. + 2. AUTOMATIC STARTUP -------------------- diff --git a/debian/changelog b/debian/changelog index e4e2240..49b4dd7 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,21 @@ -postfwd (1.10pre8b-2) UNRELEASED; urgency=low +postfwd (1.13-1) unstable; urgency=low - * NOT RELEASED YET + * new upstream release + - enabled dns cache for sender(ns|mx) and helo address + - new options --dns_max_ns_lookups and --dns_max_mx_lookups + - new items sender_ns_names and sender_ns_addrs + - new items sender_mx_names and sender_mx_addrs + - new item helo_address, please see docs for more + - added --proto switch, to enable the use of unix domain sockets + - added command-line options --kill and --reload + - dnsbl txt lookups only for dnsbls with at least one a record + - small performance improvement + - ask() action allows to use another policy service + - new options --noidlestats and --norulelog + * install postfwd.cf.sample, was renamed upstream + * leave hint about documentation in README.Debian - -- Jan Wagner Thu, 19 Feb 2009 22:57:47 +0100 + -- Jan Wagner Sun, 28 Jun 2009 13:27:18 +0200 postfwd (1.10pre8b-1) unstable; urgency=low diff --git a/debian/rules b/debian/rules index 4a71dc6..0d77979 100755 --- a/debian/rules +++ b/debian/rules @@ -24,7 +24,7 @@ binary-indep: build install dh_testroot dh_installchangelogs doc/CHANGELOG dh_installdocs tools - dh_installexamples etc/postfwd.cf debian/example-cfg* + dh_installexamples etc/postfwd.cf.sample debian/example-cfg* dh_installinit -- defaults 19 21 dh_installman man/man8/postfwd.8 dh_link From ea74626a0daa94459897bc54d887bf8925fa88a1 Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Mon, 29 Jun 2009 22:05:26 +0000 Subject: [PATCH 027/159] new upstream release --- debian/README.Debian | 40 +++++++++++++++++++++++++++++++++++++++- debian/changelog | 9 ++++++--- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/debian/README.Debian b/debian/README.Debian index 9003d76..45989b6 100644 --- a/debian/README.Debian +++ b/debian/README.Debian @@ -13,7 +13,45 @@ A quickstart guide is available at http://www.postfwd.org/quick.html and the online documentation at http://www.postfwd.org/doc.html, the offline version can be viewed with 'postfix -m'. -2. AUTOMATIC STARTUP +2. VERIFY CONFIG +---------------- + +How interpret the parser your rules, you can check with: + +# postfwd -f /etc/postfix/postfwd.cf -C -v + +Check your rules against sample request: + +# cat request.sample | postfwd -f /etc/postfix/postfwd.cf -L + +# cat request.sample + +------ snip ------- +ccert_fingerprint= +size=64063 +helo_name=english-breakfast.cloud9.net +reverse_client_name=english-breakfast.cloud9.net +queue_id= +encryption_cipher= +encryption_protocol= +etrn_domain= +ccert_subject= +request=smtpd_access_policy +protocol_state=RCPT +recipient=someone@domain.local +instance=6748.46adf3f8.62156.0 +protocol_name=ESMTP +encryption_keysize=0 +recipient_count=0 +ccert_issuer= +sender=owner-postfix-users@postfix.org +client_name=english-breakfast.cloud9.net +client_address=168.100.1.7 +------ snip ------- + +Samples can be taken into the logfile when starting the daemon with "-vv" + +3. AUTOMATIC STARTUP -------------------- In order to avoid the startup of the daemon on an unconfigured machine, diff --git a/debian/changelog b/debian/changelog index 49b4dd7..3d4d4c5 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,6 +1,9 @@ -postfwd (1.13-1) unstable; urgency=low +postfwd (1.14-1) unstable; urgency=low * new upstream release + - new compare operators + - added --nodaemon option + - perform non dns items first - enabled dns cache for sender(ns|mx) and helo address - new options --dns_max_ns_lookups and --dns_max_mx_lookups - new items sender_ns_names and sender_ns_addrs @@ -13,9 +16,9 @@ postfwd (1.13-1) unstable; urgency=low - ask() action allows to use another policy service - new options --noidlestats and --norulelog * install postfwd.cf.sample, was renamed upstream - * leave hint about documentation in README.Debian + * leave hints about documentation and config verification in README.Debian - -- Jan Wagner Sun, 28 Jun 2009 13:27:18 +0200 + -- Jan Wagner Mon, 29 Jun 2009 23:49:17 +0200 postfwd (1.10pre8b-1) unstable; urgency=low From 2b381673acdce6efe30dbbb143318f3695007929 Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Mon, 29 Jun 2009 22:09:10 +0000 Subject: [PATCH 028/159] bump standards --- debian/changelog | 1 + debian/control | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index 3d4d4c5..e844b8e 100644 --- a/debian/changelog +++ b/debian/changelog @@ -17,6 +17,7 @@ postfwd (1.14-1) unstable; urgency=low - new options --noidlestats and --norulelog * install postfwd.cf.sample, was renamed upstream * leave hints about documentation and config verification in README.Debian + * Bump standards version (no changes) -- Jan Wagner Mon, 29 Jun 2009 23:49:17 +0200 diff --git a/debian/control b/debian/control index 3117ed5..cb90f72 100644 --- a/debian/control +++ b/debian/control @@ -6,7 +6,7 @@ Build-Depends: debhelper (>= 5) Homepage: http://www.postfwd.org/ Vcs-Browser: https://trac.cyconet.org/debian/browser/debian/postfwd Vcs-Svn: https://trac.cyconet.org/svn/debian/postfwd -Standards-Version: 3.8.0 +Standards-Version: 3.8.2 Package: postfwd Architecture: all From 467a554679475a2ab91ef15d68b098c8504b2c93 Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Mon, 6 Jul 2009 19:16:14 +0000 Subject: [PATCH 029/159] verbose changelog --- debian/changelog | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/debian/changelog b/debian/changelog index e844b8e..39a68fd 100644 --- a/debian/changelog +++ b/debian/changelog @@ -17,9 +17,9 @@ postfwd (1.14-1) unstable; urgency=low - new options --noidlestats and --norulelog * install postfwd.cf.sample, was renamed upstream * leave hints about documentation and config verification in README.Debian - * Bump standards version (no changes) + * Bump standards version to 3.8.2 (no changes needed) - -- Jan Wagner Mon, 29 Jun 2009 23:49:17 +0200 + -- Jan Wagner Mon, 06 Jul 2009 21:15:35 +0200 postfwd (1.10pre8b-1) unstable; urgency=low From 31e2e69bb10028e791863fdec10906a6558df916 Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Sat, 25 Jul 2009 23:40:55 +0000 Subject: [PATCH 030/159] fixup copyright --- debian/changelog | 6 ++++++ debian/copyright | 6 +++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/debian/changelog b/debian/changelog index 39a68fd..ec85a70 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +postfwd (1.14-2) UNRELEASED; urgency=low + + * add "Copyright" to all copyrights in debian/copyright + + -- Jan Wagner Sun, 26 Jul 2009 01:34:03 +0200 + postfwd (1.14-1) unstable; urgency=low * new upstream release diff --git a/debian/copyright b/debian/copyright index 7878293..eb7ac45 100644 --- a/debian/copyright +++ b/debian/copyright @@ -6,15 +6,15 @@ Upstream-Maintainer: Jan Peter Kessler Upstream-Source: http://www.postfwd.org Files: * -Copyright: (c) 2007, Jan Peter Kessler, All rights reserved. +Copyright: Copyright (c) 2007, Jan Peter Kessler, All rights reserved. License: BSD-3 Files: debian/* -Copyright: (C) 2006, 2008 Jan Wagner +Copyright: Copyright (C) 2006, 2008 Jan Wagner License: GPL-2+ Files: debian/example-cfg2.txt -Copyright: (c) 2008, Henrik Krohns +Copyright: Copyright (c) 2008, Henrik Krohns License: BSD-3 License: BSD-3 From 4c0a32b283bc41c6a0ee004456674eae845245a7 Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Thu, 14 Jan 2010 18:28:19 +0000 Subject: [PATCH 031/159] new uptream --- debian/changelog | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/debian/changelog b/debian/changelog index ec85a70..a553667 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,6 +1,14 @@ -postfwd (1.14-2) UNRELEASED; urgency=low +postfwd (1.16-1) UNRELEASED; urgency=low - * add "Copyright" to all copyrights in debian/copyright + * NEW upstream release + - documentation fixed + - configuration parser improvements + - option --reload (HUP signal) now reloads config, if the file is unchanged + - redirect syslog to stdout for --kill, --reload and --showconfig + - new rcpt() command counts recipients for rate limits + - helo_address, and sender_(ns|mx)_addrs can now be csv items + - items may now be retrieved from files using "item=file:/some/where" + * Add "Copyright" to all copyrights in debian/copyright -- Jan Wagner Sun, 26 Jul 2009 01:34:03 +0200 From 473ea9970f44641570e55a8fc662e347f3a813f5 Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Thu, 14 Jan 2010 18:33:12 +0000 Subject: [PATCH 032/159] fix spelling errors --- debian/README.Debian | 4 ++-- debian/changelog | 6 ++++-- debian/control | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/debian/README.Debian b/debian/README.Debian index 45989b6..508b7df 100644 --- a/debian/README.Debian +++ b/debian/README.Debian @@ -4,9 +4,9 @@ postfwd for Debian 1. PROVIDE A CONFIGFILE ----------------------- -Please provide a config file, usualy /etc/postfix/postfwd.cf. Examples are +Please provide a config file, usually /etc/postfix/postfwd.cf. Examples are located in /usr/share/doc/postfwd/examples/. -An other can be found at http://hege.li/howto/spam/etc/postfwd/postfwd.conf +Another can be found at http://hege.li/howto/spam/etc/postfwd/postfwd.conf and is provided as example-cfg2.txt. A quickstart guide is available at http://www.postfwd.org/quick.html and the diff --git a/debian/changelog b/debian/changelog index a553667..1a1c65e 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -postfwd (1.16-1) UNRELEASED; urgency=low +postfwd (1.16-1) unstable; urgency=low * NEW upstream release - documentation fixed @@ -9,8 +9,10 @@ postfwd (1.16-1) UNRELEASED; urgency=low - helo_address, and sender_(ns|mx)_addrs can now be csv items - items may now be retrieved from files using "item=file:/some/where" * Add "Copyright" to all copyrights in debian/copyright + * Bump standards version to 3.8.3 (no changes needed) + * Fix speeling errors in debian/README.Debian - -- Jan Wagner Sun, 26 Jul 2009 01:34:03 +0200 + -- Jan Wagner Thu, 14 Jan 2010 19:32:26 +0100 postfwd (1.14-1) unstable; urgency=low diff --git a/debian/control b/debian/control index cb90f72..f05c003 100644 --- a/debian/control +++ b/debian/control @@ -6,7 +6,7 @@ Build-Depends: debhelper (>= 5) Homepage: http://www.postfwd.org/ Vcs-Browser: https://trac.cyconet.org/debian/browser/debian/postfwd Vcs-Svn: https://trac.cyconet.org/svn/debian/postfwd -Standards-Version: 3.8.2 +Standards-Version: 3.8.3 Package: postfwd Architecture: all From 50395b4f55c71baeec119fbca1455a08d1e0a660 Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Tue, 23 Feb 2010 15:44:51 +0000 Subject: [PATCH 033/159] * Bump Standards-Version to 3.8.4, no changes needed * Migrate Vcs-Fields over to scm.uncompleted.org * Add 1.0 to debian/source/format --- debian/changelog | 8 ++++++++ debian/control | 6 +++--- debian/source/format | 1 + 3 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 debian/source/format diff --git a/debian/changelog b/debian/changelog index 1a1c65e..5177ddd 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +postfwd (1.16-2) UNRELEASED; urgency=low + + * Bump Standards-Version to 3.8.4, no changes needed + * Migrate Vcs-Fields over to scm.uncompleted.org + * Add 1.0 to debian/source/format + + -- Jan Wagner Thu, 14 Jan 2010 19:54:06 +0100 + postfwd (1.16-1) unstable; urgency=low * NEW upstream release diff --git a/debian/control b/debian/control index f05c003..6b45680 100644 --- a/debian/control +++ b/debian/control @@ -4,9 +4,9 @@ Priority: optional Maintainer: Jan Wagner Build-Depends: debhelper (>= 5) Homepage: http://www.postfwd.org/ -Vcs-Browser: https://trac.cyconet.org/debian/browser/debian/postfwd -Vcs-Svn: https://trac.cyconet.org/svn/debian/postfwd -Standards-Version: 3.8.3 +Vcs-Browser: https://scm.uncompleted.org/projects/debian/repository/show/postfwd +Vcs-Svn: https://scm.uncompleted.org/svn/debian/postfwd +Standards-Version: 3.8.4 Package: postfwd Architecture: all diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +1.0 From 8e3a76093b64e7e89f4963bf70927b50ea5089f5 Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Wed, 10 Mar 2010 08:16:15 +0000 Subject: [PATCH 034/159] Add trailing trunk/ at Vcs-Svn-field --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 6b45680..5e8f08d 100644 --- a/debian/control +++ b/debian/control @@ -5,7 +5,7 @@ Maintainer: Jan Wagner Build-Depends: debhelper (>= 5) Homepage: http://www.postfwd.org/ Vcs-Browser: https://scm.uncompleted.org/projects/debian/repository/show/postfwd -Vcs-Svn: https://scm.uncompleted.org/svn/debian/postfwd +Vcs-Svn: https://scm.uncompleted.org/svn/debian/postfwd/trunk Standards-Version: 3.8.4 Package: postfwd From 159b81fed2b5e0620424ce4293cea6a94a403768 Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Wed, 10 Mar 2010 11:36:13 +0000 Subject: [PATCH 035/159] prepare release --- debian/changelog | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/debian/changelog b/debian/changelog index 5177ddd..d37ee43 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,10 +1,10 @@ -postfwd (1.16-2) UNRELEASED; urgency=low +postfwd (1.16-2) unstable; urgency=low * Bump Standards-Version to 3.8.4, no changes needed * Migrate Vcs-Fields over to scm.uncompleted.org * Add 1.0 to debian/source/format - -- Jan Wagner Thu, 14 Jan 2010 19:54:06 +0100 + -- Jan Wagner Wed, 10 Mar 2010 12:35:57 +0100 postfwd (1.16-1) unstable; urgency=low From 13ed17b74564d73a38ecbd725de81c37ce21bf36 Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Wed, 10 Mar 2010 11:37:57 +0000 Subject: [PATCH 036/159] new changelog --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index d37ee43..8c191ca 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +postfwd (1.16-3) UNRELEASED; urgency=low + + * NOT RELEASED YET + + -- Jan Wagner Wed, 10 Mar 2010 12:37:45 +0100 + postfwd (1.16-2) unstable; urgency=low * Bump Standards-Version to 3.8.4, no changes needed From 4d6f363860b88b67c2e448331bfa5c166d836148 Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Mon, 22 Mar 2010 08:02:23 +0000 Subject: [PATCH 037/159] new upstrem --- debian/changelog | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/debian/changelog b/debian/changelog index 8c191ca..868a2d9 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,6 +1,10 @@ -postfwd (1.16-3) UNRELEASED; urgency=low +postfwd (1.17-1) UNRELEASED; urgency=low - * NOT RELEASED YET + * New upstream release + - Net::DNS internal errors will now be handled gracefully + - default for options --dns_max_ns_a_lookups and --dns_max_mx_a_lookups of + 100 + - Fixed variable substitution when the '=' operator is used -- Jan Wagner Wed, 10 Mar 2010 12:37:45 +0100 From 017f6192d7a670d9519d56e556b1e085a0607e3d Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Mon, 22 Mar 2010 08:07:16 +0000 Subject: [PATCH 038/159] release --- debian/changelog | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/debian/changelog b/debian/changelog index 868a2d9..f9ed718 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -postfwd (1.17-1) UNRELEASED; urgency=low +postfwd (1.17-1) unstable; urgency=low * New upstream release - Net::DNS internal errors will now be handled gracefully @@ -6,7 +6,7 @@ postfwd (1.17-1) UNRELEASED; urgency=low 100 - Fixed variable substitution when the '=' operator is used - -- Jan Wagner Wed, 10 Mar 2010 12:37:45 +0100 + -- Jan Wagner Mon, 22 Mar 2010 09:02:31 +0100 postfwd (1.16-2) unstable; urgency=low From f61ad1d51dc31df09afe7ceabe68f7e0d5de0f13 Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Mon, 22 Mar 2010 08:14:49 +0000 Subject: [PATCH 039/159] new changelog --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index f9ed718..2d4f107 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +postfwd (1.17-2) UNRELEASED; urgency=low + + * NOT RELEASED YET + + -- Jan Wagner Mon, 22 Mar 2010 09:07:34 +0100 + postfwd (1.17-1) unstable; urgency=low * New upstream release From 392f384c59bd6ff3b70c0439ede342a4070b7d97 Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Thu, 29 Apr 2010 06:48:34 +0000 Subject: [PATCH 040/159] new release --- debian/changelog | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/debian/changelog b/debian/changelog index 2d4f107..3bc9003 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,10 @@ -postfwd (1.17-2) UNRELEASED; urgency=low +postfwd (1.18-1) unstable; urgency=low - * NOT RELEASED YET + * New upstream release + - Fixed bug when comparing sender and recipient addresses, like + "sender=$$recipient" - -- Jan Wagner Mon, 22 Mar 2010 09:07:34 +0100 + -- Jan Wagner Thu, 29 Apr 2010 08:46:25 +0200 postfwd (1.17-1) unstable; urgency=low From c69417d934db70ab69b10e2361c9dd1df823b9d0 Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Thu, 29 Apr 2010 06:49:50 +0000 Subject: [PATCH 041/159] new changelog --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index 3bc9003..a44926f 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +postfwd (1.18-2) UNRELEASED; urgency=low + + * NOT RELEASED YET + + -- Jan Wagner Thu, 29 Apr 2010 08:49:20 +0200 + postfwd (1.18-1) unstable; urgency=low * New upstream release From 47f61ec7089ea54fefc6a37de654eca479857da0 Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Wed, 16 Jun 2010 12:12:15 +0000 Subject: [PATCH 042/159] add dpatch infrastructure --- debian/changelog | 2 +- debian/control | 2 +- debian/rules | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/debian/changelog b/debian/changelog index a44926f..3f55f60 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,6 +1,6 @@ postfwd (1.18-2) UNRELEASED; urgency=low - * NOT RELEASED YET + * Add dpatch infrastructure -- Jan Wagner Thu, 29 Apr 2010 08:49:20 +0200 diff --git a/debian/control b/debian/control index 5e8f08d..c932674 100644 --- a/debian/control +++ b/debian/control @@ -2,7 +2,7 @@ Source: postfwd Section: mail Priority: optional Maintainer: Jan Wagner -Build-Depends: debhelper (>= 5) +Build-Depends: debhelper (>= 5), dpatch Homepage: http://www.postfwd.org/ Vcs-Browser: https://scm.uncompleted.org/projects/debian/repository/show/postfwd Vcs-Svn: https://scm.uncompleted.org/svn/debian/postfwd/trunk diff --git a/debian/rules b/debian/rules index 0d77979..539d0f9 100755 --- a/debian/rules +++ b/debian/rules @@ -4,9 +4,11 @@ # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 -build: +include /usr/share/dpatch/dpatch.make -clean: +build: patch-stamp + +clean: unpatch dh_testdir dh_testroot dh_clean From e11360a2d6bd9d5d5520e5f2eb101d3dbc5b9fcd Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Wed, 16 Jun 2010 12:20:15 +0000 Subject: [PATCH 043/159] add missing documentation --- debian/README.source | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 debian/README.source diff --git a/debian/README.source b/debian/README.source new file mode 100644 index 0000000..f0fe49a --- /dev/null +++ b/debian/README.source @@ -0,0 +1,3 @@ +We use dpatch for patch handling inside our package(s). Please see +/usr/share/doc/dpatch/README.source.gz (if you have installed dpatch) for +documentation about dpatch. From d43172b6faeab93925438bbd9263fde441f482f7 Mon Sep 17 00:00:00 2001 From: Jan Wagner Date: Wed, 16 Jun 2010 12:38:03 +0000 Subject: [PATCH 044/159] add postfwd2 --- debian/patches/00list | 1 + debian/patches/01_postfwd2.dpatch | 4127 +++++++++++++++++++++++++++++ 2 files changed, 4128 insertions(+) create mode 100644 debian/patches/00list create mode 100755 debian/patches/01_postfwd2.dpatch diff --git a/debian/patches/00list b/debian/patches/00list new file mode 100644 index 0000000..7fade7d --- /dev/null +++ b/debian/patches/00list @@ -0,0 +1 @@ +01_postfwd2 diff --git a/debian/patches/01_postfwd2.dpatch b/debian/patches/01_postfwd2.dpatch new file mode 100755 index 0000000..61f80f5 --- /dev/null +++ b/debian/patches/01_postfwd2.dpatch @@ -0,0 +1,4127 @@ +#! /bin/sh /usr/share/dpatch/dpatch-run +## 01_postfwd2.dpatch by Jan Wagner +## +## DP: Add postfwd2 + +@DPATCH@ +diff -urNad '--exclude=CVS' '--exclude=.svn' '--exclude=.git' '--exclude=.arch' '--exclude=.hg' '--exclude=_darcs' '--exclude=.bzr' postfwd-1.18~/sbin/postfwd2 postfwd-1.18/sbin/postfwd2 +--- postfwd-1.18~/sbin/postfwd2 1970-01-01 01:00:00.000000000 +0100 ++++ postfwd-1.18/sbin/postfwd2 2010-04-28 20:16:44.000000000 +0200 +@@ -0,0 +1,4117 @@ ++#!/usr/bin/perl -T -w ++ ++############################ ++package postfwd2::basic; ++ ++use warnings; ++use strict; ++use IO::Socket qw(SOCK_STREAM); ++use Sys::Syslog qw(:DEFAULT setlogsock); ++# export ++use Exporter qw(import); ++our @EXPORT = qw( ++ %postfwd_settings %postfwd_patterns ++ &uniq &init_log &log_info &log_note ++ &log_warn &log_err &log_crit ++); ++our @EXPORT_OK = qw( ++ %postfwd_commands ++ &wantsdebug &hash_to_list ++ &hash_to_str &str_to_hash ++ &check_inet &check_unix ++); ++ ++# basics ++our $NAME = "postfwd2"; ++our $VERSION = "0.21"; ++our $DEFAULT = 'DUNNO'; ++ ++# change this, to match your POD requirements ++# we need pod2text for the -m switch (manual) ++$ENV{PATH} = "/bin:/usr/bin:/usr/local/bin"; ++$ENV{ENV} = ""; ++our($cmd_manual) = "pod2text"; ++our($cmd_pager) = "more"; ++ ++my $sepreq = '///'; ++my $seplst = ':::'; ++my $nounixsock = ($^O eq 'solaris'); ++ ++# program settings ++our %postfwd_settings = ( ++ base => { ++ user => 'nobody', ++ group => 'nobody', ++ #setsid => 1, ++ log_level => 2, ++ log_file => 'Sys::Syslog', ++ syslog_logopt => 'pid', ++ syslog_facility => 'mail', ++ syslog_ident => "$NAME", ++ #chroot => $net_chroot ? $net_chroot : undef, ++ }, ++ master => { ++ pid_file => "/var/tmp/$NAME-master.pid", ++ watchdog => 60, ++ failures => 7, ++ respawn => 4, ++ daemons => [ 'cache', 'server' ], ++ }, ++ cache => { ++ commandline => " ".$NAME."::cache", ++ syslog_ident => "$NAME/cache", ++ host => (($nounixsock) ? "127.0.0.1" : ""), ++ port => (($nounixsock) ? "10043" : "/var/tmp/$NAME-cache.socket"), ++ proto => (($nounixsock) ? "tcp" : "unix"), ++ check => (($nounixsock) ? \&check_inet : \&check_unix), ++ }, ++ server => { ++ commandline => " ".$NAME."::policy", ++ syslog_ident => "$NAME/policy", ++ host => '127.0.0.1', ++ port => 10045, ++ proto => "tcp", ++ check => \&check_inet, ++ # child control ++ #check_for_dead => 30, ++ #check_for_waiting => 10, ++ min_spare_servers => 5, ++ min_servers => 10, ++ max_spare_servers => 50, ++ max_servers => 100, ++ max_requests => 1000, ++ child_communication => 1, # children report data to parent for summary ++ leave_children_open_on_hup => 1, # children should finish their work ++ }, ++ syslog => { ++ nolog => 0, ++ noidlestats => 0, ++ norulestats => 0, ++ name => $NAME, ++ facility => 'mail', ++ options => 'pid', ++ # allow "umlaute" ;) ++ #unsafe_charset => qr/[^\x20-\x7E,\x80-\xFE]/, ++ unsafe_charset => qr/[^\x20-\x7E]/, ++ unsafe_version => (not(defined $Sys::Syslog::VERSION) or $Sys::Syslog::VERSION lt '0.15'), ++ perfmon => 0, ++ stdout => 0, ++ }, ++ timeout => { ++ rule => 40, ++ cache => 3, ++ server => 3, ++ config => 4, ++ }, ++ request => { ++ ttl => 600, ++ cleanup => 600, ++ no_sender => 0, ++ rdomain_only => 0, ++ no_size => 0, ++ nolog => 0, ++ noparent => 0, ++ }, ++ dns => { ++ disable => 0, ++ nolog => 0, ++ noparent => 1, ++ anylog => 0, ++ async_txt => 0, ++ timeout => 14, ++ max_timeout => 10, ++ max_interval => 1200, ++ ttl => 3600, ++ cleanup => 600, ++ mask => '^127\.', ++ max_ns_lookups => 100, ++ max_mx_lookups => 100, ++ }, ++ rate => { ++ cleanup => 600, ++ noparent => 0, ++ }, ++ scores => { ++ "5.0" => "554 5.7.1 ".$NAME." score exceeded", ++ }, ++ debug => { ++ #all => 0, ++ #verbose => 0, ++ #cache => 0, ++ #getcache => 0, ++ #setcache => 0, ++ #dns => 0, ++ #getdns => 0, ++ #setdns => 0, ++ }, ++ name => $NAME, ++ version => $VERSION, ++ default => $DEFAULT, ++ daemon => 1, ++ manual => $cmd_manual, ++ pager => $cmd_pager, ++ sepreq => $sepreq, ++ seplst => $seplst, ++ summary => 600, ++ instant => 0, ++ verbose => 0, ++ test => 0, ++); ++ ++# daemon commands ++our %postfwd_commands = ( ++ ping => 'PING', ++ pong => 'PONG', ++ dumpstats => 'DS', ++ dumpcache => 'DC', ++ #wipecache => 'WC', ++ countcache => 'CN', ++ matchcache => 'MT', ++ setcacheitem => 'SC', ++ getcacheitem => 'GC', ++ getcacheval => 'GV', ++); ++ ++# precompiled patterns ++our %postfwd_patterns = ( ++ ping => $postfwd_commands{ping}, ++ pong => $postfwd_commands{pong}, ++ keyval => qr/^([^=]+)=(.*)$/, ++ cntval => qr/^([^=]+)=(\d+)$/, ++ command => qr/^CMD\s*=/i, ++ dumpstats => qr/^CMD\s*=\s*$postfwd_commands{dumpstats}\s*;\s*$/i, ++ dumpcache => qr/^CMD\s*=\s*$postfwd_commands{dumpcache}\s*;\s*$/i, ++ #wipecache => qr/^CMD\s*=\s*$postfwd_commands{wipecache}\s*;\s*$/i, ++ countcache => qr/^CMD\s*=\s*$postfwd_commands{countcache}\s*;\s*TYPE\s*=\s*(.*?)\s*$/i, ++ matchcache => qr/^CMD\s*=\s*$postfwd_commands{matchcache}\s*;\s*TYPE\s*=\s*(.*?)\s*$/i, ++ setcacheitem => qr/^CMD\s*=\s*$postfwd_commands{setcacheitem}\s*;\s*TYPE\s*=\s*([^;]+)\s*;\s*ITEM\s*=\s*(.*?)\s*$sepreq\s*(.*?)\s*$/i, ++ getcacheitem => qr/^CMD\s*=\s*$postfwd_commands{getcacheitem}\s*;\s*TYPE\s*=\s*([^;]+)\s*;\s*ITEM\s*=\s*(.*?)\s*$/i, ++ getcacheval => qr/^CMD\s*=\s*$postfwd_commands{getcacheval}\s*;\s*TYPE\s*=\s*([^;]+)\s*;\s*ITEM\s*=\s*(.*?)\s*$sepreq\s*KEY\s*=\s*(.*?)\s*$/i, ++); ++ ++ ++## SUBS ++ ++# takes a list and returns a unified list, keeping given order ++sub uniq { ++ undef my %uniq; ++ return grep(!$uniq{$_}++, @_); ++}; ++ ++# tests debug levels ++sub wantsdebug { ++ return unless %{$postfwd_settings{debug}}; ++ foreach (@_) { return 1 if $postfwd_settings{debug}{$_} }; ++}; ++ ++# hash -> scalar ++sub hash_to_str { ++ my %request = @_; my $result = ''; ++ map { $result .= $postfwd_settings{sepreq}."$_=".((ref $request{$_} eq 'ARRAY') ? (join $postfwd_settings{seplst}, @{$request{$_}}) : ($request{$_} || '')) } (keys %request); ++ return $result; ++}; ++ ++# scalar -> hash ++sub str_to_hash { ++ my $request = shift; my %result = (); ++ foreach (split $postfwd_settings{sepreq}, $request) { ++ next unless m/$postfwd_patterns{keyval}/; ++ my @items = split $postfwd_settings{seplst}, $2; ++ ($#items) ? @{$result{$1}} = @items : $result{$1} = $2; ++ }; return %result; ++}; ++ ++# displays hash structure ++sub hash_to_list { ++ my ($pre, %request) = @_; my @output = (); ++ # get longest key ++ my $minkey = '-'.(length((sort {length($b) <=> length($a)} (keys %request))[0] || '') + 1); ++ while ( my($s, $v) = each %request ) { ++ my $r = ref $v; ++ if ($r eq 'HASH') { ++ push @output, (%{$v}) ++ ? hash_to_list ( sprintf ("%s -> %".$minkey."s", $pre, '%'.$s), %{$v} ) ++ : sprintf ("%s -> %".$minkey."s -> %s", $pre, '%'.$s, 'undef'); ++ } elsif ($r eq 'ARRAY') { ++ push @output, sprintf ("%s -> %".$minkey."s -> %s", $pre, '@'.$s, ((@{$v}) ? "'".(join ",", @{$v})."'" : 'undef')); ++ } elsif ($r eq 'CODE') { ++ push @output, sprintf ("%s -> %".$minkey."s -> %s", $pre, '&'.$s, ((defined $v) ? "'".$v."'" : 'undef')); ++ } else { ++ push @output, sprintf ("%s -> %".$minkey."s -> %s", $pre, '$'.$s, ((defined $v) ? "'".$v."'" : 'undef')); ++ }; ++ }; ++ return sort { my ($c, $d) = ($a, $b); ++ $c =~ tr/$/1/; $c =~ tr/&/2/; $c =~ tr/@/3/; $c =~ tr/%/4/; ++ $d =~ tr/$/1/; $d =~ tr/&/2/; $d =~ tr/@/3/; $d =~ tr/%/4/; ++ return $c cmp $d; } @output; ++}; ++ ++# Sys::Syslog < 0.15 ++sub mylogs_old { ++ my($prio,$msg) = @_; ++ eval { local $SIG{'__DIE__'}; syslog ($prio,$msg) }; ++}; ++ ++# Sys::Syslog >= 0.15 ++sub mylogs_new { ++ my($prio,$msg) = @_; ++ syslog ($prio,$msg); ++}; ++ ++# Syslog to stdout ++sub mylogs_stdout { ++ my($prio,$msg) = @_; ++ printf STDOUT "[LOG $prio]: $msg\n", @_; ++}; ++ ++# send log message ++sub mylogs { ++ my($prio,$msg) = @_; ++ return if $postfwd_settings{syslog}{nolog}; ++ # escape unsafe characters ++ $msg =~ s/$postfwd_settings{syslog}{unsafe_charset}/?/g; ++ $msg =~ s/\%/%%/g; ++ &{$postfwd_settings{syslog}{logger}} ($prio,$msg); ++}; ++ ++# short versions ++sub log_info { mylogs ('info', @_) }; ++sub log_note { mylogs ('notice', @_) }; ++sub log_warn { mylogs ('warning', @_) }; ++sub log_err { mylogs ('err', @_) }; ++sub log_crit { mylogs ('crit', @_) }; ++ ++# init logging ++sub init_log { ++ my($logname) = @_; ++ $postfwd_settings{syslog}{name} = $logname if $logname; ++ $postfwd_settings{syslog}{socktype} = ($postfwd_settings{syslog}{unsafe_version}) ? (($nounixsock) ? 'inet' : 'unix') : 'native'; ++ if ($postfwd_settings{syslog}{stdout}) { ++ $postfwd_settings{syslog}{logger} = \&mylogs_stdout; ++ } else { ++ # syslog init ++ $postfwd_settings{syslog}{logger} = ($postfwd_settings{syslog}{unsafe_version}) ? \&mylogs_old : \&mylogs_new; ++ setlogsock $postfwd_settings{syslog}{socktype}; ++ openlog $postfwd_settings{syslog}{name}, $postfwd_settings{syslog}{options}, $postfwd_settings{syslog}{facility}; ++ }; ++ log_info ("set up syslogging Sys::Syslog".((defined $Sys::Syslog::VERSION) ? " version $Sys::Syslog::VERSION" : '') ) if wantsdebug (qw[ all verbose ]); ++}; ++ ++# check: INET ++sub check_inet { ++ my ($type,$send) = @_; ++ if ( my $socket = new IO::Socket::INET ( ++ PeerAddr => $postfwd_settings{$type}{host}, ++ PeerPort => $postfwd_settings{$type}{port}, ++ Proto => 'tcp', ++ Timeout => $postfwd_settings{timeout}{$type}, ++ Type => SOCK_STREAM ) ) { ++ $socket->print("$send\r\n"); ++ $send = $socket->getline(); ++ chomp($send); ++ $socket->close(); ++ } else { ++ warn("can not open socket to $postfwd_settings{$type}{host}:$postfwd_settings{$type}{port}: '$!' '$@'\n"); ++ undef $send; ++ }; ++ return $send; ++}; ++ ++# check: UNIX ++sub check_unix { ++ my ($type,$send) = @_; ++ if ( my $socket = new IO::Socket::UNIX ( ++ Peer => $postfwd_settings{$type}{port}, ++ Timeout => $postfwd_settings{timeout}{$type}, ++ Type => SOCK_STREAM ) ) { ++ $socket->print("$send\r\n"); ++ $send = $socket->getline(); ++ chomp($send); ++ $socket->close(); ++ } else { ++ warn("can not open socket to $postfwd_settings{$type}{host}:$postfwd_settings{$type}{port}: '$!' '$@'\n"); ++ undef $send; ++ }; ++ return $send; ++}; ++ ++1; # EOF postfwd2::basic ++ ++ ++############################ ++package postfwd2::cache; ++ ++## MODULES ++use warnings; ++use strict; ++use base 'Net::Server::Multiplex'; ++import postfwd2::basic qw(:DEFAULT &wantsdebug &hash_to_list); ++use vars qw( %Cache %Cleanup %Count %Interval %Top $Reload_Conf $Summary $StartTime ); ++ ++ ++## SUBS ++ ++# prepare stats ++sub list_stats { ++ my @output = (); my $line = ''; my $now = time(); ++ my $uptime = $now - $StartTime; ++ return @output unless $uptime and (%Count or %Cache); ++ push ( @output, sprintf ( ++ "[STATS] %s::cache %s: %d queries since %d days, %02d:%02d:%02d hours", ++ $postfwd_settings{name}, ++ $postfwd_settings{version}, ++ $Count{cache_queries}, ++ ($uptime / 60 / 60 / 24), ++ (($uptime / 60 / 60) % 24), ++ (($uptime / 60) % 60), ++ ($uptime % 60) ++ ) ); ++ my $lastreq = (($now - $Summary) > 0) ? (($Interval{request_set} || 0) + ($Interval{request_get} || 0)) / ($now - $Summary) * 60 : 0; ++ $Top{request} = $lastreq if ($lastreq > ($Top{request} || 0)); $Top{request} ||= 0; ++ my $lastdns = (($now - $Summary) > 0) ? (($Interval{dns_set} || 0) + ($Interval{dns_get} || 0)) / ($now - $Summary) * 60 : 0; ++ $Top{dns} = $lastdns if ($lastdns > ($Top{dns} || 0)); $Top{dns} ||= 0; ++ push ( @output, sprintf ( ++ "[STATS] Requests: %.1f/min last, %.1f/min overall, %.1f/min top", ++ $lastreq, ++ (($Count{request_set} || 0) + ($Count{request_get} || 0)) / $uptime * 60, ++ $Top{request} ++ ) ); ++ push ( @output, sprintf ( ++ "[STATS] Dnsstats: %.1f/min last, %.1f/min overall, %.1f/min top", ++ $lastdns, ++ (($Count{dns_set} || 0) + ($Count{dns_get} || 0)) / $uptime * 60, ++ $Top{dns} ++ ) ) unless ($postfwd_settings{dns}{disable} or $postfwd_settings{dns}{noparent}); ++ push ( @output, sprintf ( ++ "[STATS] Hitrates: %.1f%% requests, %.1f%% dns", ++ ($Count{request_get}) ? ($Count{request_hits} || 0) / $Count{request_get} * 100 : 0, ++ ($Count{dns_get}) ? ($Count{dns_hits} || 0) / $Count{dns_get} * 100 : 0 ++ ) ); ++ push ( @output, "[STATS] Contents: ". ++ join ', ', map { $_ = "$_=".(scalar keys %{$Cache{$_}}) } (reverse sort keys %Cache) ++ ); ++ if (wantsdebug (qw[ all devel parent_cache ])) { ++ push ( @output, "[STATS] Counters: ". ++ join ', ', map { $_ = "$_=".$Count{$_} } (reverse sort keys %Count) ); ++ push ( @output, "[STATS] Interval: ". ++ join ', ', map { $_ = "$_=".$Interval{$_} } (reverse sort keys %Interval) ); ++ }; ++ map { $Interval{$_} = 0 } (keys %Interval); ++ $Summary = $now; ++ return @output; ++}; ++ ++# return cache contents ++sub dump_cache { ++ my @result = (); ++ foreach (keys %Cache) { ++ push @result, hash_to_list ('%'.$_."_cache", %{$Cache{$_}}) ++ if %{$Cache{$_}} and wantsdebug ("devel", "parent_cache", "parent_".$_."_cache"); ++ }; return @result; ++}; ++ ++# get a whole cache item ++sub get_cache { ++ my ($self,$type,$item) = @_; ++ my @answer = (); ++ return '' unless ( defined $Cache{$type}{$item}{'until'} and (time() <= $Cache{$type}{$item}{'until'}[0])); ++ $Count{$type."_hits"}++; ++ map { push @answer, "$_=".(join $postfwd_settings{seplst}, @{$Cache{$type}{$item}{$_}}) } (keys %{$Cache{$type}{$item}}); ++ return (join $postfwd_settings{sepreq}, @answer); ++}; ++ ++# set item to cache ++sub set_cache { ++ my ($self,$type,$item,$vals) = @_; ++ my @answer = (); ++ undef $Cache{$type}{$item}; ++ foreach my $arg (split ($postfwd_settings{sepreq}, $vals)) { ++ map { push @{$Cache{$type}{$item}{$1}}, $_; ++ push @answer, "$type->$item->$1=$_"; ++ @{$Cache{$type}{$item}{$1}} = uniq(@{$Cache{$type}{$item}{$1}}); ++ } (split $postfwd_settings{seplst}, $2) if ($arg =~ m/$postfwd_patterns{keyval}/); ++ }; ++ @answer = '' unless @answer; ++ return (join '; ', @answer); ++}; ++ ++# clean up cache ++sub cleanup_cache { ++ my($type,$now) = @_; ++ my $start = time(); ++ return unless defined $Cache{$type} and my $count = scalar keys %{$Cache{$type}}; ++ foreach my $checkitem (keys %{$Cache{$type}}) { ++ # remove inclomplete objects ++ if ( !defined($Cache{$type}{$checkitem}{'until'}) or !defined($Cache{$type}{$checkitem}{ttl}) ) { ++ log_info ("[CLEANUP] deleting incomplete $type cache item $checkitem after " ++ ." timeout: ".((defined $Cache{$type}{$checkitem}{ttl}) ? $Cache{$type}{$checkitem}{ttl}[0] : '')."s") ++ if wantsdebug (qw[ all ]); ++ delete $Cache{$type}{$checkitem}; ++ # remove timed out objects ++ } elsif ( $now > $Cache{$type}{$checkitem}{'until'}[0] ) { ++ log_info ("[CLEANUP] removing $type cache for $checkitem after " ++ ." timeout: ".$Cache{$type}{$checkitem}{ttl}[0]."s)") ++ if wantsdebug (qw[ all ]); ++ delete $Cache{$type}{$checkitem}; ++ }; ++ }; ++ my $end = time(); ++ log_info ("[CLEANUP] needed ".($end - $start) ++ ." seconds for request cleanup of " ++ .($count - scalar keys %{$Cache{$type}})." out of ".$count ++ ." cached items after ".($now - $Cleanup{$type}) ++ ." seconds (min ".$postfwd_settings{request}{cleanup}."s)") if ( wantsdebug (qw[ all verbose ]) or (($end - $start) > 0) ); ++ $Cleanup{$type} = $start; ++}; ++ ++ ++ ++## Net::Server::Multiplex methods ++ ++# ignore syslog failures ++sub handle_syslog_error {}; ++ ++# set $Reload_Conf marker on HUP signal ++sub sig_hup { ++ log_note ("catched HUP signal - clearing request cache on next request"); ++ $Reload_Conf = 1; ++}; ++ ++# cache start ++sub pre_loop_hook() { ++ my $self = shift; ++ # change cache name ++ $0 = $self->{server}->{commandline} = " ".$postfwd_settings{name}.'::cache'; ++ $self->{server}->{syslog_ident} = $postfwd_settings{name}."/cache"; ++ init_log ($self->{server}->{syslog_ident}); ++ $StartTime = $Summary = $Cleanup{request} = $Cleanup{rate} = $Cleanup{dns} = time(); ++ log_info ("ready for input"); ++}; ++ ++# cache process request ++sub mux_input { ++ my ($self, $mux, $client, $mydata) = @_; ++ my $action = ''; ++ my $now = time(); ++ while ( $$mydata =~ s/^([^\r\n]*)\r?\n// ) { ++ # check request line ++ next unless defined $1; ++ my $request = $1; ++ log_info ("request: '$request'") if wantsdebug (qw[ all ]); ++ if ($Reload_Conf) { ++ undef $Reload_Conf; ++ delete $Cache{request}; ++ log_info ("request cache cleared") if wantsdebug (qw[ all verbose ]); ++ }; ++ if ($request eq $postfwd_patterns{ping}) { ++ $action = $postfwd_patterns{pong}; ++ } elsif ($request =~ m/$postfwd_patterns{getcacheitem}/) { ++ my ($type, $item) = ($1, $2); ++ log_info ("[GETCACHEITEM] request: '$request'") if wantsdebug (qw[ all cache getcache ]); ++ cleanup_cache ($type,$now) if (($now - $Cleanup{$type}) > ($postfwd_settings{$type}{cleanup} || 300)); ++ $Count{cache_queries}++; $Interval{cache_queries}++; ++ $Count{$type."_get"}++; $Interval{$type."_get"}++; ++ $action = $self->get_cache($type,$item); ++ log_info ("[GETCACHEITEM] answer: '$action'") if wantsdebug (qw[ all cache getcache ]); ++ } elsif ($request =~ m/$postfwd_patterns{setcacheitem}/) { ++ log_info ("[SETCACHEITEM] request: '$request'") if wantsdebug (qw[ all cache setcache ]); ++ $Count{cache_queries}++; $Interval{cache_queries}++; ++ $Count{$1."_set"}++; $Interval{$1."_set"}++; ++ $action = $self->set_cache($1,$2,$3); ++ log_info ("[SETCACHEITEM] answer: '$action'") if wantsdebug (qw[ all cache setcache ]); ++ } elsif ($request =~ m/$postfwd_patterns{dumpstats}/) { ++ $action = join $postfwd_settings{sepreq}.$postfwd_settings{seplst}, list_stats(); ++ } elsif ($request =~ m/$postfwd_patterns{dumpcache}/) { ++ $action = join $postfwd_settings{sepreq}.$postfwd_settings{seplst}, dump_cache(); ++ } else { ++ log_note ("warning: ignoring unknown command '".substr($request,0,512)."'"); ++ }; ++ print $client "$action\n"; ++ log_info ("answer: '$action'") if wantsdebug (qw[ all ]); ++ }; ++}; ++ ++1; # EOF postfwd2::cache ++ ++ ++############################ ++package postfwd2::server; ++ ++use warnings; ++use strict; ++use IO::Socket qw(SOCK_STREAM); ++use Net::DNS; ++use base 'Net::Server::PreFork'; ++import postfwd2::basic qw(:DEFAULT %postfwd_commands &check_inet &check_unix &wantsdebug &hash_to_str &str_to_hash &hash_to_list); ++# export these functions for '-C' switch ++use Exporter qw(import); ++our @EXPORT_OK = qw( ++ &read_config &show_config &process_input ++); ++ ++ ++# these items have to be compared as... ++# scoring ++my $COMP_SCORES = "score"; ++my $COMP_NS_NAME = "sender_ns_names"; ++my $COMP_NS_ADDR = "sender_ns_addrs"; ++my $COMP_MX_NAME = "sender_mx_names"; ++my $COMP_MX_ADDR = "sender_mx_addrs"; ++my $COMP_HELO_ADDR = "helo_address"; ++# networks in CIDR notation (a.b.c.d/nn) ++my $COMP_NETWORK_CIDRS = "(client_address|sender_(ns|mx)_addrs|helo_address)"; ++# RBL checks ++my $COMP_DNSBL_TEXT = "dnsbltext"; ++my $COMP_RBL_CNT = "rblcount"; ++my $COMP_RHSBL_CNT = "rhsblcount"; ++my $COMP_RBL_KEY = "rbl"; ++my $COMP_RHSBL_KEY = "rhsbl"; ++my $COMP_RHSBL_KEY_CLIENT = "rhsbl_client"; ++my $COMP_RHSBL_KEY_SENDER = "rhsbl_sender"; ++my $COMP_RHSBL_KEY_RCLIENT = "rhsbl_reverse_client"; ++my $COMP_RHSBL_KEY_HELO = "rhsbl_helo"; ++my %DNSBLITEMS = ( ++ rbl => { ++ cnt => "rblcount", ++ }, ++ rhsbl => { ++ cnt => "rhsblcount", ++ }, ++ rhsbl_client => { ++ cnt => "rhsblcount", ++ }, ++ rhsbl_sender => { ++ cnt => "rhsblcount", ++ }, ++ rhsbl_reverse_client => { ++ cnt => "rhsblcount", ++ }, ++ rhsbl_helo => { ++ cnt => "rhsblcount", ++ }, ++); ++# dns key value matching ++my %DNS_REPNAMES = ( ++ "NS" => "nsdname", ++ "MX" => "exchange", ++ "A" => "address", ++ "TXT" => "char_str_list", ++ "CNAME" => "cname", ++); ++ ++# file items ++our($COMP_CONF_FILE) = 'cfile|file'; ++our($COMP_CONF_TABLE) = 'ctable|table'; ++our($COMP_LIVE_FILE) = 'lfile'; ++our($COMP_LIVE_TABLE) = 'ltable'; ++our($COMP_TABLES) = qr/^($COMP_CONF_TABLE|$COMP_LIVE_TABLE)$/i; ++our($COMP_CONF_FILE_TABLE) = qr/^($COMP_CONF_FILE|$COMP_CONF_TABLE):(.+)$/i; ++our($COMP_LIVE_FILE_TABLE) = qr/^($COMP_LIVE_FILE|$COMP_LIVE_TABLE):(.+)$/i; ++# date checks ++my $COMP_DATE = "date"; ++my $COMP_TIME = "time"; ++my $COMP_DAYS = "days"; ++my $COMP_MONTHS = "months"; ++# always true ++my $COMP_ACTION = "action"; ++my $COMP_ID = "id"; ++my $COMP_CACHE = "cache"; ++# rule hits ++my $COMP_HITS = "request_hits"; ++# item match counter ++my $COMP_MATCHES = "matches"; ++# separator ++my $COMP_SEPARATOR = "[=\~\<\>]=|[=\!][=\~\<\>]|="; ++# macros ++my $COMP_ACL = "[\&][\&]"; ++# negation ++my $COMP_NEG = "[\!][\!]"; ++# variables ++my $COMP_VAR = "[\$][\$]"; ++# date calculations ++my $COMP_DATECALC = "($COMP_DATE|$COMP_TIME|$COMP_DAYS|$COMP_MONTHS)"; ++# these items allow whitespace-or-comma-separated values ++my $COMP_CSV = "($COMP_NETWORK_CIDRS|$COMP_RBL_KEY|$COMP_RHSBL_KEY|$COMP_RHSBL_KEY_CLIENT|$COMP_RHSBL_KEY_HELO|$COMP_RHSBL_KEY_SENDER|$COMP_RHSBL_KEY_RCLIENT|$COMP_DATECALC|$COMP_HELO_ADDR|$COMP_NS_ADDR|$COMP_MX_ADDR)"; ++# dont treat these as lists ++my $COMP_SINGLE = "($COMP_ID|$COMP_ACTION|$COMP_CACHE|$COMP_SCORES|$COMP_RBL_CNT|$COMP_RHSBL_CNT)"; ++ ++# date tools ++my %months = ( ++ "Jan" => 0, "jan" => 0, "JAN" => 0, ++ "Feb" => 1, "feb" => 1, "FEB" => 1, ++ "Mar" => 2, "mar" => 2, "MAR" => 2, ++ "Apr" => 3, "apr" => 3, "APR" => 3, ++ "May" => 4, "may" => 4, "MAY" => 4, ++ "Jun" => 5, "jun" => 5, "JUN" => 5, ++ "Jul" => 6, "jul" => 6, "JUL" => 6, ++ "Aug" => 7, "aug" => 7, "AUG" => 7, ++ "Sep" => 8, "sep" => 8, "SEP" => 8, ++ "Oct" => 9, "oct" => 9, "OCT" => 9, ++ "Nov" => 10, "nov" => 10, "NOV" => 10, ++ "Dec" => 11, "dec" => 11, "DEC" => 11, ++); ++my %weekdays = ( ++ "Sun" => 0, "sun" => 0, "SUN" => 0, ++ "Mon" => 1, "mon" => 1, "MON" => 1, ++ "Tue" => 2, "tue" => 2, "TUE" => 2, ++ "Wed" => 3, "wed" => 3, "WED" => 3, ++ "Thu" => 4, "thu" => 4, "THU" => 4, ++ "Fri" => 5, "fri" => 5, "FRI" => 5, ++ "Sat" => 6, "sat" => 6, "SAT" => 6, ++); ++ ++use vars qw( ++ @Rules @DNSBL_Text ++ %Rule_by_ID %Matches %ACLs %Timeouts %Hits %Count ++ %postfwd_items %postfwd_compare %postfwd_actions ++ %postfwd_items_plugin %postfwd_compare_plugin %postfwd_actions_plugin ++ %Request_Cache %Config_Cache %DNS_Cache %Rate_Cache ++ $Cleanup_Requests $Cleanup_RBLs $Cleanup_Rates $Cleanup_Timeouts ++ %Cache %Cleanup $StartTime $Summary ++); ++ ++ ++## SUBS ++ ++# cache query ++sub cache_query { return ( &{$postfwd_settings{cache}{check}}('cache',@_) || '' ) }; ++ ++# get ip and mask ++sub cidr_parse { ++ return undef unless defined $_[0]; ++ return undef unless $_[0] =~ m/^(\d+)\.(\d+)\.(\d+)\.(\d+)\/(\d+)$/; ++ return undef unless ($1 < 256 and $2 < 256 and $3 < 256 and $4 < 256 and $5 <= 32 and $5 >= 0); ++ my $net = ($1<<24)+($2<<16)+($3<<8)+$4; ++ my $mask = ~((1<<(32-$5))-1); ++ return ($net & $mask, $mask); ++}; ++ ++# compare address to network ++sub cidr_match { ++ my ($net, $mask, $addr) = @_; ++ return undef unless defined $net and defined $addr; ++ $addr = ($1<<24)+($2<<16)+($3<<8)+$4 if ($addr =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/); ++ return ($addr & $mask) == $net; ++}; ++ ++# sets an action for a score ++sub modify_score { ++ my($myscore,$myaction) = @_; ++ log_info ( ((defined $postfwd_settings{scores}{$myscore}) ? "redefined" : "setting new") ++ ." score $myscore with action=\"$myaction\"") if wantsdebug (qw[ all verbose ]); ++ $postfwd_settings{scores}{$myscore} = $myaction; ++}; ++ ++# returns content of !!() negation ++sub deneg_item { ++ my($val) = (defined $_[0]) ? $_[0] : ''; ++ return ( ($val =~ /^$COMP_NEG\s*\(?\s*(.+?)\s*\)?$/) ? $1 : '' ); ++}; ++ ++# resolves $$() variables ++sub devar_item { ++ my($cmp,$val,$myitem,%request) = @_; ++ my($pre,$post,$var,$myresult) = ''; ++ while ( ($val =~ /(.*)$COMP_VAR\s*(\w+)(.*)/g) or ($val =~ /(.*)$COMP_VAR\s*\((\w+)\)(.*)/g) ) { ++ ($pre,$var,$post) = ($1,$2,$3); ++ if ($var eq $COMP_DNSBL_TEXT) { ++ $myresult=$val=$pre.(join "; ", uniq(@DNSBL_Text)).$post; ++ } elsif (defined $request{$var}) { ++ $myresult=$val=$pre.$request{$var}.$post; ++ }; ++ log_info ("substitute : \"$myitem\" \"$cmp\" \"$val\"") if wantsdebug (qw[ all verbose ]); ++ }; ++ return $myresult; ++}; ++ ++# clean up RBL cache ++sub cleanup_dns_cache { ++ my($now) = $_[0]; return unless $now; ++ foreach my $checkitem (keys %DNS_Cache) { ++ # remove inclomplete objects (dns timeouts) ++ if ( !defined($DNS_Cache{$checkitem}{'until'}) or !defined($DNS_Cache{$checkitem}{ttl}) ) { ++ log_info ("[CLEANUP] deleting incomplete dns-cache item $checkitem after " ++ ." timeout: ".((defined $DNS_Cache{$checkitem}{ttl}) ? $DNS_Cache{$checkitem}{ttl} : '')."s") ++ if wantsdebug (qw[ all ]); ++ delete $DNS_Cache{$checkitem}; ++ # remove timed out objects ++ } elsif ( $now > $DNS_Cache{$checkitem}{'until'} ) { ++ log_info ("[CLEANUP] removing rbl-cache for $checkitem after timeout: " ++ .$DNS_Cache{$checkitem}{ttl}."s") if wantsdebug (qw[ all ]); ++ delete $DNS_Cache{$checkitem}; ++ }; ++ }; ++}; ++ ++# clean up request cache ++sub cleanup_request_cache { ++ my($now) = $_[0]; return unless $now; ++ RITEM: foreach my $checkitem (keys %Request_Cache) { ++ next RITEM unless defined $Request_Cache{$checkitem}{'until'}; ++ if ( $now > $Request_Cache{$checkitem}{'until'} ) { ++ log_info ("[CLEANUP] removing request-cache $checkitem after timeout: " ++ .$Request_Cache{$checkitem}{ttl}."s") if wantsdebug (qw[ all ]); ++ delete $Request_Cache{$checkitem}; ++ }; ++ }; ++}; ++ ++# clean up rate cache ++sub cleanup_rate_cache { ++ my($now) = $_[0]; return unless $now; ++ LITEM: foreach my $checkitem (keys %Rate_Cache) { ++ next LITEM unless defined $Rate_Cache{$checkitem}{'until'}; ++ if ( $now > $Rate_Cache{$checkitem}{'until'} ) { ++ log_info ("[CLEANUP] removing rate-cache for $checkitem after timeout: " ++ .$Rate_Cache{$checkitem}{ttl}."s") if wantsdebug (qw[ all ]); ++ delete $Rate_Cache{$checkitem}; ++ }; ++ }; ++}; ++ ++# preparses configuration line for ACL syntax ++sub acl_parser { ++ my($file,$num,$myline) = @_; ++ if ( $myline =~ /^\s*($COMP_ACL[\-\w]+)\s*{\s*(.*?)\s*;\s*}[\s;]*$/ ) { ++ $ACLs{$1} = $2; $myline = ""; ++ } else { ++ while ( $myline =~ /($COMP_ACL[\-\w]+)/) { ++ my($acl) = $1; ++ if ( $acl and defined $ACLs{$acl} ) { ++ $myline =~ s/\s*$acl\s*/$ACLs{$acl}/g; ++ } else { ++ #return "action=note(undefined macro '$acl')"; ++ log_warn ("file $file, ignoring line $num: undefined macro '$acl'"); ++ return ""; ++ }; ++ }; ++ }; ++ return $myline; ++}; ++ ++# prepares pcre item ++sub prepare_pcre { ++ my($item) = shift; undef my $neg; ++ # temporarily remove negation ++ $item = $neg if ($neg = deneg_item($item)); ++ # allow // regex ++ $item =~ s/^\/?(.*?)\/?$/$1/; ++ # tested slow ++ #$item = qr/$item/i; ++ # re-enable negation ++ $item = "!!($item)" if $neg; ++ return $item; ++}; ++ ++# prepares file item ++sub prepare_file { ++ my($forced_reload,$type,$cmp,$file) = @_; my(@result) = (); undef my $fh; ++ my($is_table) = ($type =~ /^$COMP_TABLES$/); ++ unless (-e $file) { ++ log_warn ("error: $type:$file not found - will be ignored"); ++ return @result; ++ }; ++ if ( not($forced_reload) and (defined $Config_Cache{$file}{lastread}) and ($Config_Cache{$file}{lastread} > (stat $file)[9]) ) { ++ log_info ("$type:$file unchanged - using cached content (mtime: " ++ .(stat $file)[9].", cache: $Config_Cache{$file}{lastread})") ++ if wantsdebug (qw[ all config ]); ++ return @{$Config_Cache{$file}{content}}; ++ }; ++ unless (open ($fh, "<$file")) { ++ log_warn ("error: could not open $type:$file - $! - will be ignored"); ++ return @result; ++ }; ++ log_info ("reading $type:$file") if wantsdebug (qw[ all config ]); ++ while (<$fh>) { ++ chomp; ++ s/#.*//g; ++ next if /^\s*$/; ++ s/\s+[^\s]+$// if $is_table; ++ s/^\s+//; s/\s+$//; ++ push @result, prepare_item($forced_reload, $cmp, $_); ++ }; close ($fh); ++ # update Config_Cache ++ $Config_Cache{$file}{lastread} = time; ++ @{$Config_Cache{$file}{content}} = @result; ++ log_info ("read ".($#result + 1)." items from $type:$file") if wantsdebug (qw[ all config ]); ++ return @result; ++}; ++ ++# prepares ruleset item ++sub prepare_item { ++ my($forced_reload,$cmp,$item) = @_; my(@result) = (); undef my $type; ++ if ($item =~ /$COMP_CONF_FILE_TABLE/) { ++ return prepare_file ($forced_reload, $1, $cmp, $2); ++ } elsif ($cmp eq '=~' or $cmp eq '!~') { ++ return $cmp.";".prepare_pcre($item); ++ } else { ++ return $cmp.";".$item; ++ }; ++}; ++ ++# parses configuration line ++sub parse_config_line { ++ my($forced_reload, $myfile, $mynum, $myindex, $myline) = @_; ++ my(%myrule) = (); ++ my($mykey, $myvalue, $mycomp); ++ eval { ++ local $SIG{'__DIE__'}; ++ local $SIG{'ALRM'} = sub { $myline =~ s/[ \t][ \t]*/ /g; log_warn ("timeout after ".$postfwd_settings{timeout}{config}."s at parsing Rule $myindex ($myfile line $mynum): \"$myline\""); %myrule = (); die }; ++ my $prevalert = alarm($postfwd_settings{timeout}{config}) if $postfwd_settings{timeout}{config}; ++ if ( $myline = acl_parser ($myfile, $mynum, $myline) ) { ++ unless ( $myline =~ /^\s*[^=\s]+\s*$COMP_SEPARATOR\s*([^;\s]+\s*)+(;\s*[^=\s]+\s*$COMP_SEPARATOR\s*([^;\s]+\s*)+)*[;\s]*$/ ) { ++ log_warn ("ignoring invalid $myfile line ".$mynum.": \"".$myline."\""); ++ } else { ++ # separate items ++ foreach (split ";", $myline) { ++ # remove whitespaces around ++ s/^\s*(.*?)\s*($COMP_SEPARATOR)\s*(.*?)\s*$/$1$2$3/; ++ ( ($mycomp = $2) =~ /^([\<\>\~])=$/ ) and $mycomp = "=$1"; ++ ($mykey, $myvalue) = split /$COMP_SEPARATOR/, $_, 2; ++ if ($mykey =~ /^$COMP_SINGLE$/) { ++ log_note ( "notice: Rule $myindex ($myfile line $mynum):" ++ ." overriding $mykey=\"".$myrule{$mykey}."\"" ++ ." with $mykey=\"$myvalue\"" ++ ) if (defined $myrule{$mykey}); ++ $myrule{$mykey} = $myvalue; ++ } elsif ($mykey =~ /^$COMP_CSV$/) { ++ map { push @{$myrule{$mykey}}, prepare_item ($forced_reload, $mycomp, $_) } ( split /\s*,\s*/, $myvalue ); ++ } else { ++ push @{$myrule{$mykey}}, prepare_item ($forced_reload, $mycomp, $myvalue); ++ }; ++ }; ++ unless (exists($myrule{$COMP_ACTION})) { ++ log_warn ("Rule ".$myindex." ($myfile line ".$mynum."): contains no action and will be ignored"); ++ return (%myrule = ()); ++ }; ++ unless (exists($myrule{$COMP_ID})) { ++ $myrule{$COMP_ID} = "R-".$myindex; ++ log_note ("notice: Rule $myindex ($myfile line $mynum): contains no rule identifier - will use \"$myrule{id}\"") if wantsdebug (qw[ all config verbose ]); ++ }; ++ log_info ("loaded: Rule $myindex ($myfile line $mynum): id->\"$myrule{id}\" action->\"$myrule{action}\"") if wantsdebug (qw[ all config verbose ]); ++ }; ++ }; ++ alarm($prevalert) if $postfwd_settings{timeout}{config}; ++ }; ++ return %myrule; ++}; ++ ++# parses configuration file ++sub read_config_file { ++ my($forced_reload, $myindex, $myfile) = @_; ++ my(%myrule, @myruleset) = (); ++ my($mybuffer) = ""; undef my $fh; ++ ++ unless (-e $myfile) { ++ log_warn ("error: file ".$myfile." not found - file will be ignored"); ++ } else { ++ unless (open ($fh, "<$myfile")) { ++ log_warn ("error: could not open ".$myfile." - $! - file will be ignored"); ++ } else { ++ log_info ("reading file $myfile") if wantsdebug (qw[ all config verbose ]); ++ while (<$fh>) { ++ chomp; ++ s/(\"|#.*)//g; ++ next if /^\s*$/; ++ if (/(.*)\\\s*$/) { $mybuffer = $mybuffer.$1; next; }; ++ %myrule = parse_config_line ($forced_reload, $myfile, $., ($#myruleset+$myindex+1), $mybuffer.$_); ++ push ( @myruleset, { %myrule } ) if (%myrule); ++ $mybuffer = ""; ++ }; ++ close ($fh); ++ log_info ("loaded: Rules $myindex - ".($myindex + $#myruleset)." from file \"$myfile\"") if wantsdebug (qw[ all config verbose ]); ++ }; ++ }; ++ return @myruleset; ++}; ++ ++# reads all configuration items ++sub read_config { ++ my($forced_reload) = shift; ++ my(%myrule, @myruleset) = (); ++ my($mytype,$myitem,$config); ++ ++ # init, cleanup cache and config vars ++ @Rules = (); %Rule_by_ID = %Request_Cache = (); ++ ++ # parse configurations ++ for $config (@{$postfwd_settings{Configs}}) { ++ ($mytype,$myitem) = split $postfwd_settings{sepreq}, $config; ++ if ($mytype eq "r" or $mytype eq "rule") { ++ %myrule = parse_config_line ($forced_reload, 'RULE', 0, ($#Rules + 1), $myitem); ++ push ( @Rules, { %myrule } ) if (%myrule); ++ } elsif ($mytype eq "f" or $mytype eq "file") { ++ if ( not($forced_reload) and defined $Config_Cache{$myitem}{lastread} and ($Config_Cache{$myitem}{lastread} > (stat $myitem)[9]) ) { ++ log_info ("file \"$myitem\" unchanged - using cached ruleset (mtime: ".(stat $myitem)[9].", ++ cache: $Config_Cache{$myitem}{lastread})" ++ ) if wantsdebug (qw[ all config verbose ]); ++ push ( @Rules, @{$Config_Cache{$myitem}{ruleset}} ) if $Config_Cache{$myitem}{ruleset}; ++ } else { ++ @myruleset = read_config_file ($forced_reload, ($#Rules + 1), $myitem); ++ if (@myruleset) { ++ @Rules = ( @Rules, @myruleset ) if @myruleset; ++ $Config_Cache{$myitem}{lastread} = time(); ++ @{$Config_Cache{$myitem}{ruleset}} = @myruleset; ++ }; ++ }; ++ }; ++ }; ++ if ($#Rules < 0) { ++ log_warn("critical: no rules found - i feel useless (have you set -f or -r?)"); ++ } else { ++ # update Rule by ID hash ++ map { $Rule_by_ID{$Rules[$_]{$COMP_ID}} = $_ } (0 .. $#Rules); ++ }; ++}; ++ ++# displays configuration ++sub show_config { ++ if (wantsdebug (qw[ all verbose ])) { ++ print STDOUT "=" x 75, "\n"; ++ printf STDOUT "Rule count: %s\n", ($#Rules + 1); ++ print STDOUT "=" x 75, "\n"; ++ }; ++ for my $index (0 .. $#Rules) { ++ next unless exists $Rules[$index]; ++ printf STDOUT "Rule %3d: id->\"%s\"; action->\"%s\"", $index, $Rules[$index]{$COMP_ID}, $Rules[$index]{$COMP_ACTION}; ++ my $line = (wantsdebug (qw[ all verbose ])) ? "\n\t " : ""; ++ for my $mykey ( reverse sort keys %{$Rules[$index]} ) { ++ unless (($mykey eq $COMP_ACTION) or ($mykey eq $COMP_ID)) { ++ $line .= "; " unless wantsdebug (qw[ all verbose ]); ++ $line .= ($mykey =~ /^$COMP_SINGLE$/) ++ ? $mykey."->\"".$Rules[$index]{$mykey}."\"" ++ : $mykey."->\"".(join ', ', @{$Rules[$index]{$mykey}})."\""; ++ $line .= " ; " if wantsdebug (qw[ all verbose ]); ++ }; ++ }; ++ $line =~ s/\s*\;\s*$// if wantsdebug (qw[ all verbose ]); ++ printf STDOUT "%s\n", $line; ++ print STDOUT "-" x 75, "\n" if wantsdebug (qw[ all verbose ]); ++ }; ++}; ++ ++ ++## sub DNS ++ ++# checks for rbl timeouts ++sub rbl_timeout { ++ my($myrbl) = shift; ++ return ( ($postfwd_settings{dns}{max_timeout} > 0) and (defined $Timeouts{$myrbl}) and ($Timeouts{$myrbl} > $postfwd_settings{dns}{max_timeout}) ); ++}; ++ ++# reads DNS answers ++sub rbl_read_dns { ++ my($myresult) = shift; ++ my($now) = time(); ++ my($que,$ttl,$res,$typ) = undef; ++ my(@addrs,@texts) = (); ++ ++ if ( defined $myresult ) { ++ # read question, for dns cache id ++ foreach ($myresult->question) { ++ $typ = ($_->qtype || ''); $que = ($_->qname || ''); ++ map { &{$postfwd_settings{syslog}{logger}} ('info', "[GETDNS00] type=$typ, query=$que, $_") } (hash_to_list ('%packet', %{$myresult})) ++ if wantsdebug (qw[ all dns getdns getdnspacket ]); ++ next unless ($typ and $que); ++ log_info ("[GETDNS01] type=$typ, query=$que") if wantsdebug (qw[ all dns getdns ]); ++ unless ( (defined $DNS_Cache{$que}) ++ and (($typ eq 'A') or ($typ eq 'TXT')) ) { ++ log_note ("[DNSBL] ignoring unknown query '$que', type '$typ'"); ++ next; ++ }; ++ ++ # parse answers ++ foreach ($myresult->answer) { ++ log_info ("[GETDNS02] type=$typ, query=$que, restype='".$_->type."'") if wantsdebug (qw[ all dns getdns ]); ++ if ($_->type eq 'A') { ++ push @addrs, $_->address if $_->address; ++ $ttl = $_->ttl; ++ log_info ("[GETDNSA1] type=$typ, query=$que, ttl=$ttl, answer='".($_->address || '')."'") if wantsdebug (qw[ all dns getdns ]); ++ } elsif ($_->type eq 'TXT') { ++ $res = (join(" ", $_->char_str_list()) || ''); ++ # escape commas for set() action ++ $res =~ s/,/ /g; ++ push @texts, $res; ++ $ttl = $_->ttl; ++ log_info ("[GETDNST1] type=$typ, query=$que, ttl=$ttl, answer='$res'") if wantsdebug (qw[ all dns getdns ]); ++ } elsif (wantsdebug (qw[ all dns getdns devel ])) { ++ log_info ("[GETDNS??] received answer type=".$typ." for query $que"); ++ }; ++ }; ++ ++ # save result in cache ++ if ($typ eq 'A') { ++ $ttl = ( $DNS_Cache{$que}{ttl} > ($ttl||=0) ) ? $DNS_Cache{$que}{ttl} : $ttl; ++ @{$DNS_Cache{$que}{A}} = @addrs; ++ $DNS_Cache{$que}{ttl} = $ttl; ++ $DNS_Cache{$que}{delay} = ($now - $DNS_Cache{$que}{delay}); ++ $DNS_Cache{$que}{'log'} = 1; ++ $DNS_Cache{$que}{'until'} = $now + $DNS_Cache{$que}{ttl}; ++ log_info ("[GETDNSA2] type=$typ, query=$que, cache='".(hash_to_str(%{$DNS_Cache{$que}}))."'") if wantsdebug (qw[ all dns getdns ]); ++ #} elsif ($typ eq 'TXT') { ++ } else { ++ $res = (join(" ", @texts) || ''); ++ $ttl = ( $DNS_Cache{$que}{ttl} > ($ttl||=0) ) ? $DNS_Cache{$que}{ttl} : $ttl; ++ $DNS_Cache{$que}{TXT} = $res; ++ $DNS_Cache{$que}{ttl} = $ttl unless $DNS_Cache{$que}{ttl}; ++ log_info ("[GETDNST2] type=$typ, query=$que, cache='".(hash_to_str(%{$DNS_Cache{$que}}))."'") if wantsdebug (qw[ all dns getdns ]); ++ }; ++ }; ++ return $que if (@addrs || $res); ++ } else { ++ log_note ("[DNSBL] dns timeout"); ++ }; ++}; ++ ++# fires DNS queries ++sub rbl_prepare_lookups { ++ my($mytype, $myval, @myrbls) = @_; ++ my($myresult) = undef; ++ my($cmp,$rblitem,$myquery); ++ my(@lookups) = (); ++ ++ # skip these ++ return @lookups if ($myval eq '') or ($myval eq "unknown"); ++ ++ # removes duplicate lookups, but keeps the specified order ++ @myrbls = uniq(@myrbls); ++ ++ RBLQUERY: foreach (@myrbls) { ++ ++ # separate rbl-name and answer ++ ($cmp,$rblitem) = split ";", $_; ++ next RBLQUERY unless $rblitem; ++ my($myrbl, $myrblans, $myrbltime) = split /\//, $rblitem; ++ next RBLQUERY unless $myrbl; ++ next RBLQUERY if rbl_timeout($myrbl); ++ $myrblans = $postfwd_settings{dns}{mask} unless $myrblans; ++ $myrbltime = $postfwd_settings{dns}{ttl} unless $myrbltime; ++ ++ # create query string ++ $myquery = $myval.".".$myrbl; ++ my $mypat = qr/$myrblans/; ++ ++ # query our cache ++ if ( exists($DNS_Cache{$myquery}) and exists($DNS_Cache{$myquery}{A}) ) { ++ ANSWER1: foreach (@{$DNS_Cache{$myquery}{A}}) { last ANSWER1 if $myresult = ( $_ =~ /$mypat/ ) }; ++ log_info ("[DNSBL] cached $mytype: $myrbl $myval ($myquery) - answer: \'".(join ", ", @{$DNS_Cache{$myquery}{A}})."\'") ++ if ( wantsdebug (qw[ all ]) or ($myresult and wantsdebug (qw[ verbose ])) ); ++ ++ # query parent cache ++ } elsif ( not($postfwd_settings{dns}{noparent}) ++ and not((my $pans = cache_query ("CMD=".$postfwd_commands{getcacheitem}.";TYPE=dns;ITEM=$myquery")) eq '') ) { ++ %{$DNS_Cache{$myquery}} = str_to_hash($pans); delete $DNS_Cache{$myquery}{'log'} if $DNS_Cache{$myquery}{'log'}; ++ if ($DNS_Cache{$myquery}{A}) { ++ ref $DNS_Cache{$myquery}{A} eq 'ARRAY' or $DNS_Cache{$myquery}{A} = [ $DNS_Cache{$myquery}{A} ]; ++ ANSWER2: foreach (@{$DNS_Cache{$myquery}{A}}) { last ANSWER2 if $myresult = ( $_ =~ /$mypat/ ) }; ++ log_info ("[DNSBL] parent cached $mytype: $myrbl $myval ($myquery) - answer: \'".(join ", ", @{$DNS_Cache{$myquery}{A}})."\'") ++ if ( wantsdebug (qw[ all ]) or ($myresult and wantsdebug (qw[ verbose ])) ); ++ }; ++ ++ # not found -> prepare dns query ++ } else { ++ $DNS_Cache{$myquery} = { ++ type => $mytype, ++ name => $myrbl, ++ value => $myval, ++ ttl => $myrbltime, ++ delay => time(), ++ }; ++ log_info("[DNSBL] query $mytype: $myrbl $myval ($myquery)") if wantsdebug (qw[ all ]); ++ push @lookups, $myquery; ++ }; ++ }; ++ # return necessary lookups ++ return @lookups; ++}; ++ ++# checks RBL items ++sub rbl_check { ++ my($mytype,$myrbl,$myval) = @_; ++ my($myanswer,$myrblans,$myrbltime,$myresult,$mystart,$myend); ++ my($m1,$m2,$myrbltype,$m4,$myrbltxt,$myquery); ++ my($now) = time(); ++ ++ # skip these ++ return $myresult if ($myval eq '') or ($myval eq "unknown"); ++ ++ # separate rbl-name and answer ++ ($myrbl, $myrblans, $myrbltime) = split '/', $myrbl; ++ $myrblans = $postfwd_settings{dns}{mask} unless $myrblans; ++ $myrbltime = $postfwd_settings{dns}{ttl} unless $myrbltime; ++ ++ # create query string ++ $myquery = $myval.".".$myrbl; ++ ++ # query our cache ++ return $myresult unless ( $myresult = (defined $DNS_Cache{$myquery} and not(defined $DNS_Cache{$myquery}{'timed'})) ); ++ if (not($postfwd_settings{dns}{noparent}) and defined $DNS_Cache{$myquery}{'log'}) { ++ my $pdns = "CMD=".$postfwd_commands{setcacheitem}.";TYPE=dns;ITEM=$myquery".hash_to_str(%{$DNS_Cache{$myquery}}); ++ cache_query ($pdns); ++ }; ++ if ( $myresult = ($#{$DNS_Cache{$myquery}{A}} >= 0) ) { ++ my $mypat = qr/$myrblans/; ++ ANSWER: foreach (@{$DNS_Cache{$myquery}{A}}) { ++ last ANSWER if ( $myresult = ( ($_) and ($_ =~ m/$mypat/)) ); ++ }; ++ push @DNSBL_Text, $DNS_Cache{$myquery}{type}.':'.$DNS_Cache{$myquery}{name}.':<'.($DNS_Cache{$myquery}{TXT} || '').'>' ++ if $myresult and defined $DNS_Cache{$myquery}{type} and defined $DNS_Cache{$myquery}{name}; ++ if ( wantsdebug (qw[ all verbose ]) or $postfwd_settings{dns}{anylog} ++ or ($myresult and not($postfwd_settings{dns}{nolog}) and defined $DNS_Cache{$myquery}{'log'}) ) { ++ log_info ("[DNSBL] ".( ($mytype eq $COMP_RBL_KEY) ? join('.', reverse(split(/\./,$myval))) : $myval )." listed on " ++ .lc(($DNS_Cache{$myquery}{type} || $mytype)).":$myrbl (answer: ".(join ", ", @{$DNS_Cache{$myquery}{A}}) ++ .", time: ".$DNS_Cache{$myquery}{delay}."s, ttl: ".$DNS_Cache{$myquery}{ttl}."s, '".($DNS_Cache{$myquery}{TXT} || '')."')"); ++ delete $DNS_Cache{$myquery}{'log'} if defined $DNS_Cache{$myquery}{'log'}; ++ }; ++ }; ++ return $myresult; ++}; ++ ++# dns resolver wrapper ++sub dns_query { ++ my (@queries) = @_; undef my @result; ++ eval { ++ local $SIG{__DIE__} = sub { log_note ("[DNS] ERROR: \"$!\", DETAIL: \"@_\""); }; ++ @result = dns_query_net_dns(@queries); ++ }; ++ return @result; ++}; ++ ++# resolves dns queries using Net::DNS ++sub dns_query_net_dns { ++ my (@queries) = @_; undef my @result; undef my $pans; ++ my %ownsock = (); my @ownready = (); undef my $bgsock; ++ my $ownsel = IO::Select->new(); ++ my $dns = Net::DNS::Resolver->new( ++ tcp_timeout => $postfwd_settings{dns}{timeout}, ++ udp_timeout => $postfwd_settings{dns}{timeout}, ++ persistent_tcp => 0, persistent_udp => 0, ++ retrans => 0, retry => 1, dnsrch => 0, defnames => 0, ++ ); ++ my $now = time(); ++ # prepare queries ++ foreach (@queries) { ++ my ($item, $type) = split ','; $type ||= 'A'; ++ # query child cache ++ if ( (defined $DNS_Cache{$item}{$type}) and (defined $DNS_Cache{$item}{'until'}) and ($DNS_Cache{$item}{'until'} >= $now) ) { ++ $DNS_Cache{$item}{$type} = [ $DNS_Cache{$item}{$type} ] unless (ref $DNS_Cache{$item}{$type} eq 'ARRAY'); ++ log_info ("[DNS] dnsccache: item=$item, type=$type -> ".(join ',', @{$DNS_Cache{$item}{$type}})." (ttl: ".($DNS_Cache{$item}{ttl} || 0).")") ++ if ($postfwd_settings{dns}{anylog} or wantsdebug (qw[ all dns getdns ])); ++ push @result, @{$DNS_Cache{$item}{$type}}; ++ # query parent cache ++ } elsif ( not($postfwd_settings{dns}{noparent}) ++ and not(($pans = cache_query ("CMD=".$postfwd_commands{getcacheitem}.";TYPE=dns;ITEM=$item")) eq '') ++ and (%{$DNS_Cache{$item}} = str_to_hash($pans)) ++ and (defined $DNS_Cache{$item}{$type}) and (defined $DNS_Cache{$item}{'until'}) and ($DNS_Cache{$item}{'until'} >= $now) ) { ++ $DNS_Cache{$item}{$type} = [ $DNS_Cache{$item}{$type} ] unless (ref $DNS_Cache{$item}{$type} eq 'ARRAY'); ++ log_info ("[DNS] dnspcache: item=$item, type=$type -> ".(join ',', @{$DNS_Cache{$item}{$type}})." (ttl: ".($DNS_Cache{$item}{ttl} || 0).")") ++ if ($postfwd_settings{dns}{anylog} or wantsdebug (qw[ all dns getdns ])); ++ push @result, @{$DNS_Cache{$item}{$type}}; ++ # send queries ++ } else { ++ log_info ("[DNS] dnsquery: item=$item, type=$type") ++ if ($postfwd_settings{dns}{anylog} or wantsdebug (qw[ all dns getdns devel ])); ++ $DNS_Cache{$item}{delay} = $now; ++ $bgsock = $dns->bgsend ($item, $type); ++ $ownsel->add($bgsock); ++ $ownsock{$bgsock} = $item.','.$type; ++ }; ++ }; ++ # retrieve answers ++ while ((scalar keys %ownsock) and (@ownready = $ownsel->can_read($postfwd_settings{dns}{timeout}))) { ++ foreach my $sock (@ownready) { ++ if (defined $ownsock{$sock}) { ++ my $packet = $dns->bgread($sock); ++ my ($item, $type) = split ',', $ownsock{$sock}; ++ my $rname = $DNS_REPNAMES{$type}; ++ my @rrs = (grep { $_->type eq $type } $packet->answer); ++ $now = time(); my $ttl = 0; my @ans = (); ++ if (@rrs) { ++ # sort MX records by preference ++ @rrs = sort { $a->preference <=> $b->preference } @rrs if ($type eq 'MX'); ++ foreach my $rr (@rrs) { ++ $ttl = $rr->ttl if ($rr->ttl > $ttl); ++ log_info ("[DNS] dnsanswer: item=$item, type=$type -> $rname=".$rr->$rname." (ttl: $ttl)") ++ if ($postfwd_settings{dns}{anylog} or wantsdebug (qw[ all dns setdns ])); ++ push @ans, $rr->$rname; ++ }; ++ push @result, @ans; ++ }; ++ # add to dns cache ++ $ttl ||= $postfwd_settings{dns}{ttl}; ++ @{$DNS_Cache{$item}{$type}} = @ans; ++ $DNS_Cache{$item}{ttl} = $ttl; ++ $DNS_Cache{$item}{'until'} = $now + $ttl; ++ $DNS_Cache{$item}{delay} = ($DNS_Cache{delay}) ? $now - $DNS_Cache{delay} : 0; ++ cache_query ( "CMD=".$postfwd_commands{setcacheitem}.";TYPE=dns;ITEM=$item".hash_to_str(%{$DNS_Cache{$item}}) ) ++ unless ($postfwd_settings{dns}{noparent}); ++ $DNS_Cache{$item}{'log'} = 1; ++ log_info ("[DNS] dnsanswers: item=$item, type=$type -> $rname=".((@{$DNS_Cache{$item}{$type}}) ? join ',', @{$DNS_Cache{$item}{$type}} : '')." (delay: ".$DNS_Cache{$item}{delay}.", ttl: $ttl)") ++ if ($postfwd_settings{dns}{anylog} or wantsdebug (qw[ all verbose dns setdns devel ])); ++ delete $ownsock{$sock}; ++ } else { ++ $ownsel->remove($sock); ++ $sock = undef; ++ }; ++ }; ++ }; ++ # show timeouts ++ map { log_note ("dnsquery: timeout for $_ after ".$postfwd_settings{dns}{timeout}." seconds") } (values %ownsock); ++ return @result; ++}; ++ ++ ++## SUB plugins ++ ++# ++# these subroutines integrate additional attributes to ++# a request before the ruleset is evaluated ++# call: %result = postfwd_items{foo}(%request) ++# save: $result{$_} ++# ++%postfwd_items = ( ++ "__builtin__" => sub { ++ my(%request) = @_; my(%result) = (); ++ # postfwd version ++ $result{version} = $postfwd_settings{name}." ".$postfwd_settings{version}; ++ # sender info ++ $request{sender} =~ /(.*)@([^@]*)$/; ++ ( $result{sender_localpart}, $result{sender_domain} ) = ( $1, $2 ); ++ # recipient info ++ $request{recipient} =~ /(.*)@([^@]*)$/; ++ ( $result{recipient_localpart}, $result{recipient_domain} ) = ( $1, $2 ); ++ # reverted ip address (for lookups) ++ $result{reverse_address} = (join(".", reverse(split(/\./,$request{client_address})))); ++ return %result; ++ }, ++ "sender_dns" => sub { ++ my(%request) = @_; my(%result) = (); ++ map { $result{$_} = $request{sender_domain} } ($COMP_NS_NAME, $COMP_NS_ADDR, $COMP_MX_NAME, $COMP_MX_ADDR); ++ $result{$COMP_HELO_ADDR} = $request{helo_name}; ++ return %result; ++ }, ++); ++# returns additional request information ++# for all postfwd_items ++sub postfwd_items { ++ my(%request) = @_; ++ my(%result) = (); ++ foreach (sort keys %postfwd_items) { ++ log_info ("[PLUGIN] executing postfwd-item ".$_) ++ if wantsdebug (qw[ all ]); ++ %result = (%result, &{$postfwd_items{$_}}((%request,%result))) ++ if (defined $postfwd_items{$_}); ++ }; ++ map { $result{$_} = '' unless $result{$_}; log_info ("[PLUGIN] Added key: $_=$result{$_}") if wantsdebug (qw[ all ]) } (keys %result); ++ return %result; ++}; ++# ++# compare item subroutines ++# must take compare_item_foo ( $COMPARE_TYPE, $RULEITEM, $REQUESTITEM, %REQUEST, %REQUESTINFO ); ++# ++%postfwd_compare = ( ++ "cidr" => sub { ++ my($cmp,$val,$myitem,%request) = @_; ++ my($myresult) = ($val and $myitem); ++ log_info ("type cidr : \"$myitem\" \"$cmp\" \"$val\"") if wantsdebug (qw[ all ]); ++ if ($myresult) { ++ return $myresult if ( ($val eq '0.0.0.0/0') and ($myitem =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) ); ++ $val .= '/32' unless ($val =~ /\/\d{1,2}$/); ++ $myresult = cidr_match((cidr_parse($val)),$myitem); ++ }; ++ $myresult = not($myresult) if ($cmp eq '!='); ++ return $myresult; ++ }, ++ "numeric" => sub { ++ my($cmp,$val,$myitem,%request) = @_; ++ my($myresult) = undef; ++ log_info ("type numeric : \"$myitem\" \"$cmp\" \"$val\"") if wantsdebug (qw[ all ]); ++ $myitem ||= "0"; $val ||= "0"; ++ if ($cmp eq '==') { ++ $myresult = ($myitem == $val); ++ } elsif ($cmp eq '=<') { ++ $myresult = ($myitem <= $val); ++ } elsif ($cmp eq '=>') { ++ $myresult = ($myitem >= $val); ++ } elsif ($cmp eq '!=') { ++ $myresult = not($myitem == $val); ++ } elsif ($cmp eq '!<') { ++ $myresult = not($myitem <= $val); ++ } elsif ($cmp eq '!>') { ++ $myresult = not($myitem >= $val); ++ } else { ++ $myresult = ($myitem >= $val); ++ }; ++ return $myresult; ++ }, ++ $COMP_RBL_KEY => sub { ++ my($cmp,$val,$myitem,%request) = @_; ++ my($myresult) = not($postfwd_settings{dns}{disabled}); ++ log_info ("type rbl : \"$myitem\" \"$cmp\" \"$val\"") if wantsdebug (qw[ all ]); ++ $myresult = ( rbl_check ($COMP_RBL_KEY, $val, $myitem) ) if $myresult; ++ $myresult = not($myresult) if ($cmp eq '!='); ++ return $myresult; ++ }, ++ $COMP_RHSBL_KEY => sub { ++ my($cmp,$val,$myitem,%request) = @_; ++ my($myresult) = not($postfwd_settings{dns}{disabled}); ++ log_info ("type rhsbl : \"$myitem\" \"$cmp\" \"$val\"") if wantsdebug (qw[ all ]); ++ $myresult = ( rbl_check ($COMP_RHSBL_KEY, $val, $myitem) ) if $myresult; ++ $myresult = not($myresult) if ($cmp eq '!='); ++ return $myresult; ++ }, ++ $COMP_MONTHS => sub { ++ my($cmp,$val,$myitem,%request) = @_; ++ my($myresult) = undef; ++ my($imon) = (split (',', $myitem))[4]; $imon ||= 0; ++ my($rmin,$rmax) = split (/\s*-\s*/, $val); ++ $rmin = ($rmin) ? (($rmin =~ /^\d$/) ? $rmin : $months{$rmin}) : $imon; ++ $rmax = ($rmax) ? (($rmax =~ /^\d$/) ? $rmax : $months{$rmax}) : (($val =~ /-/) ? $imon : $rmin); ++ log_info ("type months : \"$imon\" \"$cmp\" \"$rmin\"-\"$rmax\"") ++ if wantsdebug (qw[ all ]); ++ $myresult = (($rmin <= $imon) and ($rmax >= $imon)); ++ $myresult = not($myresult) if ($cmp eq '!='); ++ return $myresult; ++ }, ++ $COMP_DAYS => sub { ++ my($cmp,$val,$myitem,%request) = @_; ++ my($myresult) = undef; ++ my($iday) = (split (',', $myitem))[6]; $iday ||= 0; ++ my($rmin,$rmax) = split (/\s*-\s*/, $val); ++ $rmin = ($rmin) ? (($rmin =~ /^\d$/) ? $rmin : $weekdays{$rmin}) : $iday; ++ $rmax = ($rmax) ? (($rmax =~ /^\d$/) ? $rmax : $weekdays{$rmax}) : (($val =~ /-/) ? $iday : $rmin); ++ log_info ("type days : \"$iday\" \"$cmp\" \"$rmin\"-\"$rmax\"") ++ if wantsdebug (qw[ all ]); ++ $myresult = (($rmin <= $iday) and ($rmax >= $iday)); ++ $myresult = not($myresult) if ($cmp eq '!='); ++ return $myresult; ++ }, ++ $COMP_DATE => sub { ++ my($cmp,$val,$myitem,%request) = @_; ++ my($myresult) = undef; ++ my($isec,$imin,$ihour,$iday,$imon,$iyear) = split (',', $myitem); ++ my($rmin,$rmax) = split (/\s*-\s*/, $val); ++ my($idat) = ($iyear + 1900) . ((($imon+1) < 10) ? '0'.($imon+1) : ($imon+1)) . (($iday < 10) ? '0'.$iday : $iday); ++ $rmin = ($rmin) ? join ('', reverse split ('\.', $rmin)) : $idat; ++ $rmax = ($rmax) ? join ('', reverse split ('\.', $rmax)) : (($val =~ /-/) ? $idat : $rmin); ++ log_info ("type date : \"$idat\" \"$cmp\" \"$rmin\"-\"$rmax\"") ++ if wantsdebug (qw[ all ]); ++ $myresult = (($rmin <= $idat) and ($rmax >= $idat)); ++ $myresult = not($myresult) if ($cmp eq '!='); ++ return $myresult; ++ }, ++ $COMP_TIME => sub { ++ my($cmp,$val,$myitem,%request) = @_; ++ my($myresult) = undef; ++ my($isec,$imin,$ihour,$iday,$imon,$iyear) = split (',', $myitem); ++ my($rmin,$rmax) = split (/\s*-\s*/, $val); ++ my($idat) = (($ihour < 10) ? '0'.$ihour : $ihour) . (($imin < 10) ? '0'.$imin : $imin) . (($isec < 10) ? '0'.$isec : $isec); ++ $rmin = ($rmin) ? join ('', split ('\:', $rmin)) : $idat; ++ $rmax = ($rmax) ? join ('', split ('\:', $rmax)) : (($val =~ /-/) ? $idat : $rmin); ++ log_info ("type time : \"$idat\" \"$cmp\" \"$rmin\"-\"$rmax\"") ++ if wantsdebug (qw[ all ]); ++ $myresult = (($rmin <= $idat) and ($rmax >= $idat)); ++ $myresult = not($myresult) if ($cmp eq '!='); ++ return $myresult; ++ }, ++ $COMP_HELO_ADDR => sub { ++ my($cmp,$val,$myitem,%request) = @_; ++ my($myresult) = undef; ++ return $myresult if $postfwd_settings{dns}{disabled}; ++ return $myresult unless $myitem =~ /\./; ++ if ( my @answers = dns_query ("$myitem,A") ) { ++ log_info ("type $COMP_HELO_ADDR : \"".(join ',', @answers)."\" \"$cmp\" \"$val\"") if wantsdebug (qw[ all ]); ++ map { $myresult = ( &{$postfwd_compare{cidr}}(($cmp,$val,$_,%request)) ); return $myresult if $myresult } @answers; ++ }; ++ return $myresult; ++ }, ++ $COMP_NS_NAME => sub { ++ my($cmp,$val,$myitem,%request) = @_; ++ my($myresult) = undef; ++ return $myresult if $postfwd_settings{dns}{disabled}; ++ return $myresult unless $myitem =~ /\./; ++ if ( my @answers = dns_query ("$myitem,NS") ) { ++ log_info ("type $COMP_NS_NAME : \"".(join ',', @answers)."\" \"$cmp\" \"$val\"") if wantsdebug (qw[ all ]); ++ map { $myresult = ( &{$postfwd_compare{default}}(($cmp,$val,$_,%request)) ); return $myresult if $myresult } @answers; ++ } else { ++ $myresult = ( &{$postfwd_compare{default}}(($cmp,$val,'',%request)) ); ++ }; ++ return $myresult; ++ }, ++ $COMP_MX_NAME => sub { ++ my($cmp,$val,$myitem,%request) = @_; ++ my($myresult) = undef; ++ return $myresult if $postfwd_settings{dns}{disabled}; ++ return $myresult unless $myitem =~ /\./; ++ if ( my @answers = dns_query ("$myitem,MX") ) { ++ log_info ("type $COMP_MX_NAME : \"".(join ',', @answers)."\" \"$cmp\" \"$val\"") if wantsdebug (qw[ all ]); ++ map { $myresult = ( &{$postfwd_compare{default}}(($cmp,$val,$_,%request)) ); return $myresult if $myresult } @answers; ++ } else { ++ $myresult = ( &{$postfwd_compare{default}}(($cmp,$val,'',%request)) ); ++ }; ++ return $myresult; ++ }, ++ $COMP_NS_ADDR => sub { ++ my($cmp,$val,$myitem,%request) = @_; ++ my($myresult) = undef; ++ return $myresult if $postfwd_settings{dns}{disabled}; ++ return $myresult unless $myitem =~ /\./; ++ if ( my @answers = dns_query ("$myitem,NS") ) { ++ splice (@answers, $postfwd_settings{dns}{max_ns_lookups}) if $postfwd_settings{dns}{max_ns_lookups} and $#answers > $postfwd_settings{dns}{max_ns_lookups}; ++ if ( @answers = dns_query (@answers) ) { ++ log_info ("type $COMP_NS_ADDR : \"".(join ',', @answers)."\" \"$cmp\" \"$val\"") if wantsdebug (qw[ all ]); ++ map { $myresult = ( &{$postfwd_compare{cidr}}(($cmp,$val,$_,%request)) ); return $myresult if $myresult } @answers; ++ }; ++ }; ++ return $myresult; ++ }, ++ $COMP_MX_ADDR => sub { ++ my($cmp,$val,$myitem,%request) = @_; ++ my($myresult) = undef; ++ return $myresult if $postfwd_settings{dns}{disabled}; ++ return $myresult unless $myitem =~ /\./; ++ if ( my @answers = dns_query ("$myitem,MX") ) { ++ splice (@answers, $postfwd_settings{dns}{max_mx_lookups}) if $postfwd_settings{dns}{max_mx_lookups} and $#answers > $postfwd_settings{dns}{max_mx_lookups}; ++ if ( @answers = dns_query (@answers) ) { ++ log_info ("type $COMP_MX_ADDR : \"".(join ',', @answers)."\" \"$cmp\" \"$val\"") if wantsdebug (qw[ all ]); ++ map { $myresult = ( &{$postfwd_compare{cidr}}(($cmp,$val,$_,%request)) ); return $myresult if $myresult } @answers; ++ }; ++ }; ++ return $myresult; ++ }, ++ "default" => sub { ++ my($cmp,$val,$myitem,%request) = @_; ++ my($var,$myresult) = undef; ++ log_info ("type default : \"$myitem\" \"$cmp\" \"$val\"") if wantsdebug (qw[ all ]); ++ # backward compatibility ++ $cmp = '==' if ( ($var) and ($cmp eq '=') ); ++ if ($cmp eq '==') { ++ $myresult = ( lc($myitem) eq lc($val) ) if $myitem; ++ } elsif ( $cmp eq '=~' ) { ++ $myresult = ( $myitem =~ /$val/i ); ++ } elsif ( $cmp eq '!~' ) { ++ $myresult = ( $myitem !~ /$val/i ); ++ } elsif ($cmp eq '!=') { ++ $myresult = not( lc($myitem) eq lc($val) ) if $myitem; ++ } elsif ($cmp eq '=<') { ++ $myresult = (($myitem || 0) <= $val); ++ } elsif ($cmp eq '!<') { ++ $myresult = not(($myitem || 0) <= $val); ++ } elsif ($cmp eq '=>') { ++ $myresult = (($myitem || 0) >= $val); ++ } elsif ($cmp eq '!>') { ++ $myresult = not(($myitem || 0) >= $val); ++ } else { ++ # allow // regex ++ $val =~ s/^\/?(.*?)\/?$/$1/; ++ $myresult = ( $myitem =~ /$val/i ); ++ }; ++ return $myresult; ++ }, ++ "client_address" => sub { return &{$postfwd_compare{cidr}}(@_); }, ++ "encryption_keysize" => sub { return &{$postfwd_compare{numeric}}(@_); }, ++ "size" => sub { return &{$postfwd_compare{numeric}}(@_); }, ++ "recipient_count" => sub { return &{$postfwd_compare{numeric}}(@_); }, ++ "request_score" => sub { return &{$postfwd_compare{numeric}}(@_); }, ++ $COMP_RHSBL_KEY_CLIENT => sub { return &{$postfwd_compare{$COMP_RHSBL_KEY}}(@_); }, ++ $COMP_RHSBL_KEY_SENDER => sub { return &{$postfwd_compare{$COMP_RHSBL_KEY}}(@_); }, ++ $COMP_RHSBL_KEY_HELO => sub { return &{$postfwd_compare{$COMP_RHSBL_KEY}}(@_); }, ++ $COMP_RHSBL_KEY_RCLIENT => sub { return &{$postfwd_compare{$COMP_RHSBL_KEY}}(@_); }, ++); ++# ++# these subroutines define postfwd actions ++# ++%postfwd_actions = ( ++ # example action foo() ++ # "foo" => sub { ++ # my($index,$now,$mycmd,$myarg,$myline,%request) = @_; ++ # my($myaction) = $postfwd_settings{default}; my($stop) = 0; ++ # ... ++ # return ($stop,$index,$myaction,$myline,%request); ++ # }, ++ # jump() command ++ "jump" => sub { ++ my($index,$now,$mycmd,$myarg,$myline,%request) = @_; ++ my($myaction) = $postfwd_settings{default}; my($stop) = 0; ++ if (defined $Rule_by_ID{$myarg}) { ++ my($ruleno) = $Rule_by_ID{$myarg}; ++ log_info ("[RULES] ".$myline ++ .", jump to rule $ruleno (id $myarg)") ++ if wantsdebug (qw[ all verbose ]); ++ $index = $ruleno - 1; ++ } else { ++ log_warn ("[RULES] ".$myline." - error: jump failed, can not find rule-id ".$myarg." - ignoring"); ++ }; ++ return ($stop,$index,$myaction,$myline,%request); ++ }, ++ # set() command ++ "set" => sub { ++ my($index,$now,$mycmd,$myarg,$myline,%request) = @_; ++ my($myaction) = $postfwd_settings{default}; my($stop) = 0; ++ foreach ( split (",", $myarg) ) { ++ if ( /^\s*([^=]+?)\s*([\.\-\*\/\+=]=|=[\.\-\*\/\+=]|=)\s*(.*?)\s*$/ ) { ++ my($r_var, $mod, $r_val) = ($1, $2, $3); ++ my($m_val) = (defined $request{$r_var}) ? $request{$r_var} : 0; ++ # saves some ifs ++ if (($mod eq '=') or ($mod eq '==')) { ++ $m_val = $r_val; ++ } elsif ( ($mod eq '.=') or ($mod eq '=.') ) { ++ $m_val .= $r_val; ++ } elsif ( (($mod eq '+=') or ($mod eq '=+')) and (($m_val=~/^\d+(\.\d+)?$/) and ($r_val=~/^\d+(\.\d+)?$/)) ) { ++ $m_val += $r_val; ++ } elsif ( (($mod eq '-=') or ($mod eq '=-')) and (($m_val=~/^\d+(\.\d+)?$/) and ($r_val=~/^\d+(\.\d+)?$/)) ) { ++ $m_val -= $r_val; ++ } elsif ( (($mod eq '*=') or ($mod eq '=*')) and (($m_val=~/^\d+(\.\d+)?$/) and ($r_val=~/^\d+(\.\d+)?$/)) ) { ++ $m_val *= $r_val; ++ } elsif ( (($mod eq '/=') or ($mod eq '=/')) and (($m_val=~/^\d+(\.\d+)?$/) and ($r_val=~/^\d+(\.\d+)?$/)) ) { ++ $m_val /= (($r_val == 0) ? 1 : $r_val); ++ } else { ++ $m_val = $r_val; ++ }; ++ $m_val = $1.((defined $2) ? $2 : '') if ( $m_val =~ /^(\-?\d+)([\.,]\d\d?)?/ ); ++ (defined $request{$r_var}) ++ ? log_info ("notice", "[RULES] ".$myline.", redefining existing ".$r_var."=".$request{$r_var}." with ".$r_var."=".$m_val) ++ : log_info ("[RULES] ".$myline.", defining ".$r_var."=".$m_val) ++ if wantsdebug (qw[ all verbose ]); ++ $request{$r_var} = $m_val; ++ } else { ++ log_warn ("[RULES] ".$myline.", ignoring unknown set() attribute ".$_); ++ }; ++ }; ++ return ($stop,$index,$myaction,$myline,%request); ++ }, ++ # score() command ++ "score" => sub { ++ my($index,$now,$mycmd,$myarg,$myline,%request) = @_; ++ my($myaction) = $postfwd_settings{default}; my($stop) = 0; ++ my($score) = (defined $request{request_score}) ? $request{request_score} : 0; ++ if ($myarg =~/^([\+\-\*\/\=]?)(\d+)([\.,](\d+))?$/) { ++ my($mod, $val) = ($1, $2 + ((defined $4) ? ($4 / 10) : 0)); ++ if ($mod eq '-') { ++ $score -= $val; ++ } elsif ($mod eq '*') { ++ $score *= $val; ++ } elsif ($mod eq '/') { ++ $score /= $val unless ($val == 0); ++ } elsif ($mod eq '=') { ++ $score = $val; ++ } else { ++ $score += $val; ++ }; ++ $score = $1.((defined $2) ? $2 : '.0') if ( $score =~ /^(\-?\d+)([\.,]\d\d?)?/ ); ++ log_info ("[SCORE] ".$myline.", modifying score about ".$myarg." points to ". $score) ++ if wantsdebug (qw[ all verbose ]); ++ $request{score} = $request{request_score} = $score; ++ } elsif ($myarg) { ++ log_warn ("[RULES] ".$myline.", invalid value for score \"$myarg\" - ignoring"); ++ }; ++ MAXSCORE: foreach my $max_score (reverse sort keys %{$postfwd_settings{scores}}) { ++ if ( ($score >= $max_score) and ($postfwd_settings{scores}{$max_score}) ) { ++ $myaction=$postfwd_settings{scores}{$max_score}; ++ $myline .= ", score=".$score."/".$max_score; ++ $stop = $score; last MAXSCORE; ++ }; ++ }; ++ return ($stop,$index,$myaction,$myline,%request); ++ }, ++ # rate() command ++ "rate" => sub { ++ my($index,$now,$mycmd,$myarg,$myline,%request) = @_; ++ my($myaction) = $postfwd_settings{default}; my($stop) = 0; ++ my($ratetype,$ratecount,$ratetime,$ratecmd) = split "/", $myarg, 4; ++ if ($ratetype and $ratecount and $ratetime and $ratecmd) { ++ unless ( defined $Rate_Cache{$ratetype} ) { ++ log_info ("[RULES] ".$myline ++ .", creating rate object ".$ratetype ++ ." [type: ".$mycmd.", max: ".$ratecount.", time: ".$ratetime."s]") ++ if wantsdebug (qw[ all ]); ++ $Rate_Cache{$ratetype} = { ++ type => $mycmd, ++ maxcount => $ratecount, ++ ttl => $ratetime, ++ 'until' => $now + $ratetime, ++ count => ( ($mycmd eq 'size') ? $request{size} : (($mycmd eq 'rcpt') ? $request{recipient_count} : 1 ) ), ++ rule => $Rules[$index]{$COMP_ID}, ++ action => $ratecmd, ++ }; ++ unless ($postfwd_settings{rate}{noparent}) { ++ my $prate = "CMD=".$postfwd_commands{setcacheitem}.";TYPE=rate;ITEM=$ratetype".hash_to_str(%{$Rate_Cache{$ratetype}}); ++ #cache_query ($prate); ++ }; ++ }; ++ } else { ++ log_note ("[RULES] ".$myline.", ignoring unknown ".$mycmd."() attribute \'".$myarg."\'"); ++ }; ++ return ($stop,$index,$myaction,$myline,%request); ++ }, ++ # size() command ++ "size" => sub { return &{$postfwd_actions{rate}}(@_); }, ++ # rcpt() command ++ "rcpt" => sub { return &{$postfwd_actions{rate}}(@_); }, ++ # wait() command ++ "wait" => sub { ++ my($index,$now,$mycmd,$myarg,$myline,%request) = @_; ++ my($myaction) = $postfwd_settings{default}; my($stop) = 0; ++ log_info ("[RULES] ".$myline.", delaying for $myarg seconds"); ++ sleep $myarg; ++ return ($stop,$index,$myaction,$myline,%request); ++ }, ++ # note() command ++ "note" => sub { ++ my($index,$now,$mycmd,$myarg,$myline,%request) = @_; ++ my($myaction) = $postfwd_settings{default}; my($stop) = 0; ++ log_info ("[RULES] ".$myline." - note: ".$myarg) if $myarg; ++ return ($stop,$index,$myaction,$myline,%request); ++ }, ++ # quit() command - not supported in this version ++ "quit" => sub { ++ my($index,$now,$mycmd,$myarg,$myline,%request) = @_; ++ log_warn ("[RULES] ".$myline." - critical: quit (".$myarg.") unsupported in this version - ignoring"); ++ }, ++ # file() command ++ "file" => sub { ++ my($index,$now,$mycmd,$myarg,$myline,%request) = @_; ++ my($myaction) = $postfwd_settings{default}; my($stop) = 0; ++ log_warn ("[RULES] ".$myline." - error: command ".$mycmd."() has not been implemented yet - ignoring"); ++ return ($stop,$index,$myaction,$myline,%request); ++ }, ++ # ask() command ++ "ask" => sub { ++ my($index,$now,$mycmd,$myarg,$myline,%request) = @_; ++ my($myaction) = $postfwd_settings{default}; my($stop) = 0; ++ log_info ("Opening socket to '$myarg'") if wantsdebug (qw[ all ]); ++ my($addr,$port,$ignore) = split ':', $myarg; ++ my %orig = str_to_hash ($request{orig}); ++ if ( ($addr and $port) and my $socket = new IO::Socket::INET ( ++ PeerAddr => $addr, ++ PeerPort => $port, ++ Proto => 'tcp', ++ Timeout => 9, ++ Type => SOCK_STREAM ) ) { ++ ++ my $sendstr = ''; ++ foreach (keys %orig) { ++ $sendstr .= $_."=".$orig{$_}."\n"; ++ }; ++ $sendstr .= "\n"; ++ log_info ("Asking service $myarg -> '$sendstr'") if wantsdebug (qw[ all ]); ++ print $socket "$sendstr"; ++ $sendstr = <$socket>; ++ chomp($sendstr); ++ log_info ("Answer from $myarg -> '$sendstr'") if wantsdebug (qw[ all verbose ]); ++ $sendstr =~ s/^(action=)//; ++ if ($1 and $sendstr) { ++ if ($ignore and ($sendstr =~ /$ignore/i)) { ++ log_info ("ignoring answer '$sendstr' from $myarg") if wantsdebug (qw[ all verbose ]); ++ } else { ++ $stop = $myaction = $sendstr; ++ }; ++ } else { ++ mylogs ('notice', "rule: $index got invalid answer '$sendstr' from $myarg"); ++ }; ++ } else { ++ log_note ("Could not open socket to '$myarg' - $!"); ++ }; ++ return ($stop,$index,$myaction,$myline,%request); ++ }, ++ # exec() command ++ "exec" => sub { return &{$postfwd_actions{file}}(@_); }, ++); ++ ++# load plugin-items ++sub get_plugins { ++ my(@pluginfiles) = @_; ++ my($pluginlog) = ''; ++ foreach my $file (@pluginfiles) { ++ unless ( -e $file ) { ++ log_warn ("File not found: $file"); ++ } else { ++ $file =~ /^(.*)$/; ++ require $1 if $1; ++ map { delete $postfwd_items_plugin{$_} unless ($_ and defined $postfwd_items_plugin{$_}) } (keys %postfwd_items_plugin); ++ map { delete $postfwd_compare_plugin{$_} unless ($_ and defined $postfwd_compare_plugin{$_}) } (keys %postfwd_compare_plugin); ++ map { delete $postfwd_actions_plugin{$_} unless ($_ and defined $postfwd_actions_plugin{$_}) } (keys %postfwd_actions_plugin); ++ map { log_note ("[PLUGIN] overriding prior item \'".$_."\'") if (defined $postfwd_items{$_}) } (keys %postfwd_items_plugin); ++ map { log_note ("[PLUGIN] overriding prior compare function \'".$_."\'") if (defined $postfwd_compare{$_}) } (keys %postfwd_compare_plugin); ++ map { log_note ("[PLUGIN] overriding prior action \'".$_."\'") if (defined $postfwd_actions{$_}) } (keys %postfwd_actions_plugin); ++ %postfwd_items = ( %postfwd_items, %postfwd_items_plugin ) if %postfwd_items_plugin; ++ %postfwd_compare = ( %postfwd_compare, %postfwd_compare_plugin ) if %postfwd_compare_plugin; ++ %postfwd_actions = ( %postfwd_actions, %postfwd_actions_plugin ) if %postfwd_actions_plugin; ++ $pluginlog = "[PLUGIN] Loaded plugins file: ".$file; ++ $pluginlog .= " items: \"".(join ", ", (sort keys %postfwd_items_plugin))."\"" ++ if %postfwd_items_plugin; ++ $pluginlog .= " compare: \"".(join ", ", (sort keys %postfwd_compare_plugin))."\"" ++ if %postfwd_compare_plugin; ++ $pluginlog .= " actions: \"".(join ", ", (sort keys %postfwd_actions_plugin))."\"" ++ if %postfwd_actions_plugin; ++ log_info ($pluginlog); ++ }; ++ }; ++}; ++ ++ ++### SUB ruleset ++ ++# compare item main ++# use: compare_item ( $TYPE, $RULEITEM, $MINIMUMHITS, $REQUESTITEM, %REQUEST, %REQUESTINFO ); ++sub compare_item { ++ my($mykey,$mymask,$mymin,$myitem, %request) = @_; ++ my($val,$var,$cmp,$neg,$myresult,$postfwd_compare_proc); ++ my($rcount) = 0; ++ $mymin ||= 1; ++ ++ # ++ # determine the right compare function ++ $postfwd_compare_proc = (defined $postfwd_compare{$mykey}) ? $mykey : "default"; ++ # ++ # save list due to possible modification ++ my @items = @{$mymask}; ++ # now compare request to every single item ++ ITEM: foreach (@items) { ++ ($cmp, $val) = split ";"; ++ next ITEM unless ($cmp and $val and $mykey); ++ # prepare_file ++ if ($val =~ /$COMP_LIVE_FILE_TABLE/) { ++ push @items, prepare_file (0, $1, $cmp, $2); ++ next ITEM; ++ }; ++ log_info ("compare $mykey: \"$myitem\" \"$cmp\" \"$val\"") if wantsdebug (qw[ all ]); ++ $val = $neg if ($neg = deneg_item($val)); ++ log_info ("deneg $mykey: \"$myitem\" \"$cmp\" \"$val\"") if ($neg and wantsdebug (qw[ all ])); ++ next ITEM unless $val; ++ # substitute check for $$vars in rule item ++ if ( $var = devar_item ($cmp,$val,$myitem,%request) ) { ++ $val = $var; $val =~ s/([^-_@\.\w\s])/\\$1/g unless ($cmp eq '=='); ++ }; ++ $myresult = &{$postfwd_compare{$postfwd_compare_proc}}($cmp,$val,$myitem,%request); ++ log_info ("match $mykey: ".($myresult ? "TRUE" : "FALSE")) if wantsdebug (qw[ all ]); ++ if ($neg) { ++ $myresult = not($myresult); ++ log_info ("negate match $mykey: ".($myresult ? "TRUE" : "FALSE")) if wantsdebug (qw[ all ]); ++ }; ++ $rcount++ if $myresult; ++ $myresult = not($mymin eq 'all'); ++ $myresult = ( $rcount >= $mymin ) if $myresult; ++ log_info ("count $mykey: request=$rcount minimum: $mymin result: ".($myresult ? "TRUE" : "FALSE")) if wantsdebug (qw[ all ]); ++ last ITEM if $myresult; ++ }; ++ $myresult = $rcount if ($myresult or ($mymin eq 'all')); ++ return $myresult; ++}; ++ ++ ++# ++# compare request against a single rule ++# ++sub compare_rule { ++ my($index,$date,%request) = @_; ++ my(@ruleitems) = keys %{$Rules[$index]}; ++ my($has_rbl) = exists($Rules[$index]{$COMP_RBL_KEY}); ++ my($has_rhl) = ( ++ exists($Rules[$index]{$COMP_RHSBL_KEY}) or exists($Rules[$index]{$COMP_RHSBL_KEY_RCLIENT}) or ++ exists($Rules[$index]{$COMP_RHSBL_KEY_CLIENT}) or exists($Rules[$index]{$COMP_RHSBL_KEY_SENDER}) or ++ exists($Rules[$index]{$COMP_RHSBL_KEY_HELO}) ++ ); ++ my($has_senderdns) = ( exists($Rules[$index]{$COMP_NS_NAME}) ++ or exists($Rules[$index]{$COMP_MX_NAME}) ++ or exists($Rules[$index]{$COMP_NS_ADDR}) ++ or exists($Rules[$index]{$COMP_MX_ADDR}) ++ ); ++ my($hasdns) = ( not($postfwd_settings{dns}{disabled}) and ($has_senderdns or $has_rhl or $has_rbl) ); ++ my($mykey,$myitem,$val,$cmp,$res,$myline,$timed) = undef; ++ my(@myresult) = (0,0,0,0); ++ my(@queries,@timedout) = (); ++ my($num) = 1; ++ undef @DNSBL_Text; ++ my($ownres,$ownsel,$bgsock) = undef; ++ my %ownsock = (); ++ my @ownready = (); ++ ++ log_info ("[RULES] rule: $index, id: $Rules[$index]{$COMP_ID}, items: '".((@ruleitems) ? join ';', @ruleitems: '')."'") if wantsdebug (qw[ all ]); ++ ++ # COMPARE-ITEMS ++ # check all non-dns items ++ ITEM: for $mykey ( keys %{$Rules[$index]} ) { ++ # always true ++ if ( ($mykey eq $COMP_ID) or ($mykey eq $COMP_ACTION) or ($mykey eq $COMP_CACHE) ) { ++ $myresult[0]++; ++ next ITEM; ++ }; ++ next ITEM if ( (($mykey eq $COMP_RBL_CNT) or ($mykey eq $COMP_RHSBL_CNT)) ); ++ next ITEM if ( (($mykey eq $COMP_RBL_KEY) or ($mykey eq $COMP_RHSBL_KEY)) ); ++ next ITEM if ( ($mykey eq $COMP_RHSBL_KEY_RCLIENT) or ($mykey eq $COMP_RHSBL_KEY_CLIENT) or ($mykey eq $COMP_RHSBL_KEY_SENDER) or ($mykey eq $COMP_RHSBL_KEY_HELO) ); ++ ++ # integration at this point enables redefining scores within ruleset ++ if ($mykey eq $COMP_SCORES) { ++ modify_score ($Rules[$index]{$mykey},$Rules[$index]{$COMP_ACTION}); ++ $myresult[0] = 0; ++ } else { ++ $val = ( $mykey =~ /^$COMP_DATECALC$/ ) ++ # prepare date check ++ ? $date ++ # default: compare against request attribute ++ : $request{$mykey}; ++ $myresult[0] = ($res = compare_item($mykey, $Rules[$index]{$mykey}, $num, ($val || ''), %request)) ? ($myresult[0] + $res) : 0; ++ }; ++ last ITEM unless ($myresult[0] > 0); ++ }; ++ log_info ("[RULES] pre-dns: rule: $index, id: $Rules[$index]{$COMP_ID}, RESULT: ".$myresult[0]) if wantsdebug (qw[ all ]); ++ ++ # DNSQUERY-SECTION ++ # fire bgsend()s with callback to result cache, ++ # if they are not contained already, ++ # and $postfwd_settings{dns}{disabled} is not set ++ if ($hasdns and $myresult[0]) { ++ ++ # prepare dns queries ++ $ownres = Net::DNS::Resolver->new( ++ tcp_timeout => $postfwd_settings{dns}{timeout}, ++ udp_timeout => $postfwd_settings{dns}{timeout}, ++ persistent_tcp => 0, persistent_udp => 0, ++ retrans => 0, retry => 1, dnsrch => 0, defnames => 0, ++ ); ++ $ownsel = IO::Select->new(); ++ ++ map { $timed .= (($timed) ? ", $_" : $_) if $Timeouts{$_} > $postfwd_settings{dns}{max_timeout} } (keys %Timeouts); ++ log_note ("[DNSBL] skipping rbls: $timed - too much timeouts") if $timed; ++ ++ push @queries, rbl_prepare_lookups ( $COMP_RBL_KEY, $request{reverse_address}, @{$Rules[$index]{$COMP_RBL_KEY}} ) ++ if (defined $Rules[$index]{$COMP_RBL_KEY}); ++ ++ push @queries, rbl_prepare_lookups ( $COMP_RHSBL_KEY, $request{client_name}, @{$Rules[$index]{$COMP_RHSBL_KEY}} ) ++ if (defined $Rules[$index]{$COMP_RHSBL_KEY}); ++ ++ push @queries, rbl_prepare_lookups ( $COMP_RHSBL_KEY_CLIENT, $request{client_name}, @{$Rules[$index]{$COMP_RHSBL_KEY_CLIENT}} ) ++ if (defined $Rules[$index]{$COMP_RHSBL_KEY_CLIENT}); ++ ++ push @queries, rbl_prepare_lookups ( $COMP_RHSBL_KEY_RCLIENT, $request{reverse_client_name}, @{$Rules[$index]{$COMP_RHSBL_KEY_RCLIENT}} ) ++ if (defined $Rules[$index]{$COMP_RHSBL_KEY_RCLIENT}); ++ ++ push @queries, rbl_prepare_lookups ( $COMP_RHSBL_KEY_HELO, $request{helo_name}, @{$Rules[$index]{$COMP_RHSBL_KEY_HELO}} ) ++ if (defined $Rules[$index]{$COMP_RHSBL_KEY_HELO}); ++ ++ push @queries, rbl_prepare_lookups ( $COMP_RHSBL_KEY_SENDER, $request{sender_domain}, @{$Rules[$index]{$COMP_RHSBL_KEY_SENDER}} ) ++ if (defined $Rules[$index]{$COMP_RHSBL_KEY_SENDER}); ++ ++ # send dns queries ++ if ( @queries ) { ++ @queries = uniq(@queries); ++ QUERY: foreach my $query (@queries) { ++ next QUERY unless $query; ++ log_info ("[SENDDNS] sending query \'$query\'") ++ if wantsdebug (qw[ all ]); ++ # send A query ++ $bgsock = $ownres->bgsend($query, 'A'); ++ $ownsel->add($bgsock); ++ $ownsock{$bgsock} = 'A:'.$query; ++ # send TXT query ++ if ($postfwd_settings{dns}{async_txt}) { ++ $bgsock = $ownres->bgsend($query, 'TXT'); ++ $ownsel->add($bgsock); ++ $ownsock{$bgsock} = 'TXT:'.$query; ++ }; ++ }; ++ log_info ("[SENDDNS] rule: $index, id: $Rules[$index]{$COMP_ID}, lookups: ".($#queries + 1)) ++ if wantsdebug (qw[ all ]); ++ $myresult[3] = "dnsqueries=".($#queries + 1).$postfwd_settings{sepreq}."dnsinterval=".($#queries + 1); ++ }; ++ ++ # DNSRESULT-SECTION ++ # wait for select() and check the results unless $postfwd_settings{dns}{disabled} ++ my($ownstart) = time(); @queries = (); ++ while ((scalar keys %ownsock) and (@ownready = $ownsel->can_read($postfwd_settings{dns}{timeout}))) { ++ foreach my $sock (@ownready) { ++ if (defined $ownsock{$sock}) { ++ log_note ("[DNSBL] answer for ".$ownsock{$sock}) ++ if wantsdebug (qw[ all ]); ++ my $packet = $ownres->bgread($sock); ++ push @queries, (split ':', $ownsock{$sock})[1] if rbl_read_dns ($packet); ++ delete $ownsock{$sock}; ++ } else { ++ $ownsel->remove($sock); ++ $sock = undef; ++ }; ++ }; ++ }; ++ ++ # timeout handling ++ map { push @timedout, (split ':', $ownsock{$_})[1] } (keys %ownsock); ++ if (@timedout) { ++ @timedout = uniq(@timedout); ++ $myresult[3] .= $postfwd_settings{sepreq}."dnstimeouts=".($#timedout + 1); ++ foreach (@timedout) { ++ my $now = time(); ++ # @{$DNS_Cache{$_}{A}} = ('__TIMEOUT__'); ++ $DNS_Cache{$_}{ttl} = $postfwd_settings{dns}{ttl} unless $DNS_Cache{$_}{ttl}; ++ $DNS_Cache{$_}{'delay'} = $now - $ownstart; ++ $DNS_Cache{$_}{'until'} = $now + $DNS_Cache{$_}{ttl}; ++ $DNS_Cache{$_}{'timed'} = 1; ++ $Timeouts{$DNS_Cache{$_}{name}} = (defined $Timeouts{$DNS_Cache{$_}{name}}) ++ ? $Timeouts{$DNS_Cache{$_}{name}} + 1 ++ : 1 ++ if ( $postfwd_settings{dns}{max_timeout} > 0 ); ++ log_note ("[DNSBL] warning: timeout (".$Timeouts{$DNS_Cache{$_}{name}}."/".$postfwd_settings{dns}{max_timeout}.") for ".$DNS_Cache{$_}{name}." after ".$DNS_Cache{$_}{'delay'}." seconds"); ++ }; ++ }; ++ ++ # perform outstanding TXT queries unless --dns_async_txt is set ++ if (not($postfwd_settings{dns}{async_txt}) and @queries) { ++ @queries = uniq(@queries); ++ log_info ("[DNSBL] sending TXT queries for ".(join ',', @queries)) if wantsdebug (qw[ all debugdns ]); ++ foreach my $query (@queries) { ++ log_info ("[SENDDNS] sending TXT query \'$query\'") if wantsdebug (qw[ all ]); ++ # send TXT query ++ $bgsock = $ownres->bgsend($query, 'TXT'); ++ $ownsel->add($bgsock); ++ $ownsock{$bgsock} = 'TXT:'.$query; ++ }; ++ while ((scalar keys %ownsock) and (@ownready = $ownsel->can_read($postfwd_settings{dns}{timeout}))) { ++ foreach my $sock (@ownready) { ++ if (defined $ownsock{$sock}) { ++ log_info ("[DNSBL] answer for ".$ownsock{$sock}) ++ if wantsdebug (qw[ all ]); ++ my $packet = $ownres->bgread($sock); ++ rbl_read_dns ($packet); ++ delete $ownsock{$sock}; ++ } else { ++ $ownsel->remove($sock); ++ $sock = undef; ++ }; ++ }; ++ }; ++ }; ++ ++ # compare dns results ++ if ( ($myresult[0] > 0) and exists($Rules[$index]{$COMP_RBL_KEY}) ) { ++ $res = compare_item( ++ $COMP_RBL_KEY, ++ $Rules[$index]{$COMP_RBL_KEY}, ++ ($Rules[$index]{$COMP_RBL_CNT} ||= 1), ++ $request{reverse_address}, ++ %request ++ ); ++ $myresult[0] = ($res or ($Rules[$index]{$COMP_RBL_CNT} eq 'all')) ? ($myresult[0] + $res) : 0; ++ $myresult[1] = ($res) ? $res : 0; ++ }; ++ ++ if ( $has_rhl and ($myresult[0] > 0) ) { ++ if ( exists($Rules[$index]{$COMP_RHSBL_KEY}) ) { ++ $res = compare_item( ++ $COMP_RHSBL_KEY, ++ $Rules[$index]{$COMP_RHSBL_KEY}, ++ ($Rules[$index]{$COMP_RHSBL_CNT} ||= 1), ++ $request{client_name}, ++ %request ++ ); ++ $myresult[0] = ($res or ($Rules[$index]{$COMP_RHSBL_CNT} eq 'all')) ? ($myresult[0] + $res) : 0; ++ $myresult[2] += $res if $res; ++ }; ++ if ( exists($Rules[$index]{$COMP_RHSBL_KEY_CLIENT}) ) { ++ $res = compare_item( ++ $COMP_RHSBL_KEY_CLIENT, ++ $Rules[$index]{$COMP_RHSBL_KEY_CLIENT}, ++ ($Rules[$index]{$COMP_RHSBL_CNT} ||= 1), ++ $request{client_name}, ++ %request ++ ); ++ $myresult[0] = ($res or ($Rules[$index]{$COMP_RHSBL_CNT} eq 'all')) ? ($myresult[0] + $res) : 0; ++ $myresult[2] += $res if $res; ++ }; ++ if ( exists($Rules[$index]{$COMP_RHSBL_KEY_SENDER}) ) { ++ $res = compare_item( ++ $COMP_RHSBL_KEY_SENDER, ++ $Rules[$index]{$COMP_RHSBL_KEY_SENDER}, ++ ($Rules[$index]{$COMP_RHSBL_CNT} ||= 1), ++ $request{sender_domain}, ++ %request ++ ); ++ $myresult[0] = ($res or ($Rules[$index]{$COMP_RHSBL_CNT} eq 'all')) ? ($myresult[0] + $res) : 0; ++ $myresult[2] += $res if $res; ++ }; ++ if ( exists($Rules[$index]{$COMP_RHSBL_KEY_HELO}) ) { ++ $res = compare_item( ++ $COMP_RHSBL_KEY_HELO, ++ $Rules[$index]{$COMP_RHSBL_KEY_HELO}, ++ ($Rules[$index]{$COMP_RHSBL_CNT} ||= 1), ++ $request{helo_name}, ++ %request ++ ); ++ $myresult[0] = ($res or ($Rules[$index]{$COMP_RHSBL_CNT} eq 'all')) ? ($myresult[0] + $res) : 0; ++ $myresult[2] += $res if $res; ++ }; ++ if ( exists($Rules[$index]{$COMP_RHSBL_KEY_RCLIENT}) ) { ++ $res = compare_item( ++ $COMP_RHSBL_KEY_RCLIENT, ++ $Rules[$index]{$COMP_RHSBL_KEY_RCLIENT}, ++ ($Rules[$index]{$COMP_RHSBL_CNT} ||= 1), ++ $request{reverse_client_name}, ++ %request ++ ); ++ $myresult[0] = ($res or ($Rules[$index]{$COMP_RHSBL_CNT} eq 'all')) ? ($myresult[0] + $res) : 0; ++ $myresult[2] += $res if $res; ++ }; ++ }; ++ }; ++ if ( wantsdebug (qw[ all ]) ) { ++ $myline = "[RULES] RULE: ".$index." MATCHES: ".((($myresult[0] - 2) > 0) ? ($myresult[0] - 2) : 0); ++ $myline .= " RBLCOUNT: ".$myresult[1] if $myresult[1]; ++ $myline .= " RHSBLCOUNT: ".$myresult[2] if $myresult[2]; ++ $myline .= " DNSBLTEXT: ".(join ("; ", @DNSBL_Text)) if ( (defined @DNSBL_Text) and (($myresult[1] > 0) or ($myresult[2] > 0)) ); ++ log_info ($myline); ++ }; ++ return @myresult; ++}; ++ ++ ++### SUB access policy ++ ++# access policy routine ++sub smtpd_access_policy { ++ my($parent,%request) = @_; ++ my($myaction) = $postfwd_settings{default}; ++ my($index) = 1; ++ my($now) = time(); ++ my($date) = join(',', localtime($now)); ++ my($counters) = "request=1".$postfwd_settings{sepreq}."interval=1"; ++ my($matched,$rblcnt,$rhlcnt,$t1,$t2,$t3,$stop) = 0; ++ my($mykey,$cacheid,$myline,$checkreq,$var,$ratehit,$rulehits) = ""; ++ ++ # save original request ++ $request{orig} = hash_to_str (%request); ++ ++ # replace empty sender with <> ++ $request{sender} = '<>' unless ($request{sender}); ++ ++ # load postfwd_items attributes ++ if ( my(%postfwd_items_attr) = postfwd_items (%request) ) { ++ %request = (%request, %postfwd_items_attr); ++ }; ++ ++ # clear dnsbl timeout counters ++ if ( $Cleanup_Timeouts and ($postfwd_settings{dns}{max_interval} > 0) and (($now - $Cleanup_Timeouts) > $postfwd_settings{dns}{max_interval}) ) { ++ undef %Timeouts; ++ log_info ("[CLEANUP] clearing dnsbl timeout counters") if wantsdebug (qw[ all verbose ]); ++ $Cleanup_Timeouts = $now; ++ }; ++ ++ # wipe out old cache items ++ if ( $Cleanup_Rates and ($postfwd_settings{rate}{cleanup} > 0) and (scalar keys %Rate_Cache > 0) and (($now - $Cleanup_Rates) > $postfwd_settings{rate}{cleanup}) ) { ++ $t1 = time(); ++ $t3 = scalar keys %Rate_Cache; ++ cleanup_rate_cache($now); ++ $t2 = time(); ++ log_info ("[CLEANUP] needed ".($t2 - $t1) ++ ." seconds for rate cleanup of " ++ .($t3 - scalar keys %Rate_Cache)." out of ".$t3 ++ ." cached items after ".($now - $Cleanup_Rates) ++ ." seconds (min ".$postfwd_settings{rate}{cleanup}."s)") if ( wantsdebug (qw[ all verbose ]) or (($t2 - $t1) > 0) ); ++ $Cleanup_Rates = $t1; ++ }; ++ ++ # increase rate limits ++ RATES: foreach $checkreq (keys %request) { ++ next RATES unless ( $request{$checkreq} and (defined $Rate_Cache{$request{$checkreq}}) ); ++ if ( $now > $Rate_Cache{$request{$checkreq}}{'until'} ) { ++ # renew rate ++ $Rate_Cache{$request{$checkreq}}{count} = ( ($Rate_Cache{$request{$checkreq}}{type} eq 'size') ? $request{size} : ++ (($Rate_Cache{$request{$checkreq}}{type} eq 'rcpt') ? $request{recipient_count} : 1 ) ); ++ $Rate_Cache{$request{$checkreq}}{'until'} = $now + $Rate_Cache{$request{$checkreq}}{ttl}; ++ log_info ("[RATE] renewing rate object ".$request{$checkreq} ++ ." [type: ".$Rate_Cache{$request{$checkreq}}{type} ++ .", max: ".$Rate_Cache{$request{$checkreq}}{maxcount} ++ .", time: ".$Rate_Cache{$request{$checkreq}}{ttl}."s]") ++ if wantsdebug (qw[ all ]); ++ } else { ++ # increase rate ++ $Rate_Cache{$request{$checkreq}}{count} += ( ($Rate_Cache{$request{$checkreq}}{type} eq 'size') ? $request{size} : ++ (($Rate_Cache{$request{$checkreq}}{type} eq 'rcpt') ? $request{recipient_count} : 1 ) ); ++ log_info ("[RATE] increasing rate object ".$request{$checkreq} ++ ." to ".$Rate_Cache{$request{$checkreq}}{count} ++ ." [type: ".$Rate_Cache{$request{$checkreq}}{type} ++ .", max: ".$Rate_Cache{$request{$checkreq}}{maxcount} ++ .", time: ".$Rate_Cache{$request{$checkreq}}{ttl}."s]" ++ ) if wantsdebug (qw[ all ]); ++ $ratehit = $checkreq if ($Rate_Cache{$request{$checkreq}}{count} > $Rate_Cache{$request{$checkreq}}{maxcount}); ++ last RATES if $ratehit; ++ }; ++ }; ++ ++ # Request cache enabled? ++ if ( $postfwd_settings{request}{ttl} > 0 ) { ++ ++ # construct cache identifier ++ if ($postfwd_settings{cacheid}) { ++ map { $cacheid .= $request{$_}.';' if (defined $request{$_}) } @{$postfwd_settings{cacheid}}; ++ } else { ++ REQITEM: foreach $checkreq (sort keys %request) { ++ next REQITEM unless $request{$checkreq}; ++ next REQITEM if ( ($checkreq eq "instance") or ($checkreq eq "queue_id") or ($checkreq eq "orig")); ++ next REQITEM if ( $postfwd_settings{request}{no_size} and ($checkreq eq "size") ); ++ next REQITEM if ( $postfwd_settings{request}{no_sender} and ($checkreq eq "sender") ); ++ if ( $postfwd_settings{request}{rdomain_only} and ($checkreq eq "recipient") ) { ++ $cacheid .= $request{recipient_domain}.';'; ++ } else { ++ $cacheid .= $request{$checkreq}.';'; ++ }; ++ }; ++ }; ++ log_info ("created cache-id: $cacheid") if wantsdebug (qw[ all ]); ++ ++ # wipe out old cache entries ++ if ( $Cleanup_Requests and (scalar keys %Request_Cache > 0) and (($now - $Cleanup_Requests) > $postfwd_settings{request}{cleanup}) ) { ++ $t1 = time(); ++ $t3 = scalar keys %Request_Cache; ++ cleanup_request_cache($now); ++ $t2 = time(); ++ log_info ("[CLEANUP] needed ".($t2 - $t1) ++ ." seconds for request cleanup of " ++ .($t3 - scalar keys %Request_Cache)." out of ".$t3 ++ ." cached items after ".($now - $Cleanup_Requests) ++ ." seconds (min ".$postfwd_settings{request}{cleanup}."s)") if ( wantsdebug (qw[ all verbose ]) or (($t2 - $t1) > 0) ); ++ $Cleanup_Requests = $t1; ++ }; ++ }; ++ ++ # check rate ++ if ( $ratehit ) { ++ ++ $counters .= $postfwd_settings{sepreq}."rate=1"; ++ $Matches{$Rate_Cache{$request{$ratehit}}{rule}}++; ++ $myaction = $Rate_Cache{$request{$ratehit}}{action}; ++ log_info ("[RATE] rule=".$Rule_by_ID{$Rate_Cache{$request{$ratehit}}{rule}} ++ . ", id=".$Rate_Cache{$request{$ratehit}}{rule} ++ . ", client=".$request{client_name}."[".$request{client_address}."]" ++ . ", sender=<".(($request{sender} eq '<>') ? "" : $request{sender}).">" ++ . ", recipient=<".$request{recipient}.">" ++ . ", helo=<".$request{helo_name}.">" ++ . ", proto=".$request{protocol_name} ++ . ", state=".$request{protocol_state} ++ . ", delay=".(time() - $now)."s" ++ . ", action=".$myaction." (item: ".$request{$ratehit} ++ . ", type: ".$Rate_Cache{$request{$ratehit}}{type} ++ . ", count: ".$Rate_Cache{$request{$ratehit}}{count}."/".$Rate_Cache{$request{$ratehit}}{maxcount} ++ . ", ttl: ".$Rate_Cache{$request{$ratehit}}{ttl}."s" ++ ) unless $postfwd_settings{request}{nolog}; ++ ++ # check own cache ++ } elsif ( ($postfwd_settings{request}{ttl} > 0) ++ and ((exists($Request_Cache{$cacheid}{$COMP_ACTION})) and ($now <= $Request_Cache{$cacheid}{'until'})) ) { ++ $counters .= $postfwd_settings{sepreq}."ccache=1"; ++ $myaction = $Request_Cache{$cacheid}{$COMP_ACTION}; ++ if ( $Request_Cache{$cacheid}{hit} ) { ++ $Matches{$Request_Cache{$cacheid}{$COMP_ID}}++; ++ $rulehits = join $postfwd_settings{sepreq}, (split ';', $Request_Cache{$cacheid}{hits}) if $Request_Cache{$cacheid}{hits}; ++ log_info ("[CACHE] rule=".$Rule_by_ID{$Request_Cache{$cacheid}{$COMP_ID}} ++ . ", id=".$Request_Cache{$cacheid}{$COMP_ID} ++ . ", client=".$request{client_name}."[".$request{client_address}."]" ++ . ", sender=<".(($request{sender} eq '<>') ? "" : $request{sender}).">" ++ . ", recipient=<".$request{recipient}.">" ++ . ", helo=<".$request{helo_name}.">" ++ . ", proto=".$request{protocol_name} ++ . ", state=".$request{protocol_state} ++ . ", delay=".(time() - $now)."s" ++ . ", hits=".$Request_Cache{$cacheid}{hits} ++ . ", action=".$Request_Cache{$cacheid}{$COMP_ACTION} ++ ) unless $postfwd_settings{request}{nolog}; ++ }; ++ ++ # check parent cache ++ } elsif ( ($postfwd_settings{request}{ttl} > 0) ++ and not($postfwd_settings{request}{noparent}) ++ and not((my $pans = cache_query ("CMD=".$postfwd_commands{getcacheitem}.";TYPE=request;ITEM=$cacheid")) eq '') ) { ++ map { $Request_Cache{$cacheid}{$1} = $2 if m/$postfwd_patterns{keyval}/ } (split $postfwd_settings{sepreq}, $pans); ++ $counters .= $postfwd_settings{sepreq}."pcache=1"; ++ $myaction = $Request_Cache{$cacheid}{$COMP_ACTION}; ++ if ( $Request_Cache{$cacheid}{hit} ) { ++ $Matches{$Request_Cache{$cacheid}{$COMP_ID}}++; ++ $rulehits = join $postfwd_settings{sepreq}, (split ';', $Request_Cache{$cacheid}{hits}) if $Request_Cache{$cacheid}{hits}; ++ log_info ("[CACHE] rule=".$Rule_by_ID{$Request_Cache{$cacheid}{$COMP_ID}} ++ . ", id=".$Request_Cache{$cacheid}{$COMP_ID} ++ . ", client=".$request{client_name}."[".$request{client_address}."]" ++ . ", sender=<".(($request{sender} eq '<>') ? "" : $request{sender}).">" ++ . ", recipient=<".$request{recipient}.">" ++ . ", helo=<".$request{helo_name}.">" ++ . ", proto=".$request{protocol_name} ++ . ", state=".$request{protocol_state} ++ . ", delay=".(time() - $now)."s" ++ . ", hits=".$Request_Cache{$cacheid}{hits} ++ . ", action=".$Request_Cache{$cacheid}{$COMP_ACTION} ++ ) unless $postfwd_settings{request}{nolog}; ++ }; ++ ++ # check rules ++ } else { ++ ++ # refresh config if '-I' was set ++ read_config(0) if $postfwd_settings{instant}; ++ ++ if ($#Rules < 0) { ++ log_note("critical: no rules found - i feel useless (have you set -f or -r?)"); ++ ++ } else { ++ ++ # clean up rbl cache ++ if ( not($postfwd_settings{dns}{disabled}) and (scalar keys %DNS_Cache > 0) and (($now - $Cleanup_RBLs) > $postfwd_settings{dns}{cleanup}) ) { ++ $t1 = time(); ++ $t3 = scalar keys %DNS_Cache; ++ cleanup_dns_cache($now); ++ $t2 = time(); ++ log_info ("[CLEANUP] needed ".($t2 - $t1) ++ ." seconds for rbl cleanup of " ++ .($t3 - scalar keys %DNS_Cache)." out of ".$t3 ++ ." cached items after ".($now - $Cleanup_RBLs) ++ ." seconds (min ".$postfwd_settings{dns}{cleanup}."s)") if ( wantsdebug (qw[ all verbose ]) or (($t2 - $t1) > 0) ); ++ $Cleanup_RBLs = $t1; ++ }; ++ ++ # prepares hit counters ++ $request{$COMP_MATCHES} = 0; ++ $request{$COMP_RBL_CNT} = 0; ++ $request{$COMP_RHSBL_CNT} = 0; ++ ++ RULE: for ($index=0;$index<=$#Rules;$index++) { ++ ++ # compare request against rule ++ next unless exists $Rules[$index]; ++ ($matched,$rblcnt,$rhlcnt,my $compcnt) = compare_rule ($index, $date, %request); ++ ++ # enables/overrides hit counters for later use ++ $request{$COMP_MATCHES} = $matched; ++ $request{$COMP_RBL_CNT} = $rblcnt; ++ $request{$COMP_RHSBL_CNT} = $rhlcnt; ++ $counters .= $postfwd_settings{sepreq}.$compcnt if $compcnt; ++ ++ # matched? prepare logline, increase counters ++ if ($matched > 0) { ++ $myaction = $Rules[$index]{$COMP_ACTION}; ++ $Matches{$Rules[$index]{$COMP_ID}}++; ++ $rulehits .= $postfwd_settings{sepreq} if $rulehits; ++ $rulehits .= $Rules[$index]{$COMP_ID}; ++ $request{$COMP_HITS} .= ';' if (defined $request{$COMP_HITS}); ++ $request{$COMP_HITS} .= $Rules[$index]{$COMP_ID}; ++ # substitute check for $$vars in action ++ $myaction = $var if ( $var = devar_item ("==",$myaction,"action",%request) ); ++ $myline = "rule=".$index ++ . ", id=".$Rules[$index]{$COMP_ID} ++ . ", client=".$request{client_name}."[".$request{client_address}."]" ++ . ", sender=<".(($request{sender} eq '<>') ? "" : $request{sender}).">" ++ . ", recipient=<".$request{recipient}.">" ++ . ", helo=<".$request{helo_name}.">" ++ . ", proto=".$request{protocol_name} ++ . ", state=".$request{protocol_state}; ++ ++ # check for postfwd action ++ if ($myaction =~ /^(\w[\-\w]+)\s*\(\s*(.*?)\s*\)$/) { ++ my($mycmd,$myarg) = ($1, $2); ++ if (defined $postfwd_actions{$mycmd}) { ++ log_info ("[PLUGIN] executing postfwd-action $mycmd") if wantsdebug (qw[ all ]); ++ ($stop, $index, $myaction, $myline, %request) = &{$postfwd_actions{$mycmd}}($index, $now, $mycmd, $myarg, $myline, %request); ++ # substitute again after postfwd-actions ++ $myaction = $var if ( $var = devar_item ("==",$myaction,"action",%request) ); ++ } else { ++ log_warn ("[RULES] ".$myline." - error: unknown command \"".$1."\" - ignoring"); ++ $myaction = $postfwd_settings{default}; ++ }; ++ # normal rule. returns $action. ++ } else { $stop = 1; }; ++ if ($stop) { ++ $myline .= ", delay=".(time() - $now)."s, hits=".$request{$COMP_HITS}.", action=".$myaction; ++ log_info ("[RULES] ".$myline) unless $postfwd_settings{request}{nolog}; ++ $counters .= $postfwd_settings{sepreq}."ruleset=1"; ++ # update cache ++ if ( $postfwd_settings{request}{ttl} > 0 ) { ++ $Request_Cache{$cacheid}{ttl} = ($Rules[$index]{$COMP_CACHE} || $postfwd_settings{request}{ttl}); ++ $Request_Cache{$cacheid}{'until'} = $now + $Request_Cache{$cacheid}{ttl}; ++ $Request_Cache{$cacheid}{$COMP_ACTION} = $myaction; ++ $Request_Cache{$cacheid}{$COMP_ID} = $Rules[$index]{$COMP_ID}; ++ $Request_Cache{$cacheid}{hit} = $matched; ++ $Request_Cache{$cacheid}{hits} = $request{$COMP_HITS}; ++ cache_query ("CMD=".$postfwd_commands{setcacheitem}.";TYPE=request;ITEM=$cacheid".hash_to_str(%{$Request_Cache{$cacheid}})) ++ unless ($postfwd_settings{request}{noparent}); ++ }; ++ last RULE; ++ }; ++ } else { undef $myline; }; ++ }; ++ }; ++ }; ++ # increase counters and return action ++ if ($postfwd_settings{summary} and defined $parent) { ++ print $parent "CMD=".$postfwd_commands{countcache}.";TYPE=$counters" ++ .(($rulehits) ? $postfwd_settings{seplst}."CMD=".$postfwd_commands{matchcache}.";TYPE=$rulehits" : "") ++ ."\n"; $parent->getline(); ++ }; ++ $myaction = $postfwd_settings{default} if ($postfwd_settings{test} or !($myaction)); ++ map { &{$postfwd_settings{syslog}{logger}} ('info', " %$_") } hash_to_list ('Request_Cache', %Request_Cache) if wantsdebug (qw[ child_cache child_request_cache ]); ++ map { &{$postfwd_settings{syslog}{logger}} ('info', " %$_") } hash_to_list ('Rate_Cache', %Rate_Cache) if wantsdebug (qw[ child_cache child_rate_cache ]); ++ map { &{$postfwd_settings{syslog}{logger}} ('info', " %$_") } hash_to_list ('DNS_Cache', %DNS_Cache) if wantsdebug (qw[ child_cache child_dns_cache ]); ++ return $myaction; ++}; ++ ++ ++## Net::Server::PreFork methods ++ ++# ignore syslog failures ++sub handle_syslog_error {}; ++ ++# reload config on HUP signal ++sub sig_hup { ++ my $self = shift; ++ log_note ("catched HUP signal - reloading ruleset on next request"); ++ read_config(1); ++ map { kill ("HUP", $_) } (keys %{$self->{server}->{children}}); ++}; ++ ++# parent start ++sub pre_loop_hook { ++ my $self = shift; ++ # change parent's name ++ $0 = $self->{server}->{commandline} = " ".$postfwd_settings{name}.'::policy'; ++ $self->{server}->{syslog_ident} = $postfwd_settings{name}."/policy"; ++ $StartTime = $Summary = $Cleanup_Timeouts = $Cleanup_Requests = $Cleanup_RBLs = $Cleanup_Rates = time(); ++ init_log ($self->{server}->{syslog_ident}); ++ read_config(1); ++ log_info ("ready for input"); ++}; ++ ++# increase counters ++sub count_cache { map { $Count{$1} += $2 if m/$postfwd_patterns{cntval}/ } (split ($postfwd_settings{sepreq}, $_[1])) if $_[1] }; ++ ++# increase matches ++sub match_cache { map { $Hits{$_}++ } (split ($postfwd_settings{sepreq}, $_[1])) if $_[1] }; ++ ++# program usage statistics ++sub list_stats { ++ my $now = time(); ++ my $uptime = $now - $StartTime; ++ my @output =(); ++ return @output unless $uptime and $Count{request}; ++ ++ # averages, hitrates and counters ++ map { $Count{$_} ||= 0 } qw(ruleset interval top rate pcache ccache dnsqueries dnstimeouts dnsinterval dnstop); ++ my $lastreq = (($now - $Summary) > 0) ? $Count{interval} / ($now - $Summary) * 60 : 0; ++ my $lastdns = (($now - $Summary) > 0) ? $Count{dnsinterval} / ($now - $Summary) * 60 : 0; ++ $Count{top} = $lastreq if $lastreq > $Count{top}; ++ $Count{dnstop} = $lastdns if $lastdns > $Count{dnstop}; ++ my $dnstimeoutrate = ($Count{dnsqueries}) ? $Count{dnstimeouts} / $Count{dnsqueries} * 100 : 0; ++ ++ # log program statistics ++ if ( not($postfwd_settings{syslog}{noidlestats}) or ($Count{interval} > 0) ) { ++ push ( @output, sprintf ( ++ "[STATS] %s::policy %s: %d requests since %d days, %02d:%02d:%02d hours", ++ $postfwd_settings{name}, ++ $postfwd_settings{version}, ++ $Count{request}, ++ ($uptime / 60 / 60 / 24), ++ (($uptime / 60 / 60) % 24), ++ (($uptime / 60) % 60), ++ ($uptime % 60) ++ ) ); ++ ++ push ( @output, sprintf ( ++ "[STATS] Requests: %.2f/min last, %.2f/min overall, %.2f/min top", ++ $lastreq, ++ ($uptime) ? $Count{request} / $uptime * 60 : 0, ++ $Count{top} ++ ) ); ++ ++ push ( @output, sprintf ( ++ "[STATS] Dnsstats: %.2f/min last, %.2f/min overall, %.2f/min top", ++ $lastdns, ++ ($uptime) ? $Count{dnsqueries} / $uptime * 60 : 0, ++ $Count{dnstop} ++ ) ) unless ($postfwd_settings{dns}{disable}); ++ ++ push ( @output, sprintf ( ++ "[STATS] Hitrates: %.1f%% ruleset, %.1f%% parent, %.1f%% child, %.1f%% rates", ++ ($Count{request}) ? $Count{ruleset} / $Count{request} * 100 : 0, ++ ($Count{request}) ? $Count{pcache} / $Count{request} * 100 : 0, ++ ($Count{request}) ? $Count{ccache} / $Count{request} * 100 : 0, ++ ($Count{request}) ? $Count{rate} / $Count{request} * 100 : 0 ++ ) ); ++ ++ push ( @output, sprintf ( ++ "[STATS] Timeouts: %.1f%% (%d of %d dns queries)", ++ $dnstimeoutrate, ++ $Count{dnstimeouts}, ++ $Count{dnsqueries} ++ ) ) unless ($postfwd_settings{dns}{disable}); ++ ++ # per rule stats ++ if (%Hits and not($postfwd_settings{syslog}{norulestats})) { ++ my @rulecharts = (sort { $Hits{$b} <=> $Hits{$a} } (keys %Hits)); my $cntln = length($Hits{$rulecharts[0]}) + 2; ++ map { push ( @output, sprintf ("[STATS] %".$cntln."d matches for id: %s", $Hits{$_}, $_)) } @rulecharts; ++ }; ++ }; ++ ++ $Count{interval} = $Count{dnsinterval} = 0; ++ $Summary = $now; ++ return @output; ++}; ++ ++# parent processes child input ++sub child_is_talking_hook { ++ my($self,$sock) = @_; ++ my $answer = "\n"; ++ my $msg = $sock->getline(); ++ # during tests it turned out that children ++ # send empty messages in some situations ++ if (defined $msg) { ++ log_info ("child said '$msg'") if wantsdebug (qw[ all ]); ++ if ($msg =~ m/$postfwd_patterns{command}/) { ++ foreach (split $postfwd_settings{seplst}, $msg) { ++ if (m/$postfwd_patterns{countcache}/) { ++ $self->count_cache($1); ++ } elsif (m/$postfwd_patterns{matchcache}/) { ++ $self->match_cache($1); ++ } elsif (m/$postfwd_patterns{dumpstats}/) { ++ $answer = (join $postfwd_settings{sepreq}.$postfwd_settings{seplst}, list_stats())."\n"; ++ } else { ++ log_note ("warning: child sent unknown command '$_'"); ++ }; ++ }; ++ } else { ++ log_note ("warning: child sent unknown message '$msg'"); ++ }; ++ }; ++ print $sock "$answer"; ++}; ++ ++# child start ++sub child_init_hook { ++ my $self = shift; ++ # change children's names ++ $0 = $self->{server}->{commandline} = " ".$postfwd_settings{name}.'::policy::child'; ++ log_info ("ready for input") if wantsdebug (qw[ all verbose ]); ++}; ++ ++# child process request ++sub process_request { ++ my($self) = shift; ++ my($client) = $self->{server}->{client}; ++ my($parent) = $self->{server}->{parent_sock}; ++ my(%attr) = (); ++ while () { ++ s/\r?\n$//; ++ # respond to masters ping ++ if ($_ eq $postfwd_patterns{ping}) { ++ $client->print("$postfwd_patterns{pong}\n"); ++ } elsif (m/$postfwd_patterns{dumpstats}/) { ++ $parent->print("$_\n"); ++ $client->print($parent->getline()."\n"); ++ # process input ++ } else { ++ process_input ($parent, $client, $_, \%attr); ++ }; ++ }; ++}; ++ ++# process delegation protocol input ++sub process_input { ++ my($parent,$client,$msg,$attr) = @_; ++ # remember argument=value ++ if ( $msg =~ /^([^=]{1,512})=(.{0,512})/ ) { ++ $$attr{$1} = $2; ++ # evaluate request ++ } elsif ( $msg eq '' ) { ++ map { log_info ("Attribute: $_=$$attr{$_}") } (keys %$attr) if wantsdebug (qw[ all request ]); ++ unless ( (defined $$attr{request}) and ($$attr{request} eq "smtpd_access_policy") ) { ++ log_note ("Ignoring unrecognized request type: '".((defined $$attr{request}) ? substr($$attr{request},0,100) : '')."'"); ++ } else { ++ my $action = smtpd_access_policy($parent, %$attr) || $postfwd_settings{default}; ++ log_info ("Action: $action") if wantsdebug (qw[ all verbose ]); ++ if ($client) { ++ print $client ("action=$action\n\n"); ++ } else { ++ print STDOUT ("action=$action\n\n"); ++ }; ++ %$attr = (); ++ }; ++ # unknown command ++ } else { ++ log_note ("Ignoring garbage '".substr($msg, 0, 100)."'"); ++ }; ++}; ++ ++1; # EOF postfwd2::server ++ ++ ++use warnings; ++use strict; ++use Getopt::Long 2.25 qw(:config no_ignore_case bundling); ++use Pod::Usage; ++# master daemon ++use Net::Server::Daemonize qw(daemonize); ++# own modules ++# program settings, syslogging ++import postfwd2::basic qw(:DEFAULT %postfwd_commands &check_inet &check_unix &wantsdebug &hash_to_list); ++# cache daemon (requests, dns, limits), Net::Server::Multiplex ++import postfwd2::cache qw(); ++# policy daemon, Net::Server::PreFork ++import postfwd2::server qw(&read_config &show_config &process_input); ++ ++# functions to start, override with '--daemons' at command line ++my @daemons = qw[ cache server ]; ++ ++use vars qw( ++ %options %children %failures ++); ++ ++# parse command-line ++GetOptions( \%options, ++ # Ruleset ++ 'rule|r=s' => sub{ my($opt,$value) = @_; push (@{$postfwd_settings{Configs}}, $opt.$postfwd_settings{sepreq}.$value) }, ++ 'file|f=s' => sub{ my($opt,$value) = @_; push (@{$postfwd_settings{Configs}}, $opt.$postfwd_settings{sepreq}.$value) }, ++ 'scores|score|s=s%' => \%{$postfwd_settings{scores}}, ++ "test|t" => \$postfwd_settings{test}, ++ "instantcfg|I" => \$postfwd_settings{instant}, ++ "config_timeout=i" => \$postfwd_settings{timeout}{config}, ++ "showconfig|C", ++ "defaults|settings|D", ++ # Networking ++ "user|u=s" => \$postfwd_settings{base}{user}, ++ "group|g=s" => \$postfwd_settings{base}{group}, ++ "server_socket|socket=s" => sub{ ($postfwd_settings{server}{proto}, $postfwd_settings{server}{host}, $postfwd_settings{server}{port}) = (split ':', $_[1]) }, ++ "interface|i=s" => \$postfwd_settings{server}{host}, ++ "port|p=s" => \$postfwd_settings{server}{port}, ++ "proto=s" => \$postfwd_settings{server}{proto}, ++ "min_servers=i" => \$postfwd_settings{server}{min_servers}, ++ "max_servers=i" => \$postfwd_settings{server}{max_servers}, ++ "min_spare_servers=i" => \$postfwd_settings{server}{min_spare_servers}, ++ "max_spare_servers=i" => \$postfwd_settings{server}{max_spare_servers}, ++ "nodns|n" => \$postfwd_settings{dns}{disabled}, ++ "dns_timeout=i" => \$postfwd_settings{dns}{timeout}, ++ "dns_async_txt" => \$postfwd_settings{dns}{async_txt}, ++ "dns_timeout_max=i" => \$postfwd_settings{dns}{max_timeout}, ++ "dns_timeout_interval=i" => \$postfwd_settings{dns}{max_interval}, ++ "dns_max_ns_lookups=i" => \$postfwd_settings{dns}{max_ns_lookups}, ++ "dns_max_mx_lookups=i" => \$postfwd_settings{dns}{max_mx_lookups}, ++ "cache-rbl-timeout=i" => \$postfwd_settings{dns}{ttl}, ++ "cache-rbl-default=s" => \$postfwd_settings{dns}{mask}, ++ "cleanup-rbls=i" => \$postfwd_settings{dns}{cleanup}, ++ "no_parent_dns_cache" => \$postfwd_settings{dns}{noparent}, ++ "parent_dns_cache" => sub { $postfwd_settings{dns}{noparent} = 0 }, ++ # Stats ++ "summary|stats|S=i" => \$postfwd_settings{summary}, ++ "norulestats" => \$postfwd_settings{syslog}{norulestats}, ++ "no-rulestats" => \$postfwd_settings{syslog}{norulestats}, ++ "noidlestats" => \$postfwd_settings{syslog}{noidlestats}, ++ "no-idlestats" => \$postfwd_settings{syslog}{noidlestats}, ++ "stdoutlog|stdout|L" => \$postfwd_settings{syslog}{stdout}, ++ # Cache ++ "cache_socket=s" => sub{ ($postfwd_settings{cache}{proto}, $postfwd_settings{cache}{host}, $postfwd_settings{cache}{port}) = (split ':', $_[1]) }, ++ "cache_interface=s" => \$postfwd_settings{cache}{host}, ++ "cache_port=s" => \$postfwd_settings{cache}{port}, ++ "cache_proto=s" => \$postfwd_settings{cache}{proto}, ++ "cache|c=i" => \$postfwd_settings{request}{ttl}, ++ "cacheid=s" => sub { push @{$postfwd_settings{cacheid}}, (split /[,\s]+/, $_[1]) }, ++ "cache-rdomain-only" => \$postfwd_settings{request}{rdomain_only}, ++ "cache-no-sender" => \$postfwd_settings{request}{no_sender}, ++ "cache-no-size" => \$postfwd_settings{request}{no_size}, ++ "cleanup-requests=i" => \$postfwd_settings{request}{cleanup}, ++ "no_parent_request_cache" => \$postfwd_settings{request}{noparent}, ++ "no_parent_rate_cache" => \$postfwd_settings{rate}{noparent}, ++ "no_parent_cache" => sub{ $postfwd_settings{request}{noparent} = $postfwd_settings{rate}{noparent} = $postfwd_settings{dns}{noparent} = 1 }, ++ # Limits ++ "cleanup-rates=i" => \$postfwd_settings{rate}{cleanup}, ++ # Control ++ 'version|V' => sub{ print "$postfwd_settings{name} $postfwd_settings{version} (Net::DNS ".(Net::DNS->VERSION || '').", Net::Server ".(Net::Server->VERSION || '').", Sys::Syslog ".($Sys::Syslog::VERSION || '').", Perl ".$]." on ".$^O.")\n"; exit 1; }, ++ 'manual|m' => sub{ # contructing command string (de-tainting $0) ++ $postfwd_settings{manual} .= ($0 =~ /^([-\@\/\w. ]+)$/) ? " \"".$1 : " \"".$postfwd_settings{name}; ++ $postfwd_settings{manual} .= "\" | ".$postfwd_settings{pager}; ++ system ($postfwd_settings{manual}); exit 1; }, ++ "term|kill|stop|k", ++ "hup|reload", ++ "dumpcache", ++ "dumpstats", ++ "pid|pidfile|pid_file=s" => \$postfwd_settings{master}{pid_file}, ++ "watchdog=i" => \$postfwd_settings{master}{watchdog}, ++ "respawn=i" => \$postfwd_settings{master}{respawn}, ++ "failures=i" => \$postfwd_settings{master}{failures}, ++ "daemon|d!" => \$postfwd_settings{daemon}, ++ "daemons=s" => sub { push @{$options{daemons}}, (split /[,\s]+/, $_[1]) }, ++ # Logging ++ "debug=s" => sub { push @{$options{debug}}, (split /[,\s]+/, $_[1]) }, ++ "verbose|v+" => \$postfwd_settings{verbose}, ++ "logname|l=s" => sub{ $postfwd_settings{name} = $_[1]; ++ $postfwd_settings{cache}{syslog_ident} = $_[1].'/cache'; ++ $postfwd_settings{server}{syslog_ident} = $_[1].'/policy'; }, ++ "facility=s" => \$postfwd_settings{base}{syslog_facility}, ++ "nodnslog" => \$postfwd_settings{dns}{nolog}, ++ "no-dnslog" => \$postfwd_settings{dns}{nolog}, ++ "anydnslog" => \$postfwd_settings{dns}{anylog}, ++ "norulelog" => \$postfwd_settings{request}{nolog}, ++ "no-rulelog" => \$postfwd_settings{request}{nolog}, ++ "perfmon|P" => \$postfwd_settings{syslog}{nolog}, ++ # Unused ++ "start", ++ "chroot|R=s", ++ "shortlog", ++ "dns_queuesize=i", ++ "dns_retries=i", ++) or pod2usage (-msg => "\nPlease see \"".$postfwd_settings{name}." -m\" for detailed instructions.\n", -verbose => 1); ++ ++map { $postfwd_settings{syslog}{stdout} = 1 if defined $options{$_} } qw(term hup showconfig dumpcache dumpstats defaults); ++ ++# basic syntax checks ++if ($postfwd_settings{verbose} > 1) { ++ $postfwd_settings{debug}{all} = 1; ++} elsif ($postfwd_settings{verbose}) { ++ $postfwd_settings{debug}{verbose} = 1; ++}; ++map { $postfwd_settings{debug}{$_} = 1 } uniq(@{$options{debug}}); ++map { $postfwd_settings{daemons}{$_} = 1 } ((defined $options{daemons}) ? uniq(@{$options{daemons}}) : uniq(@daemons)); ++map { $postfwd_settings{$_}{check} = ($postfwd_settings{$_}{proto} eq 'unix') ? \&check_unix : \&check_inet } @daemons; ++ ++# terminate at -k or --kill ++if (defined $options{'term'}) { ++ kill "TERM", get_master_pid(); ++ exit (0); ++# reload at --reload ++} elsif (defined $options{'hup'}) { ++ kill "HUP", get_master_pid(); ++ exit (0); ++}; ++ ++# init_log ++init_log ($postfwd_settings{name}."/master"); ++ ++# read and display configuration ++if (defined $options{'showconfig'}) { ++ read_config(1); ++ show_config(); ++ exit 1; ++}; ++ ++# show program settings ++if (defined $options{'defaults'}) { ++ print "\n"; map { print " %$_\n" } hash_to_list ('postfwd_settings', %postfwd_settings); ++ if (wantsdebug (qw[ all verbose ])) { ++ map { print " %$_\n" } hash_to_list ('postfwd_commands', %postfwd_commands); ++ map { print " %$_\n" } hash_to_list ('postfwd_patterns', %postfwd_patterns); ++ }; ++ print "\n"; exit 1; ++}; ++ ++# dump stats ++if (defined $options{'dumpstats'}) { ++ foreach my $daemon (sort keys %{$postfwd_settings{daemons}}) { ++ print "\n"; ++ map { print ("$_\n") } get_stats ($daemon); ++ }; ++ print "\n"; ++ exit 1; ++}; ++ ++# dump cache contents ++if (defined $options{'dumpcache'}) { ++ print "\n".( join "\n", ++ split $postfwd_settings{sepreq}.$postfwd_settings{seplst}, ++ (&{$postfwd_settings{cache}{check}} ('cache', 'CMD=DC;') || '') ++ )."\n\n"; ++ exit 1; ++}; ++ ++# de-taint command-line ++%postfwd_settings = detaint_hash (%postfwd_settings); ++ ++# check for --nodaemon option ++unless ($postfwd_settings{daemon}) { ++ my(%attr) = (); ++ read_config(1); ++ map { $postfwd_settings{daemons}{$_} = 0 } (keys %{$postfwd_settings{daemons}}); ++ $postfwd_settings{request}{noparent} = $postfwd_settings{rate}{noparent} = $postfwd_settings{dns}{noparent} = 1; ++ while (<>) { ++ chomp; ++ process_input (undef, undef, $_, \%attr); ++ }; ++ exit; ++}; ++ ++# daemonize master ++my $arg0 = $0; my $argv = join (' ', @ARGV); ++log_info ($postfwd_settings{name}." " ++ .$postfwd_settings{version}." starting" ++ .((scalar keys %{$postfwd_settings{debug}}) ? " with debug levels: ".(join ',', keys %{$postfwd_settings{debug}}) : '')); ++log_info ("Net::DNS ".(Net::DNS->VERSION || '').", Net::Server ".(Net::Server->VERSION || '').", Sys::Syslog ".($Sys::Syslog::VERSION || '').", Perl ".$]." on ".$^O) if wantsdebug (qw[ all verbose ]); ++daemonize($postfwd_settings{base}{user}, $postfwd_settings{base}{group}, $postfwd_settings{master}{pid_file}); ++$0 = $arg0." ".$argv; ++ ++# prepare shared SIG handlers ++$SIG{__WARN__} = sub { log_warn("warning: $_[0]") }; ++$SIG{__DIE__} = sub { log_crit("FATAL: $_[0]"); die @_; }; ++ ++# fork daemons: cache and server ++foreach my $daemon (sort keys %{$postfwd_settings{daemons}}) { ++ if (my $pid = spawn_daemon ($daemon)) { ++ log_info ("Started $daemon at pid $pid"); ++ $children{$daemon} = $pid; ++ }; ++}; ++ ++# prepare master SIG handlers and enter main loop ++$SIG{TERM} = sub { end_program(); }; ++$SIG{HUP} = sub { reload_program(); }; ++if ($postfwd_settings{summary}) { ++ $SIG{ALRM} = sub { ++ log_stats(); ++ alarm ($postfwd_settings{summary}) ++ }; ++ alarm ($postfwd_settings{summary}); ++}; ++ ++while (1) { ++ # check daemons every seconds ++ if ($postfwd_settings{master}{watchdog}) { ++ sleep ($postfwd_settings{master}{watchdog}); ++ foreach my $daemon (sort keys %{$postfwd_settings{daemons}}) { ++ if (check_daemon ($daemon)) { ++ $failures{$daemon} = 0; ++ } else { ++ if (++$failures{$daemon} >= $postfwd_settings{master}{failures}) { ++ # terminate program ++ log_crit ("$daemon-daemon check failed $failures{$daemon} times - terminating program"); ++ end_program(); ++ } else { ++ # restart daemon ++ log_crit ("$daemon-daemon check failed $failures{$daemon} times - respawning in ".$postfwd_settings{master}{respawn}." seconds"); ++ kill 15, $children{$daemon}; sleep $postfwd_settings{master}{respawn}; ++ if (my $pid = spawn_daemon ($daemon)) { ++ log_info ("Started $daemon at pid $pid"); ++ $children{$daemon} = $pid; ++ }; ++ }; ++ }; ++ }; ++ # no watchdog -> sleep until signal ++ } else { ++ sleep; ++ }; ++}; ++die "master-daemon: should never see me!\n"; ++ ++ ++## SUBS ++ ++# cleanup children and files and terminate ++sub end_program { ++ local $SIG{TERM} = 'IGNORE'; ++ if ($postfwd_settings{summary}) { ++ undef $postfwd_settings{syslog}{noidlestats}; ++ log_stats(); ++ }; ++ log_note ($postfwd_settings{name}." ".$postfwd_settings{version}." terminating..."); ++ unlink $postfwd_settings{master}{pid_file} if (-T $postfwd_settings{master}{pid_file}); ++ # negative signal no. kills the whole process group ++ kill -15, $$; ++ exit (0); ++}; ++ ++# send hup to child processes ++sub reload_program { ++ log_note ($postfwd_settings{name}." ".$postfwd_settings{version}." reloading..."); ++ map { kill 1, $_ } (values %children) if %children; ++}; ++ ++# check a cache or server daemon ++sub check_daemon { return ((&{$postfwd_settings{$_[0]}{check}}($_[0],$postfwd_patterns{ping}) || '') eq $postfwd_patterns{pong}) }; ++ ++# spawn a cache or server daemon ++sub spawn_daemon { ++ my ($type) = @_; ++ my $pid = fork(); ++ die "Can not fork $type: $!\n" unless defined $pid; ++ if ($pid == 0) { ++ my %service = %{$postfwd_settings{$type}}; ++ # Net::Server dies when a unix domain socket without dot "." is used ++ $service{port} .= '|unix' if (($service{proto} eq 'unix') and not($service{port} =~ /\|unix$/)); ++ my %daemonopts = (%{$postfwd_settings{base}}, %service); ++ my $daemon = bless { server => { %daemonopts } }, "postfwd2::$type"; ++ $daemon->run(); ++ die "$type-daemon: should never see me!\n"; ++ }; ++ return $pid; ++}; ++ ++# get pid of running master process ++sub get_master_pid { ++ (-e $postfwd_settings{master}{pid_file}) or die $postfwd_settings{name}.": Can not find pid_file ".$postfwd_settings{master}{pid_file}.": $!\n"; ++ (-T $postfwd_settings{master}{pid_file}) or die $postfwd_settings{name}.": Can not open pid_file ".$postfwd_settings{master}{pid_file}.": not a textfile\n"; ++ open PIDFILE, "<".$postfwd_settings{master}{pid_file} or die $postfwd_settings{name}.": Can open pid_file ".$postfwd_settings{master}{pid_file}.": $!\n"; ++ my $pid = ; ++ ($pid =~ m/^(\d+)$/) or die $postfwd_settings{name}.": Invalid pid_file content '$pid' (pid_file ".$postfwd_settings{master}{pid_file}.")\n"; ++ return $1; ++}; ++ ++# detaints postfwd2 settings ++sub detaint_hash { ++ my (%request) = @_; ++ # cycle through key=value pairs ++ while ( my($s, $v) = each %request ) { ++ my $r = ref $v; ++ # type hash: recursively call ourself ++ if ($r eq 'HASH') { ++ %{$v} = detaint_hash ( %{$v} ); ++ # type array: detaint whole list ++ } elsif ($r eq 'ARRAY') { ++ @{$request{$s}} = map { $_ = (($_ =~ m/^(.*)$/) ? $1 : $_ ) if $_ } @{$v}; ++ # type scalar: detaint argument ++ } elsif ($r eq '') { ++ $request{$s} = (($v =~ m/^(.*)$/) ? $1 : $v) if ($s and $v); ++ }; ++ }; ++ return %request; ++}; ++ ++# send stats to syslog ++sub log_stats { map { &{$postfwd_settings{syslog}{logger}} ('notice', "$_") unless ($_ eq '') } get_stats(sort keys %{$postfwd_settings{daemons}}); }; ++ ++# retrieve status from children ++sub get_stats { ++ my @daemons = @_; my @output = (); ++ map { push @output, (split $postfwd_settings{sepreq}.$postfwd_settings{seplst}, (&{$postfwd_settings{$_}{check}} ($_, 'CMD=DS;') || '')) } @daemons; ++ return @output; ++}; ++ ++# EOF postfwd2 ++ ++__END__ ++ ++=head1 NAME ++ ++postfwd2 - postfix firewall daemon ++ ++=head1 SYNOPSIS ++ ++B [OPTIONS] [SOURCE1, SOURCE2, ...] ++ ++ Ruleset: (at least one, multiple use is allowed): ++ -f, --file reads rules from ++ -r, --rule adds to config ++ -s, --scores = returns when score exceeds ++ ++ Server: ++ -i, --interface listen on interface ++ -p, --port listen on port ++ --proto socket type (tcp or unix) ++ --server_socket e.g. tcp:127.0.0.1:10045 ++ -u, --user set uid to user ++ -g, --group set gid to group ++ --pidfile create pidfile under ++ --min_servers spawn at least children ++ --max_servers do not spawn more than children ++ --min_spare_servers minimum idle children ++ --max_spare_servers maximum idle children ++ ++ Cache: ++ -c, --cache sets the request-cache timeout to seconds ++ --cleanup-requests cleanup interval in seconds for request cache ++ --cache_interface listen on interface ++ --cache_port listen on port ++ --cache_proto socket type (tcp or unix) ++ --cache_socket e.g. tcp:127.0.0.1:10043 ++ --cacheid list of request items for cache-id ++ --cache-rdomain-only skip recipient localpart for cache-id ++ --cache-no-sender skip sender address for cache-id ++ --cache-no-size skip size for cache-id ++ --no_parent_request_cache disable parent request cache ++ --no_parent_rate_cache disable parent rate cache ++ --no_parent_dns_cache disable parent dns cache ++ --no_parent_cache disable all parent caches ++ ++ Rates: ++ --cleanup-rates cleanup interval in seconds for rate cache ++ ++ Control: ++ -k, --kill, --stop terminate postfwd2 ++ --reload, --hup reload postfwd2 ++ --watchdog watchdog timer in seconds ++ --respawn respawn delay in seconds ++ --failures max respawn failure counter ++ --daemons list of daemons to start ++ --dumpcache show cache contents ++ --dumpstats show statistics ++ ++ DNS: ++ -n, --nodns skip any dns based test ++ --dns_timeout dns query timeout in seconds ++ --dns_timeout_max disable dnsbl after timeouts ++ --dns_timeout_interval reenable dnsbl after seconds ++ --cache-rbl-timeout default dns ttl if not specified in ruleset ++ --cache-rbl-default default dns pattern if not specified in ruleset ++ --cleanup-rbls cleanup old dns cache items every seconds ++ --dns_async_txt perform dnsbl A and TXT lookups simultaneously ++ --dns_max_ns_lookups max names to look up with sender_ns_addrs ++ --dns_max_mx_lookups max names to look up with sender_mx_addrs ++ ++ Optional: ++ -t, --test testing, always returns "dunno" ++ -S, --summary show stats every seconds ++ --noidlestats disables statistics when idle ++ --norulestats disables per rule statistics ++ -I, --instantcfg reloads ruleset on every new request ++ --config_timeout parser timeout in seconds ++ ++ Logging: ++ -l, --logname