diff --git a/bin/postfwd-script.sh b/bin/postfwd-script.sh
index 199a50d..f6308da 100755
--- a/bin/postfwd-script.sh
+++ b/bin/postfwd-script.sh
@@ -10,6 +10,8 @@ PATH=/bin:/usr/bin:/usr/local/bin
PFWCMD=/usr/local/postfwd/sbin/postfwd
# rulesetconfig file
PFWCFG=/etc/postfix/postfwd.cf
+# pidfile
+PFWPID=/var/tmp/postfwd.pid
# daemon settings
PFWUSER=nobody
@@ -23,45 +25,32 @@ PFWARG="--shortlog --summary=600 --cache=600 --cache-rbl-timeout=3600 --cleanup-
## should be no need to change below
-P1="`basename ${PFWCMD}`"; P2="`basename $0`";
-PIDS="`ps -aef | grep "${P1}" | grep -v "${P2}" | grep -v grep | awk '{print $2}' | sort -nr`"
-
+P1="`basename ${PFWCMD}`"
case "$1" in
- start*) if [ -n "${PIDS}" ]; then
- echo "Process called \"${P1}\" already found at PID ${PIDS}. Please use \"${P2} restart\" instead." ;
- false;
- else
- echo "Starting ${P1}...";
- ${PFWCMD} ${PFWARG} --daemon --file=${PFWCFG} --interface=${PFWINET} --port=${PFWPORT} --user=${PFWUSER} --group=${PFWGROUP};
- fi ;
+ start*) echo "Starting ${P1}...";
+ ${PFWCMD} ${PFWARG} --daemon --file=${PFWCFG} --interface=${PFWINET} --port=${PFWPORT} --user=${PFWUSER} --group=${PFWGROUP} --pidfile=${PFWPID};
;;
- debug*) if [ -n "${PIDS}" ]; then
- echo "Process called \"${P1}\" already found at PID ${PIDS}. Please use \"${P2} restart\" instead." ;
- false;
- else
- echo "Starting ${P1} in DEBUG mode...";
- ${PFWCMD} ${PFWARG} -vv --daemon --file=${PFWCFG} --interface=${PFWINET} --port=${PFWPORT} --user=${PFWUSER} --group=${PFWGROUP};
- fi ;
- ;;
+ debug*) echo "Starting ${P1} in debug mode...";
+ ${PFWCMD} ${PFWARG} -vv --daemon --file=${PFWCFG} --interface=${PFWINET} --port=${PFWPORT} --user=${PFWUSER} --group=${PFWGROUP} --pidfile=${PFWPID};
+ ;;
-
- stop*) if [ -z "${PIDS}" ]; then
- echo "No process called \"${P1}\" found" ;
- false;
- else
+ stop*) if [ -f "${PFWPID}" ]; then
echo "Stopping ${P1}...";
- for pid in ${PIDS}; do kill ${pid}; done ;
+ kill `cat ${PFWPID}`;
+ else
+ echo "Pidfile \"${PFWPID}\" not found" ;
+ false;
fi ;
;;
- reload*) if [ -z "${PIDS}" ]; then
- echo "No process called \"${P1}\" found" ;
- false;
+ reload*) if [ -f "${PFWPID}" ]; then
+ echo "Stopping ${P1}...";
+ kill -HUP `cat ${PFWPID}`;
else
- echo "Refreshing ${P1}...";
- for pid in ${PIDS}; do kill -HUP ${pid}; done ;
+ echo "Pidfile \"${PFWPID}\" not found" ;
+ false;
fi ;
;;
@@ -71,7 +60,7 @@ case "$1" in
;;
*) echo "Unknown argument \"$1\"" >&2;
- echo "Usage: ${P2} {start|stop|reload|restart}" >&2;
+ echo "Usage: `basename $0` {start|stop|reload|restart}" >&2;
exit 1;;
esac
exit $?
diff --git a/doc/CHANGELOG b/doc/CHANGELOG
index 38b8c8a..e301ca9 100644
--- a/doc/CHANGELOG
+++ b/doc/CHANGELOG
@@ -1,12 +1,43 @@
+
**************************************************************************************************
-ATTENTION: requirements changed - as dns queries are now performed asynchronously, postfwd from
- v1.10pre2 and above needs the perl module Net::DNS::Async! it is available via CPAN
- and installed for my tests without any problems on different linux and solaris systems
+ATTENTION: requirements changed - postfwd since v1.10pre8 now uses Net::DNS.
+ Net::DNS::Async and Net::CIDR::Lite are not required anymore.
NOTE: please see the docs ('postfwd -m' or 'perldoc postfwd') for more information
**************************************************************************************************
+1.10pre8b
+==========
+- bugfix: fixed two warnings about logging of undefined values in verbose mode
+
+1.10pre8a
+==========
+- bugfix: item plugins have been made available as cache-id items. this fixes a minor issue with
+ --cache-rdomain-only and version 1.10pre8
+
+1.10pre8
+=========
+- code: Net::DNS::Async is no longer used. The parameters --dns_queuesize and
+ --dns_retries are still valid but have no function. The option --dns_timeout
+ now defaults to 14s and applies to all rules containing dns items.
+- code: Net::CIDR::Lite is not required any longer.
+- feature: the new variable $$request_hits contains a list of all matching ruleids
+- feature: the new variable $$dnsbltext allows access to txt records of rbls
+- feature: new options --no-rulestats and --nodnslog
+- feature: ttls of the dns responses override --cache-rbl-timeout when bigger, which means
+ that you can set the option to 0 if you want to use the ttl of the dns answer.
+- feature: new item "rhsbl_helo" allows to check helo against rhsbls
+- bugfix: disabled fallback to synchronous dns on timed out rbls, default is now
+ to disable non responding dnsbls after 11 timeouts for 1200 seconds.
+ use --dns_timeout_max and --dns_timeout_interval to adjust these settings.
+- bugfix: days=Wed now means exactly Wednesday. to use a range you may
+ still specify days=Wed- days=-Wed and days=Tue-Thu
+ this applies to all date and time items
+- code: --shortlog is now default behaviour (use -v to see more)
+- code: changed Net::Server behaviour to ignore syslog errors
+
+
1.10pre7c
==========
- note: 1.10pre7c does not contain any code-changes to the postfwd daemon.
@@ -177,3 +208,4 @@ NOTE: please see the docs ('postfwd -m' or 'perldoc postfwd') for more inf
=====
- first public beta version
+
diff --git a/doc/postfwd.html b/doc/postfwd.html
index ddb2e36..e9932b5 100644
--- a/doc/postfwd.html
+++ b/doc/postfwd.html
@@ -1,11 +1,15 @@
-
-
+
+
postfwd - postfix firewall daemon
-
+
+
+
+
+
-
+
@@ -22,6 +26,7 @@
ITEMS
ACTIONS
MACROS/ACLS
+ PLUGINS
COMMAND LINE
REFRESH
EXAMPLES
@@ -62,8 +67,9 @@
-u, --user <name> set uid to user <name>
-g, --group <name> set gid to group <name>
-R, --chroot <path> chroot the daemon to <path>
+ --pidfile <path> create pidfile under <path>
-l, --logname <label> label for syslog messages
- --pidfile <path> create pidfile under <path>
+ --loglen <int> truncates syslogs after <int> chars
Caching:
-c, --cache <int> sets the request-cache timeout to <int> seconds
@@ -80,23 +86,25 @@
Optional:
-t, --test testing, always returns "dunno"
-v, --verbose verbose logging, use twice (-vv) to increase level
- --shortlog disables logging of some postfwd commands
-S, --summary <int> show some usage statistics every <int> seconds
+ --no-rulestats disables per rule statistics
-n, --nodns disable dns
- --dns_queuesize sets the queue size for asynchonous dns queries
- --dns_retries how many retries for a single asynchonous dns query
+ --nodnslog disable dns logging
--dns_timeout timeout in seconds for asynchonous dns queries
--dns_timeout_max maximum of dns timeouts until a dnsbl will be deactivated
--dns_timeout_interval interval in seconds for dns timeout maximum counter
-I, --instantcfg re-reads rulefiles for every new request
- Informational (use only at command-line, not with postfix!):
+ Informational (use only at command-line!):
-C, --showconfig shows ruleset summary, -v for verbose
-L, --stdoutlog redirect syslog messages to stdout
-P, --perfmon no syslogging, no stdout
-V, --version shows program version
-h, --help shows usage
-m, --manual shows program manual
+
+ Plugins:
+ --plugins <file> loads plugins from <file>
@@ -161,7 +169,16 @@ arguments. Please see the COMMAND LINE section below for more information on thi
id - a unique rule id, which can be used for log analysis
ids also serve as targets for the "jump" command.
- date, time - a time or date range within the specified rule shall hit
+ date, time - a time or date range within the specified rule shall hit
+ # FORMAT:
+ # Feb, 29th
+ date=29.02.2008
+ # Dec, 24th - 26th
+ date=24.12.2008-26.12.2008
+ # from today until Nov, 23rd
+ date=-23.09.2008
+ # from April, 1st until today
+ date=01.04.2008-
days, months - a range of weekdays (Sun-Sat) or months (Jan-Dec)
within the specified rule shall hit
@@ -354,20 +371,25 @@ rule containing only an action statement:
rblcount - contains the number of RBL answers
rhsblcount - contains the number of RHSBL answers
- matches - contains the number of matched items
-This means that you must save them, if you plan to use these values in later rules:
+ matches - contains the number of matched items
+ dnsbltext - contains the dns TXT part of all RBL and RHSBL replies in the form
+ rbltype:rblname:<txt>; rbltype:rblname:<txt>; ...
+These special attributes will be changed for any matching rule:
+
+ request_hits - contains ids of all matching rules
+This means that it might be necessary to save them, if you plan to use these values in later rules:
# set vals
id=RBL01 ; rhsblcount=all ; rblcount=all ; \
rbl=list.dsbl.org, bl.spamcop.net, dnsbl.sorbs.net, zen.spamhaus.org ; \
rhsbl_client=rddn.dnsbl.net.au, rhsbl.ahbl.org, rhsbl.sorbs.net ; \
rhsbl_sender=rddn.dnsbl.net.au, rhsbl.ahbl.org, rhsbl.sorbs.net ; \
- action=set(HIT_rhls=$$rhsblcount,HIT_rbls=$$rblcount)
+ action=set(HIT_rhls=$$rhsblcount,HIT_rbls=$$rblcount,HIT_txt=$$dnsbltext)
# compare
- id=RBL02 ; HIT_rhls>=1 ; HIT_rbls>=1 ; action=554 5.7.1 blocked using $$HIT_rhls RHSBLs and $$HIT_rbls RBLs
- id=RBL03 ; HIT_rhls>=2 ; action=554 5.7.1 blocked using $$HIT_rhls RHSBLs
- id=RBL04 ; HIT_rbls>=2 ; action=554 5.7.1 blocked using $$HIT_rbls RBLs
+ id=RBL02 ; HIT_rhls>=1 ; HIT_rbls>=1 ; action=554 5.7.1 blocked using $$HIT_rhls RHSBLs and $$HIT_rbls RBLs [INFO: $$HIT_txt]
+ id=RBL03 ; HIT_rhls>=2 ; action=554 5.7.1 blocked using $$HIT_rhls RHSBLs [INFO: $$HIT_txt]
+ id=RBL04 ; HIT_rbls>=2 ; action=554 5.7.1 blocked using $$HIT_rbls RBLs [INFO: $$HIT_txt]
@@ -409,6 +431,10 @@ First the macros have to be defined as follows:
Basically macros are simple text substitutions - see the PARSER section for more information.
+
+Please visit http://www.postfwd.org/postfwd.plugins
+
+
Ruleset
The following arguments are used to specify the source of the postfwd ruleset. This means
@@ -421,13 +447,18 @@ that at least one of the following is required for postfwd to work.
-r, --rule <rule>
Adds <rule> to ruleset. Remember that you might have to quote
strings that contain whitespaces or shell characters.
+Plugins
+
+ --plugins
+ A file containing plugin routines for postfwd. Please see the
+ PLUGINS section for more information.
Scoring
-s, --scores <val>=<action>
Returns <action> to postfix, when the request's score exceeds <val>
Multiple usage is allowed. Just chain your arguments, like:
- postfwd -r "<item>=<value>;action=<result>" -f <file> -f <file> ...
+ postfwd -r "<item>=<value>;action=<result>" -f <file> -f <file> --plugins <file> ...
or
postfwd --scores 4.5="WARN high score" --scores 5.0="REJECT postfwd score too high" ...
In case of multiple scores, the highest match will count. The order of the arguments will be
@@ -455,13 +486,16 @@ The following arguments will control it's behaviour in this case.
-R, --chroot <path>
Chroot the process to the specified path.
Test this before using - you might need some libs there.
+
+ --pidfile <path>
+ The process id will be saved in the specified file.
-l, --logname <label>
Labels the syslog messages. Useful when running multiple
instances of postfwd.
- --pidfile <path>
- The process id will be saved in the specified file.
+ --loglen <int>
+ Truncates any syslog message after <int> characters.
Optional arguments
These parameters influence the way postfwd is working. Any of them can be combined.
@@ -535,13 +569,13 @@ The following arguments will control it's behaviour in this case.
Aug 19 12:39:45 mail1 postfwd[666]: [STATS] Rule ID: R-002 matched: 9351 times
Aug 19 12:39:45 mail1 postfwd[666]: [STATS] Rule ID: R-003 matched: 3116 times
...
+
+ --no-rulestats
+ Disables per rule statistics. Keeps your log clean, if you do not use them.
+ This option has no effect without --summary or --verbose set.
-L, --stdoutlog
Redirects all syslog messages to stdout for debugging. Never use this with postfix!
-
- --shortlog
- As postfwd now logs all hits for a request, you might find it unecessary to log the
- postfwd actions jump(), set() and score(). You may disable it with this option.
-t, --test
In test mode postfwd always returns "dunno", but logs according
@@ -551,15 +585,10 @@ The following arguments will control it's behaviour in this case.
Disables all DNS based checks like RBL checks. Rules containing
such elements will be ignored.
- --dns_queuesize (default: 100)
- Sets the queue size for asynchonous dns queries. If the query exceeds this value,
- postfwd waits for answers of timeouts for previous queries.
+ -n, --nodnslog
+ Disables logging of dns events.
- --dns_retries (default: 3)
- Sets the retry counter for asynchonous dns queries. This value will apply to
- every single query.
-
- --dns_timeout (default: 7)
+ --dns_timeout (default: 14)
Sets the timeout for asynchonous dns queries in seconds. This value will apply to
all dns items in a rule.
@@ -723,11 +752,11 @@ the '-I' switch to have your configuration refreshed for every request postfwd r
...
};
&&MAINTENANCE { \
- date=15.01.2007 ; \
- date=15.04.2007 ; \
- date=15.07.2007 ; \
- date=15.10.2007 ; \
- time=03:00:00-04:00:00 ; \
+ date=15.01.2007 ; \
+ date=15.04.2007 ; \
+ date=15.07.2007 ; \
+ date=15.10.2007 ; \
+ time=03:00:00 - 04:00:00 ; \
};
# rules
id=COMBINED ; &&RBLS ; &&DYNAMIC ; action=REJECT dynamic client and listed on RBL
@@ -742,14 +771,11 @@ the '-I' switch to have your configuration refreshed for every request postfwd r
id=REJECT02 ; HIT_rbls==1 ; HIT_dyna==1 ; action=REJECT please see http://some.org/info?reject=02 for more info
id=REJECT03 ; HIT_helo==1 ; HIT_dyna==1 ; action=REJECT please see http://some.org/info?reject=03 for more info
- # combined with enhanced rbl features
- # set vals
+ ## combined with enhanced rbl features
+ #
id=RBL01 ; rhsblcount=all ; rblcount=all ; &&RBLS ; &&RHSBLS ; \
- action=set(HIT_rhls=$$rhsblcount,HIT_rbls=$$rblcount)
- # compare
- id=RBL02 ; HIT_rhls>=1 ; HIT_rbls>=1 ; action=554 5.7.1 blocked using $$HIT_rhls RHSBLs and $$HIT_rbls RBLs
- id=RBL03 ; HIT_rhls>=2 ; action=554 5.7.1 blocked using $$HIT_rhls RHSBLs
- id=RBL04 ; HIT_rbls>=2 ; action=554 5.7.1 blocked using $$HIT_rbls RBLs
+ action=set(HIT_dnsbls=$$rhsblcount,HIT_dnsbls+=$$rblcount,HIT_dnstxt=$$dnsbltext)
+ id=RBL02 ; HIT_dnsbls>=2 ; action=554 5.7.1 blocked using $$HIT_dnsbls DNSBLs [INFO: $$HIT_dnstxt]
@@ -794,7 +820,7 @@ verbority using use the ``-v'' or ``-vv'' switch. ``-L'' redirects log messages
id=R001; sender=bob@alice.local; client_address=192.168.1.1; action=dunno
Lists will be evaluated in the specified order. This allows to place faster expressions at first:
- postfwd -vv -L -r "id=RBL001; rbl=localrbl.local zen.spamhaus.org; action=REJECT" /root/request.sample
+ postfwd -vv -L -r "id=RBL001; rbl=localrbl.local zen.spamhaus.org; action=REJECT" /some/where/request.sample
produces the following
[LOGS info]: compare rbl: "remotehost.remote.net[68.10.1.7]" -> "localrbl.local"
@@ -810,7 +836,7 @@ verbority using use the ``-v'' or ``-vv'' switch. ``-L'' redirects log messages
[LOGS info]: Action: dunno
The negation operator !!(<value>) has the highest priority and therefore will be evaluated first. Then variable substitutions are performed:
- postfwd -vv -L -r "id=TEST; action=REJECT; client_name=!!($$heloname)" /root/request.sample
+ postfwd -vv -L -r "id=TEST; action=REJECT; client_name=!!($$heloname)" /some/where/request.sample
will give
[LOGS info]: compare client_name: "unknown" -> "!!($$helo_name)"
@@ -924,13 +950,14 @@ listening on the specified network settings.
Some of these proposals might not match your environment. Please check your requirements and test new options carefully!
-- use caching options
-- use the correct match operator ==, <=, >=
-- use ^ and $ in regular expressions
-- use item lists (faster than single rules)
-- use set()
action on repeated item lists
-- use jump action
-- use pre-lookup rule for rbl/rhsbls with empty note()
action
+
+ - use caching options
+ - use the correct match operator ==, <=, >=
+ - use ^ and/or $ in regular expressions
+ - use item lists (faster than single rules)
+ - use set() action on repeated item lists
+ - use jumps and rate limits
+ - use a pre-lookup rule for rbl/rhsbls with empty note() action
@@ -969,8 +996,17 @@ POSSIBILITY OF SUCH DAMAGE.
-Jan Peter Kessler <info (AT) postfwd (DOT) org>. Let me know, if you have any suggestions.
+Jan Peter Kessler <info (AT) postfwd (DOT) org>. Let me know, if you have any suggestions.
+
+
+
+ http://www.postfwd.org/doc.html
+ 2007 by Jan Peter Kessler
+ info (AT) postfwd (DOT) org
+
+
+
diff --git a/doc/postfwd.txt b/doc/postfwd.txt
index 178720d..4966add 100644
--- a/doc/postfwd.txt
+++ b/doc/postfwd.txt
@@ -18,8 +18,9 @@ SYNOPSIS
-u, --user set uid to user
-g, --group set gid to group
-R, --chroot chroot the daemon to
- -l, --logname label for syslog messages
--pidfile create pidfile under
+ -l, --logname label for syslog messages
+ --loglen truncates syslogs after chars
Caching:
-c, --cache sets the request-cache timeout to seconds
@@ -36,17 +37,16 @@ SYNOPSIS
Optional:
-t, --test testing, always returns "dunno"
-v, --verbose verbose logging, use twice (-vv) to increase level
- --shortlog disables logging of some postfwd commands
-S, --summary show some usage statistics every seconds
+ --no-rulestats disables per rule statistics
-n, --nodns disable dns
- --dns_queuesize sets the queue size for asynchonous dns queries
- --dns_retries how many retries for a single asynchonous dns query
+ --nodnslog disable dns logging
--dns_timeout timeout in seconds for asynchonous dns queries
--dns_timeout_max maximum of dns timeouts until a dnsbl will be deactivated
--dns_timeout_interval interval in seconds for dns timeout maximum counter
-I, --instantcfg re-reads rulefiles for every new request
- Informational (use only at command-line, not with postfix!):
+ Informational (use only at command-line!):
-C, --showconfig shows ruleset summary, -v for verbose
-L, --stdoutlog redirect syslog messages to stdout
-P, --perfmon no syslogging, no stdout
@@ -54,6 +54,9 @@ SYNOPSIS
-h, --help shows usage
-m, --manual shows program manual
+ Plugins:
+ --plugins loads plugins from
+
DESCRIPTION
INTRODUCTION
postfwd is written to combine complex postfix restrictions in a ruleset
@@ -138,6 +141,15 @@ DESCRIPTION
ids also serve as targets for the "jump" command.
date, time - a time or date range within the specified rule shall hit
+ # FORMAT:
+ # Feb, 29th
+ date=29.02.2008
+ # Dec, 24th - 26th
+ date=24.12.2008-26.12.2008
+ # from today until Nov, 23rd
+ date=-23.09.2008
+ # from April, 1st until today
+ date=01.04.2008-
days, months - a range of weekdays (Sun-Sat) or months (Jan-Dec)
within the specified rule shall hit
@@ -360,21 +372,27 @@ DESCRIPTION
rblcount - contains the number of RBL answers
rhsblcount - contains the number of RHSBL answers
matches - contains the number of matched items
+ dnsbltext - contains the dns TXT part of all RBL and RHSBL replies in the form
+ rbltype:rblname:; rbltype:rblname:; ...
- This means that you must save them, if you plan to use these values in
- later rules:
+ These special attributes will be changed for any matching rule:
+
+ request_hits - contains ids of all matching rules
+
+ This means that it might be necessary to save them, if you plan to use
+ these values in later rules:
# set vals
id=RBL01 ; rhsblcount=all ; rblcount=all ; \
rbl=list.dsbl.org, bl.spamcop.net, dnsbl.sorbs.net, zen.spamhaus.org ; \
rhsbl_client=rddn.dnsbl.net.au, rhsbl.ahbl.org, rhsbl.sorbs.net ; \
rhsbl_sender=rddn.dnsbl.net.au, rhsbl.ahbl.org, rhsbl.sorbs.net ; \
- action=set(HIT_rhls=$$rhsblcount,HIT_rbls=$$rblcount)
+ action=set(HIT_rhls=$$rhsblcount,HIT_rbls=$$rblcount,HIT_txt=$$dnsbltext)
# compare
- id=RBL02 ; HIT_rhls>=1 ; HIT_rbls>=1 ; action=554 5.7.1 blocked using $$HIT_rhls RHSBLs and $$HIT_rbls RBLs
- id=RBL03 ; HIT_rhls>=2 ; action=554 5.7.1 blocked using $$HIT_rhls RHSBLs
- id=RBL04 ; HIT_rbls>=2 ; action=554 5.7.1 blocked using $$HIT_rbls RBLs
+ id=RBL02 ; HIT_rhls>=1 ; HIT_rbls>=1 ; action=554 5.7.1 blocked using $$HIT_rhls RHSBLs and $$HIT_rbls RBLs [INFO: $$HIT_txt]
+ id=RBL03 ; HIT_rhls>=2 ; action=554 5.7.1 blocked using $$HIT_rhls RHSBLs [INFO: $$HIT_txt]
+ id=RBL04 ; HIT_rbls>=2 ; action=554 5.7.1 blocked using $$HIT_rbls RBLs [INFO: $$HIT_txt]
MACROS/ACLS
Multiple use of long items or combinations of them may be abbreviated by
@@ -420,6 +438,9 @@ DESCRIPTION
Basically macros are simple text substitutions - see the "PARSER"
section for more information.
+ PLUGINS
+ Please visit
+
COMMAND LINE
*Ruleset*
@@ -435,6 +456,12 @@ DESCRIPTION
Adds to ruleset. Remember that you might have to quote
strings that contain whitespaces or shell characters.
+ *Plugins*
+
+ --plugins
+ A file containing plugin routines for postfwd. Please see the
+ PLUGINS section for more information.
+
*Scoring*
-s, --scores =
@@ -442,7 +469,7 @@ DESCRIPTION
Multiple usage is allowed. Just chain your arguments, like:
- postfwd -r "- =
;action=" -f -f ...
+ postfwd -r "- =
;action=" -f -f --plugins ...
or
postfwd --scores 4.5="WARN high score" --scores 5.0="REJECT postfwd score too high" ...
@@ -475,12 +502,15 @@ DESCRIPTION
Chroot the process to the specified path.
Test this before using - you might need some libs there.
+ --pidfile
+ The process id will be saved in the specified file.
+
-l, --logname
Labels the syslog messages. Useful when running multiple
instances of postfwd.
- --pidfile
- The process id will be saved in the specified file.
+ --loglen
+ Truncates any syslog message after characters.
*Optional arguments*
@@ -558,13 +588,13 @@ DESCRIPTION
Aug 19 12:39:45 mail1 postfwd[666]: [STATS] Rule ID: R-003 matched: 3116 times
...
+ --no-rulestats
+ Disables per rule statistics. Keeps your log clean, if you do not use them.
+ This option has no effect without --summary or --verbose set.
+
-L, --stdoutlog
Redirects all syslog messages to stdout for debugging. Never use this with postfix!
- --shortlog
- As postfwd now logs all hits for a request, you might find it unecessary to log the
- postfwd actions jump(), set() and score(). You may disable it with this option.
-
-t, --test
In test mode postfwd always returns "dunno", but logs according
to it`s ruleset. -v will be set automatically with this option.
@@ -573,15 +603,10 @@ DESCRIPTION
Disables all DNS based checks like RBL checks. Rules containing
such elements will be ignored.
- --dns_queuesize (default: 100)
- Sets the queue size for asynchonous dns queries. If the query exceeds this value,
- postfwd waits for answers of timeouts for previous queries.
+ -n, --nodnslog
+ Disables logging of dns events.
- --dns_retries (default: 3)
- Sets the retry counter for asynchonous dns queries. This value will apply to
- every single query.
-
- --dns_timeout (default: 7)
+ --dns_timeout (default: 14)
Sets the timeout for asynchonous dns queries in seconds. This value will apply to
all dns items in a rule.
@@ -746,11 +771,11 @@ DESCRIPTION
...
};
&&MAINTENANCE { \
- date=15.01.2007 ; \
- date=15.04.2007 ; \
- date=15.07.2007 ; \
- date=15.10.2007 ; \
- time=03:00:00-04:00:00 ; \
+ date=15.01.2007 ; \
+ date=15.04.2007 ; \
+ date=15.07.2007 ; \
+ date=15.10.2007 ; \
+ time=03:00:00 - 04:00:00 ; \
};
# rules
id=COMBINED ; &&RBLS ; &&DYNAMIC ; action=REJECT dynamic client and listed on RBL
@@ -765,14 +790,11 @@ DESCRIPTION
id=REJECT02 ; HIT_rbls==1 ; HIT_dyna==1 ; action=REJECT please see http://some.org/info?reject=02 for more info
id=REJECT03 ; HIT_helo==1 ; HIT_dyna==1 ; action=REJECT please see http://some.org/info?reject=03 for more info
- # combined with enhanced rbl features
- # set vals
+ ## combined with enhanced rbl features
+ #
id=RBL01 ; rhsblcount=all ; rblcount=all ; &&RBLS ; &&RHSBLS ; \
- action=set(HIT_rhls=$$rhsblcount,HIT_rbls=$$rblcount)
- # compare
- id=RBL02 ; HIT_rhls>=1 ; HIT_rbls>=1 ; action=554 5.7.1 blocked using $$HIT_rhls RHSBLs and $$HIT_rbls RBLs
- id=RBL03 ; HIT_rhls>=2 ; action=554 5.7.1 blocked using $$HIT_rhls RHSBLs
- id=RBL04 ; HIT_rbls>=2 ; action=554 5.7.1 blocked using $$HIT_rbls RBLs
+ action=set(HIT_dnsbls=$$rhsblcount,HIT_dnsbls+=$$rblcount,HIT_dnstxt=$$dnsbltext)
+ id=RBL02 ; HIT_dnsbls>=2 ; action=554 5.7.1 blocked using $$HIT_dnsbls DNSBLs [INFO: $$HIT_dnstxt]
PARSER
*Configuration*
@@ -834,7 +856,7 @@ DESCRIPTION
Lists will be evaluated in the specified order. This allows to place
faster expressions at first:
- postfwd -vv -L -r "id=RBL001; rbl=localrbl.local zen.spamhaus.org; action=REJECT" /root/request.sample
+ postfwd -vv -L -r "id=RBL001; rbl=localrbl.local zen.spamhaus.org; action=REJECT" /some/where/request.sample
produces the following
@@ -853,7 +875,7 @@ DESCRIPTION
The negation operator !!() has the highest priority and therefore
will be evaluated first. Then variable substitutions are performed:
- postfwd -vv -L -r "id=TEST; action=REJECT; client_name=!!($$heloname)" /root/request.sample
+ postfwd -vv -L -r "id=TEST; action=REJECT; client_name=!!($$heloname)" /some/where/request.sample
will give
@@ -1006,10 +1028,13 @@ DESCRIPTION
Some of these proposals might not match your environment. Please check
your requirements and test new options carefully!
- - use caching options - use the correct match operator ==, <=, >= - use
- ^ and $ in regular expressions - use item lists (faster than single
- rules) - use set() action on repeated item lists - use jump action - use
- pre-lookup rule for rbl/rhsbls with empty note() action
+ - use caching options
+ - use the correct match operator ==, <=, >=
+ - use ^ and/or $ in regular expressions
+ - use item lists (faster than single rules)
+ - use set() action on repeated item lists
+ - use jumps and rate limits
+ - use a pre-lookup rule for rbl/rhsbls with empty note() action
SEE ALSO
See for a description
diff --git a/etc/postfwd.cf b/etc/postfwd.cf
index e43c261..8c9de1d 100644
--- a/etc/postfwd.cf
+++ b/etc/postfwd.cf
@@ -1,9 +1,11 @@
-#################################################################################################
+
+
+###################################################################################################
##
-## ATTENTION: This example configuration uses features which require postfwd 1.10pre6!
+## ATTENTION: This example configuration uses features which require at least postfwd 1.10pre6!
## Please see the manual ('postfwd -m') for example syntax for prior versions.
##
-#################################################################################################
+###################################################################################################
##
@@ -12,11 +14,11 @@
# 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 ; \
+ date=15.01.2007 - 15.01.2007 ; \
+ date=15.04.2007 - 15.04.2007 ; \
+ date=15.07.2007 - 15.07.2007 ; \
+ date=15.10.2007 - 15.10.2007 ; \
+ time=03:00:00 - 04:00:00 ; \
};
# Whitelists
@@ -48,6 +50,13 @@
client_name~=[\.\-]static[[\.\-] ; \
client_name~=^(mail|smtp|mout|mx)[\-]*[0-9]*\. ; \
};
+&&DNSWLS { \
+ rbl=list.dnswl.org ; \
+ rbl=exemptions.ahbl.org ; \
+ rbl=query.bondedsender.org ; \
+ rbl=hostkarma.junkemailfilter.com/^127\.0\.0\.1$/3600 ; \
+ rhsbl_client=hostkarma.junkemailfilter.com/^127\.0\.0\.1$/3600 ; \
+};
# Spamchecks
&&BADHELO { \
@@ -59,17 +68,15 @@
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 { \
+&&DNSBLS { \
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 ; \
+ rhsbl=rddn.dnsbl.net.au ; \
+ rhsbl=rhsbl.ahbl.org ; \
+ rhsbl=rhsbl.sorbs.net ; \
};
@@ -89,32 +96,38 @@ 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
+# DNSWL checks - lookup
+id=RWL_001 ; &&DNSWLS ; rhsblcount=all ; rblcount=all ; \
+ action=set(HIT_dnswls=$$rhsblcount,HIT_dnswls+=$$rblcount,DSWL_text=$$dnsbltext)
+
+# DNSWL - whitelisting
+id=RWL_002 ; HIT_dnswls>=2 ; action=PREPEND X-PFW-STATE: INFO: [$$DSWL_text]
+id=RWL_003 ; HIT_dnswls>=1 ; action=PREPEND X-PFW-STATE: INFO: [$$DSWL_text] ; &&STATIC
+id=RWL_004 ; HIT_dnswls>=1 ; action=PREPEND X-PFW-STATE: INFO: [$$DSWL_text] ; $$client_name~=$$(sender_domain)$
+
+# DNSBL checks - lookup
+id=RBL_001 ; &&DNSBLS ; rhsblcount=all ; rblcount=all ; \
+ action=set(HIT_dnsbls=$$rhsblcount,HIT_dnsbls+=$$rblcount,DSBL_text=$$dnsbltext)
+
+# DNSBL checks - evaluation
+id=RBL_002 ; HIT_dnsbls>=2 ; action=554 5.7.1 blocked using $$DSBL_count dnsbls, INFO: [$$DSBL_text]
+id=RBL_003 ; HIT_dnsbls>=1 ; &&DYNAMIC ; action=REJECT listed on dnsbl and $$client_name looks like dynip, INFO: [$$DSBL_text]
+id=RBL_004 ; HIT_dnsbls>=1 ; &&BADHELO ; action=REJECT listed on dnsbl and $$helo_name does not match $$client_name, INFO: [$$DSBL_text]
# Rate limits
-id=RATE_001 ; &&DYNAMIC ; action=rate($$client_address/1/300/450 4.7.1 please do not send more than once per 5 minutes)
-id=RATE_002 ; HIT_rhls>=1 ; action=rate($$client_address/1/300/450 4.7.1 please do not send more than once per 5 minutes)
-id=RATE_003 ; HIT_rbls>=1 ; action=rate($$client_address/1/300/450 4.7.1 please do not send more than once per 5 minutes)
-id=RATE_004 ; sasl_username==boss ; action=size($$sasl_username/30000000/300/450 4.7.1 please do not send more than 30mb within 5 minutes)
-id=RATE_005 ; sasl_username~=\w ; action=size($$sasl_username/10000000/300/450 4.7.1 please do not send more than 10mb within 5 minutes)
+id=RATE_001 ; HIT_dnsbls>=1; \
+ action=rate($$client_address/1/300/450 4.7.1 please do not try more than once per 5 minutes)
+id=RATE_002 ; &&DYNAMIC ; \
+ action=rate($$client_address/1/300/450 4.7.1 please do not try more than once per 5 minutes)
# 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
+id=GREY_001 ; action=dunno ; &&STATIC
+id=GREY_002 ; action=dunno ; $$client_name~=$$(sender_domain)$
+id=GREY_003 ; action=dunno ; HIT_dnswls>=1
+id=GREY_004 ; action=greylisting ; &&DYNAMIC
+id=GREY_005 ; action=greylisting ; HIT_dnsbls>=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/man/man8/postfwd.8 b/man/man8/postfwd.8
index cb7f290..ccb2ed1 100644
--- a/man/man8/postfwd.8
+++ b/man/man8/postfwd.8
@@ -129,7 +129,7 @@
.\" ========================================================================
.\"
.IX Title "POSTFWD 8"
-.TH POSTFWD 8 "2008-05-12" "perl v5.8.5" "User Contributed Perl Documentation"
+.TH POSTFWD 8 "2008-09-14" "perl v5.8.5" "User Contributed Perl Documentation"
.SH "NAME"
postfwd \- postfix firewall daemon
.SH "SYNOPSIS"
@@ -147,7 +147,7 @@ postfwd [\s-1OPTIONS\s0] [\s-1SOURCE1\s0, \s-1SOURCE2\s0, ...]
\& -s, --scores = returns when score exceeds
.Ve
.PP
-.Vb 9
+.Vb 10
\& Networking:
\& -d, --daemon run postfwd as daemon
\& -i, --interface listen on interface
@@ -155,8 +155,9 @@ postfwd [\s-1OPTIONS\s0] [\s-1SOURCE1\s0, \s-1SOURCE2\s0, ...]
\& -u, --user set uid to user
\& -g, --group set gid to group
\& -R, --chroot chroot the daemon to
-\& -l, --logname label for syslog messages
\& --pidfile create pidfile under
+\& -l, --logname label for syslog messages
+\& --loglen truncates syslogs after chars
.Ve
.PP
.Vb 11
@@ -173,15 +174,14 @@ postfwd [\s-1OPTIONS\s0] [\s-1SOURCE1\s0, \s-1SOURCE2\s0, ...]
\& --cleanup-rates cleanup interval in seconds for rate cache
.Ve
.PP
-.Vb 12
+.Vb 11
\& Optional:
\& -t, --test testing, always returns "dunno"
\& -v, --verbose verbose logging, use twice (-vv) to increase level
-\& --shortlog disables logging of some postfwd commands
\& -S, --summary show some usage statistics every seconds
+\& --no-rulestats disables per rule statistics
\& -n, --nodns disable dns
-\& --dns_queuesize sets the queue size for asynchonous dns queries
-\& --dns_retries how many retries for a single asynchonous dns query
+\& --nodnslog disable dns logging
\& --dns_timeout timeout in seconds for asynchonous dns queries
\& --dns_timeout_max maximum of dns timeouts until a dnsbl will be deactivated
\& --dns_timeout_interval interval in seconds for dns timeout maximum counter
@@ -189,7 +189,7 @@ postfwd [\s-1OPTIONS\s0] [\s-1SOURCE1\s0, \s-1SOURCE2\s0, ...]
.Ve
.PP
.Vb 7
-\& Informational (use only at command-line, not with postfix!):
+\& Informational (use only at command-line!):
\& -C, --showconfig shows ruleset summary, -v for verbose
\& -L, --stdoutlog redirect syslog messages to stdout
\& -P, --perfmon no syslogging, no stdout
@@ -197,6 +197,11 @@ postfwd [\s-1OPTIONS\s0] [\s-1SOURCE1\s0, \s-1SOURCE2\s0, ...]
\& -h, --help shows usage
\& -m, --manual shows program manual
.Ve
+.PP
+.Vb 2
+\& Plugins:
+\& --plugins loads plugins from
+.Ve
.SH "DESCRIPTION"
.IX Header "DESCRIPTION"
.Sh "\s-1INTRODUCTION\s0"
@@ -286,8 +291,17 @@ Rules can span multiple lines by adding a trailing backslash \*(L"\e\*(R" charac
\& ids also serve as targets for the "jump" command.
.Ve
.PP
-.Vb 1
+.Vb 10
\& date, time - a time or date range within the specified rule shall hit
+\& # FORMAT:
+\& # Feb, 29th
+\& date=29.02.2008
+\& # Dec, 24th - 26th
+\& date=24.12.2008-26.12.2008
+\& # from today until Nov, 23rd
+\& date=-23.09.2008
+\& # from April, 1st until today
+\& date=01.04.2008-
.Ve
.PP
.Vb 2
@@ -548,13 +562,21 @@ You can reference to request attributes, like
.PP
These special attributes will be reset for any new rule:
.PP
-.Vb 3
+.Vb 5
\& rblcount - contains the number of RBL answers
\& rhsblcount - contains the number of RHSBL answers
\& matches - contains the number of matched items
+\& dnsbltext - contains the dns TXT part of all RBL and RHSBL replies in the form
+\& rbltype:rblname:; rbltype:rblname:; ...
.Ve
.PP
-This means that you must save them, if you plan to use these values in later rules:
+These special attributes will be changed for any matching rule:
+.PP
+.Vb 1
+\& request_hits - contains ids of all matching rules
+.Ve
+.PP
+This means that it might be necessary to save them, if you plan to use these values in later rules:
.PP
.Vb 6
\& # set vals
@@ -562,14 +584,14 @@ This means that you must save them, if you plan to use these values in later rul
\& rbl=list.dsbl.org, bl.spamcop.net, dnsbl.sorbs.net, zen.spamhaus.org ; \e
\& rhsbl_client=rddn.dnsbl.net.au, rhsbl.ahbl.org, rhsbl.sorbs.net ; \e
\& rhsbl_sender=rddn.dnsbl.net.au, rhsbl.ahbl.org, rhsbl.sorbs.net ; \e
-\& action=set(HIT_rhls=$$rhsblcount,HIT_rbls=$$rblcount)
+\& action=set(HIT_rhls=$$rhsblcount,HIT_rbls=$$rblcount,HIT_txt=$$dnsbltext)
.Ve
.PP
.Vb 4
\& # compare
-\& id=RBL02 ; HIT_rhls>=1 ; HIT_rbls>=1 ; action=554 5.7.1 blocked using $$HIT_rhls RHSBLs and $$HIT_rbls RBLs
-\& id=RBL03 ; HIT_rhls>=2 ; action=554 5.7.1 blocked using $$HIT_rhls RHSBLs
-\& id=RBL04 ; HIT_rbls>=2 ; action=554 5.7.1 blocked using $$HIT_rbls RBLs
+\& id=RBL02 ; HIT_rhls>=1 ; HIT_rbls>=1 ; action=554 5.7.1 blocked using $$HIT_rhls RHSBLs and $$HIT_rbls RBLs [INFO: $$HIT_txt]
+\& id=RBL03 ; HIT_rhls>=2 ; action=554 5.7.1 blocked using $$HIT_rhls RHSBLs [INFO: $$HIT_txt]
+\& id=RBL04 ; HIT_rbls>=2 ; action=554 5.7.1 blocked using $$HIT_rbls RBLs [INFO: $$HIT_txt]
.Ve
.Sh "\s-1MACROS/ACLS\s0"
.IX Subsection "MACROS/ACLS"
@@ -621,6 +643,9 @@ Macros can contain macros, too:
.Ve
.PP
Basically macros are simple text substitutions \- see the \*(L"\s-1PARSER\s0\*(R" section for more information.
+.Sh "\s-1PLUGINS\s0"
+.IX Subsection "PLUGINS"
+Please visit
.Sh "\s-1COMMAND\s0 \s-1LINE\s0"
.IX Subsection "COMMAND LINE"
\&\fIRuleset\fR
@@ -640,6 +665,14 @@ that at least one of the following is required for postfwd to work.
\& strings that contain whitespaces or shell characters.
.Ve
.PP
+\&\fIPlugins\fR
+.PP
+.Vb 3
+\& --plugins
+\& A file containing plugin routines for postfwd. Please see the
+\& PLUGINS section for more information.
+.Ve
+.PP
\&\fIScoring\fR
.PP
.Vb 2
@@ -650,7 +683,7 @@ that at least one of the following is required for postfwd to work.
Multiple usage is allowed. Just chain your arguments, like:
.PP
.Vb 3
-\& postfwd -r "- =
;action=" -f -f ...
+\& postfwd -r "- =
;action=" -f -f --plugins ...
\& or
\& postfwd --scores 4.5="WARN high score" --scores 5.0="REJECT postfwd score too high" ...
.Ve
@@ -695,6 +728,11 @@ The following arguments will control it's behaviour in this case.
\& Test this before using - you might need some libs there.
.Ve
.PP
+.Vb 2
+\& --pidfile
+\& The process id will be saved in the specified file.
+.Ve
+.PP
.Vb 3
\& -l, --logname
\& Labels the syslog messages. Useful when running multiple
@@ -702,8 +740,8 @@ The following arguments will control it's behaviour in this case.
.Ve
.PP
.Vb 2
-\& --pidfile
-\& The process id will be saved in the specified file.
+\& --loglen
+\& Truncates any syslog message after characters.
.Ve
.PP
\&\fIOptional arguments\fR
@@ -807,18 +845,18 @@ These parameters influence the way postfwd is working. Any of them can be combin
\& ...
.Ve
.PP
+.Vb 3
+\& --no-rulestats
+\& Disables per rule statistics. Keeps your log clean, if you do not use them.
+\& This option has no effect without --summary or --verbose set.
+.Ve
+.PP
.Vb 2
\& -L, --stdoutlog
\& Redirects all syslog messages to stdout for debugging. Never use this with postfix!
.Ve
.PP
.Vb 3
-\& --shortlog
-\& As postfwd now logs all hits for a request, you might find it unecessary to log the
-\& postfwd actions jump(), set() and score(). You may disable it with this option.
-.Ve
-.PP
-.Vb 3
\& -t, --test
\& In test mode postfwd always returns "dunno", but logs according
\& to it`s ruleset. -v will be set automatically with this option.
@@ -830,20 +868,13 @@ These parameters influence the way postfwd is working. Any of them can be combin
\& such elements will be ignored.
.Ve
.PP
-.Vb 3
-\& --dns_queuesize (default: 100)
-\& Sets the queue size for asynchonous dns queries. If the query exceeds this value,
-\& postfwd waits for answers of timeouts for previous queries.
+.Vb 2
+\& -n, --nodnslog
+\& Disables logging of dns events.
.Ve
.PP
.Vb 3
-\& --dns_retries (default: 3)
-\& Sets the retry counter for asynchonous dns queries. This value will apply to
-\& every single query.
-.Ve
-.PP
-.Vb 3
-\& --dns_timeout (default: 7)
+\& --dns_timeout (default: 14)
\& Sets the timeout for asynchonous dns queries in seconds. This value will apply to
\& all dns items in a rule.
.Ve
@@ -1044,11 +1075,11 @@ the '\-I' switch to have your configuration refreshed for every request postfwd
\& ...
\& };
\& &&MAINTENANCE { \e
-\& date=15.01.2007 ; \e
-\& date=15.04.2007 ; \e
-\& date=15.07.2007 ; \e
-\& date=15.10.2007 ; \e
-\& time=03:00:00-04:00:00 ; \e
+\& date=15.01.2007 ; \e
+\& date=15.04.2007 ; \e
+\& date=15.07.2007 ; \e
+\& date=15.10.2007 ; \e
+\& time=03:00:00 - 04:00:00 ; \e
\& };
\& # rules
\& id=COMBINED ; &&RBLS ; &&DYNAMIC ; action=REJECT dynamic client and listed on RBL
@@ -1066,15 +1097,12 @@ the '\-I' switch to have your configuration refreshed for every request postfwd
\& id=REJECT03 ; HIT_helo==1 ; HIT_dyna==1 ; action=REJECT please see http://some.org/info?reject=03 for more info
.Ve
.PP
-.Vb 8
-\& # combined with enhanced rbl features
-\& # set vals
+.Vb 5
+\& ## combined with enhanced rbl features
+\& #
\& id=RBL01 ; rhsblcount=all ; rblcount=all ; &&RBLS ; &&RHSBLS ; \e
-\& action=set(HIT_rhls=$$rhsblcount,HIT_rbls=$$rblcount)
-\& # compare
-\& id=RBL02 ; HIT_rhls>=1 ; HIT_rbls>=1 ; action=554 5.7.1 blocked using $$HIT_rhls RHSBLs and $$HIT_rbls RBLs
-\& id=RBL03 ; HIT_rhls>=2 ; action=554 5.7.1 blocked using $$HIT_rhls RHSBLs
-\& id=RBL04 ; HIT_rbls>=2 ; action=554 5.7.1 blocked using $$HIT_rbls RBLs
+\& action=set(HIT_dnsbls=$$rhsblcount,HIT_dnsbls+=$$rblcount,HIT_dnstxt=$$dnsbltext)
+\& id=RBL02 ; HIT_dnsbls>=2 ; action=554 5.7.1 blocked using $$HIT_dnsbls DNSBLs [INFO: $$HIT_dnstxt]
.Ve
.Sh "\s-1PARSER\s0"
.IX Subsection "PARSER"
@@ -1147,7 +1175,7 @@ equals to
Lists will be evaluated in the specified order. This allows to place faster expressions at first:
.PP
.Vb 1
-\& postfwd -vv -L -r "id=RBL001; rbl=localrbl.local zen.spamhaus.org; action=REJECT" /root/request.sample
+\& postfwd -vv -L -r "id=RBL001; rbl=localrbl.local zen.spamhaus.org; action=REJECT" /some/where/request.sample
.Ve
.PP
produces the following
@@ -1169,7 +1197,7 @@ produces the following
The negation operator !!() has the highest priority and therefore will be evaluated first. Then variable substitutions are performed:
.PP
.Vb 1
-\& postfwd -vv -L -r "id=TEST; action=REJECT; client_name=!!($$heloname)" /root/request.sample
+\& postfwd -vv -L -r "id=TEST; action=REJECT; client_name=!!($$heloname)" /some/where/request.sample
.Ve
.PP
will give
@@ -1340,13 +1368,15 @@ listening on the specified network settings.
.IX Subsection "PERFORMANCE"
Some of these proposals might not match your environment. Please check your requirements and test new options carefully!
.PP
-\&\- use caching options
-\&\- use the correct match operator ==, <=, >=
-\&\- use ^ and $ in regular expressions
-\&\- use item lists (faster than single rules)
-\&\- use \fIset()\fR action on repeated item lists
-\&\- use jump action
-\&\- use pre-lookup rule for rbl/rhsbls with empty \fInote()\fR action
+.Vb 7
+\& - use caching options
+\& - use the correct match operator ==, <=, >=
+\& - use ^ and/or $ in regular expressions
+\& - use item lists (faster than single rules)
+\& - use set() action on repeated item lists
+\& - use jumps and rate limits
+\& - use a pre-lookup rule for rbl/rhsbls with empty note() action
+.Ve
.Sh "\s-1SEE\s0 \s-1ALSO\s0"
.IX Subsection "SEE ALSO"
See for a description
diff --git a/sbin/postfwd b/sbin/postfwd
index f7450ae..b8cb6f2 100755
--- a/sbin/postfwd
+++ b/sbin/postfwd
@@ -6,28 +6,25 @@
# `postfwd -m` for detailed instructions.
#
-
-### SUB init
-
package postfwd;
+use warnings;
use strict;
# Includes
+use Pod::Usage;
use Sys::Syslog qw(:DEFAULT setlogsock);
use Getopt::Long 2.25 qw(:config no_ignore_case bundling);
use POSIX qw(setsid setuid setgid setlocale strftime LC_ALL);
-use Pod::Usage;
-use Net::DNS::Async;
-use Net::CIDR::Lite;
+# Networking
+use Net::DNS;
use Net::Server::Multiplex;
-
use vars qw(@ISA);
@ISA = qw(Net::Server::Multiplex);
# Program constants
our($NAME) = 'postfwd';
-our($VERSION) = '1.10pre7c';
+our($VERSION) = '1.10pre8b';
# Networking options (use -i, -p and -R to change)
our($def_net_pid) = "/var/run/".$NAME.".pid";
@@ -37,9 +34,10 @@ our($def_net_port) = "10040";
our($def_net_user) = "nobody";
our($def_net_group) = "nobody";
our($def_net_proto) = "tcp";
-our($def_dns_queuesize) = "100";
+our($def_dns_queuesize) = "300";
our($def_dns_retries) = "3";
-our($def_dns_timeout) = "7";
+our($def_dns_timeout) = "14";
+our($reply_maxlen) = "512";
# change this, to match your POD requirements
# we need pod2text for the -m switch (manual)
@@ -67,12 +65,24 @@ our($Stat_Interval_Time) = 600;
# can be changed with `-c` command-line option
our($REQUEST_MAX_CACHE) = 600;
+# minimum ttl for other dns cache objects
+our($DNS_MIN_CACHE) = 3600;
+
+# dns key value matching
+our(%DNS_REPNAMES) = (
+ "NS" => "nsdname",
+ "MX" => "exchange",
+ "A" => "address",
+ "TXT" => "char_str_list",
+ "CNAME" => "cname",
+);
+
# RBL / RHSBL parameters, use "rbl = //"
# to override for each RBL in your config
# maximum cache time in seconds, use 0 to deactivate
our($RBL_MAX_CACHE) = 3600;
# default rbl reply if not specified
-our($RBL_DEFAULT) = '^127\.\d+\.\d+\.\d+$';
+our($RBL_DEFAULT) = '^127\.';
# skip this dnsbl after timeouts
our($MAX_DNSBL_TIMEOUTS) = 10;
our($MAX_DNSBL_INTERVAL) = 1200;
@@ -85,9 +95,14 @@ our($CLEANUP_RATE_CACHE) = 600;
# these items have to be compared as...
# scoring
our($COMP_SCORES) = "score";
+our($COMP_NS_NAME) = "sender_ns_names";
+our($COMP_NS_ADDR) = "sender_ns_addrs";
+our($COMP_MX_NAME) = "sender_mx_names";
+our($COMP_MX_ADDR) = "sender_mx_addrs";
# networks in CIDR notation (a.b.c.d/nn)
-our($COMP_NETWORK_CIDRS) = "client_address";
+our($COMP_NETWORK_CIDRS) = "(client_address|sender_(ns|mx)_addrs)";
# RBL checks
+our($COMP_DNSBL_TEXT) = "dnsbltext";
our($COMP_RBL_CNT) = "rblcount";
our($COMP_RHSBL_CNT) = "rhsblcount";
our($COMP_RBL_KEY) = "rbl";
@@ -95,6 +110,7 @@ our($COMP_RHSBL_KEY) = "rhsbl";
our($COMP_RHSBL_KEY_CLIENT) = "rhsbl_client";
our($COMP_RHSBL_KEY_SENDER) = "rhsbl_sender";
our($COMP_RHSBL_KEY_RCLIENT) = "rhsbl_reverse_client";
+our($COMP_RHSBL_KEY_HELO) = "rhsbl_helo";
# date checks
our($COMP_DATE) = "date";
our($COMP_TIME) = "time";
@@ -103,6 +119,8 @@ our($COMP_MONTHS) = "months";
# always true
our($COMP_ACTION) = "action";
our($COMP_ID) = "id";
+# rule hits
+our($COMP_HITS) = "request_hits";
# item match counter
our($COMP_MATCHES) = "matches";
# separator
@@ -117,16 +135,18 @@ our($COMP_VAR) = "[\$][\$]";
# date calculations
our($COMP_DATECALC) = "($COMP_DATE|$COMP_TIME|$COMP_DAYS|$COMP_MONTHS)";
# these items allow whitespace-or-comma-separated values
-our($COMP_CSV) = "($COMP_NETWORK_CIDRS|$COMP_RBL_KEY|$COMP_RHSBL_KEY|$COMP_RHSBL_KEY_CLIENT|$COMP_RHSBL_KEY_SENDER|$COMP_RHSBL_KEY_RCLIENT|$COMP_DATECALC)";
+our($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)";
# dont treat these as lists
our($COMP_SINGLE) = "($COMP_ID|$COMP_ACTION|$COMP_SCORES|$COMP_RBL_CNT|$COMP_RHSBL_CNT)";
-# Syslogging options
+# Syslog options
our($syslog_name) = $NAME;
our($syslog_facility) = "mail";
our($syslog_priority) = "info";
our($syslog_options) = "pid";
our($syslog_socktype) = 'unix';
+our($syslog_maxlen) = 0;
+our($syslog_safe) = 0;
if ( defined $Sys::Syslog::VERSION and $Sys::Syslog::VERSION ge '0.15' ) {
# use 'native' when Sys::Syslog >= 0.15
$syslog_socktype = 'native';
@@ -135,20 +155,12 @@ if ( defined $Sys::Syslog::VERSION and $Sys::Syslog::VERSION ge '0.15' ) {
# 'stream' is broken and 'unix' doesn't work on Solaris: only 'inet'
# seems to be useable with Sys::Syslog < 0.15
$syslog_socktype = 'inet';
-};
+} else { $syslog_safe = 1 };
# save command-line
our(@CommandArgs) = @ARGV;
# initializations - do not change
-our(@Configs,@Rules,@CacheID) = ();
-our(%Config_Cache, %RBL_Cache, %Request_Cache) = ();
-our(%Matches, %opt_scores, %ACLs, %compare, %Rates, %Timeouts) = ();
-our( $Counter_Requests,$Counter_Hits,
- $Counter_Interval,$Counter_Top, $Counter_Rates,
- $Starttime,$Startdate, $Cleanup_Requests,
- $Cleanup_RBLs, $Cleanup_Rates, $Cleanup_Timeouts) = 0;
-
our(%months) = (
"Jan" => 0, "jan" => 0, "JAN" => 0,
"Feb" => 1, "feb" => 1, "FEB" => 1,
@@ -173,12 +185,22 @@ our(%weekdays) = (
"Sat" => 6, "sat" => 6, "SAT" => 6,
);
use vars qw(
- $opt_daemon $opt_instantconfig $opt_nodns
+ @Configs @Rules @CacheID @DNSBL_Text @Plugins
+ %Config_Cache %DNS_Cache %Request_Cache %Rule_by_ID
+ %Matches %opt_scores %ACLs %Rates %Timeouts
+ %postfwd_items %postfwd_items_plugin
+ %postfwd_compare %postfwd_compare_plugin
+ %postfwd_actions %postfwd_actions_plugin
+ $Counter_Requests $Counter_Hits
+ $Counter_Interval $Counter_Top $Counter_Rates
+ $Starttime $Startdate $Cleanup_Requests
+ $Cleanup_RBLs $Cleanup_Rates $Cleanup_Timeouts
+ $opt_daemon $opt_instantconfig $opt_nodns $opt_nodnslog
$opt_summary $net_interface $net_port
$net_user $net_group $net_chroot $net_pid
$opt_perfmon $opt_test $opt_verbose
$opt_cache_rdomain_only $opt_cache_no_size
- $opt_cache_no_sender
+ $opt_cache_no_sender $opt_no_rulestats
$opt_showconfig $opt_stdoutlog $opt_shortlog
$DNS $Reload_Conf $dns_queuesize $dns_retries $dns_timeout
);
@@ -188,49 +210,34 @@ use vars qw(
#
# send log message
-# escaping % character for safe syslogging
-#
-sub mylogs {
- my($prio) = shift(@_);
- my($msg) = shift(@_);
- # dangerous % will be replaced by %%
- $msg =~ s/\%/%%/g;
- unless ($opt_stdoutlog) {
- # Workaround for a crash when syslog daemon is temporarily not
- # present (for example on syslog rotation)
- if(!defined $Sys::Syslog::VERSION or $Sys::Syslog::VERSION lt '0.15') {
- eval {
- local $SIG{"__DIE__"} = sub { };
- syslog $prio, "$msg", @_ if (($prio eq "crit") or not($opt_perfmon));
- };
- } else {
- syslog $prio, "$msg", @_ if (($prio eq "crit") or not($opt_perfmon));
- };
- } else {
- printf "[LOGS $prio]: $msg\n", @_ if (($prio eq "crit") or not($opt_perfmon));
- };
-}
-#
-# send log message
-# no escaping for the % character - use only for safe output (stats)
#
sub mylog {
my($prio) = shift(@_);
my($msg) = shift(@_);
- unless ($opt_stdoutlog) {
- # Workaround, see mylogs function
- if(!defined $Sys::Syslog::VERSION or $Sys::Syslog::VERSION lt '0.15') {
- eval {
- local $SIG{"__DIE__"} = sub { };
- syslog $prio, "$msg", @_ if (($prio eq "crit") or not($opt_perfmon));
- };
- } else {
- syslog $prio, "$msg", @_ if (($prio eq "crit") or not($opt_perfmon));
- };
- } else {
- printf "[LOG $prio]: $msg\n", @_ if (($prio eq "crit") or not($opt_perfmon));
+ # truncate syslogs (--loglen option)
+ $msg = substr($msg, 0, $syslog_maxlen) if $syslog_maxlen;
+ if ( not($opt_perfmon) or ($prio eq "crit") ) {
+ unless ($opt_stdoutlog) {
+ # Sys::Syslog < 0.15 dies when syslog daemon is temporarily not
+ # present (for example on syslog rotation)
+ if($syslog_safe) {
+ eval {
+ local $SIG{__DIE__} = sub { };
+ syslog $prio, "$msg", @_;
+ };
+ } else { syslog $prio, "$msg", @_; };
+ } else { printf "[LOG $prio]: $msg\n", @_; };
};
-}
+};
+#
+# send log message, escaping % character
+#
+sub mylogs {
+ my($prio) = shift(@_);
+ my($msg) = shift(@_);
+ $msg =~ s/\%/%%/g;
+ mylog $prio, $msg;
+};
#
# print a string to STDOUT
#
@@ -238,7 +245,7 @@ sub myprint {
my($msg) = shift(@_);
print STDOUT $msg, @_
unless $opt_perfmon;
-}
+};
#
# print formatted string to STDOUT
#
@@ -246,7 +253,7 @@ sub myprintf {
my($msg) = shift(@_);
printf STDOUT $msg, @_
unless $opt_perfmon;
-}
+};
#
# Log an error and abort.
#
@@ -254,7 +261,7 @@ sub fatal_exit {
my($msg) = shift(@_);
warn "fatal: $msg", @_;
exit 1;
-}
+};
#
# finish program
#
@@ -277,42 +284,84 @@ sub exec_cmd {
return not($myresult);
};
#
+# takes a list and returns a unified list, keeping given order
+#
+sub uniq {
+ undef my %uniq;
+ return grep(!$uniq{$_}++, @_);
+};
+#
+# get ip and mask
+#
+sub cidr_parse
+{
+ defined $_[0] or return undef;
+ $_[0] =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)\/(\d+)$/ or return undef;
+ $1 < 256 and $2 < 256 and $3 < 256 and $4 < 256 and $5 <= 32 and $5 > 0
+ or return undef;
+ 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 $mask and defined $addr;
+ if($addr =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) {
+ $addr = ($1<<24)+($2<<16)+($3<<8)+$4;
+ };
+ return ($addr & $mask) == $net;
+};
+
+#
+# clean up RBL cache
+#
+sub cleanup_dns_cache {
+ my($now) = $_[0];
+ foreach my $checkitem (keys %DNS_Cache) {
+ # remove inclomplete objects (dns timeouts)
+ if ( !defined($DNS_Cache{$checkitem}{time}) or !defined($DNS_Cache{$checkitem}{time}) ) {
+ mylogs $syslog_priority, "[CLEANUP] deleting incomplete dns-cache item $checkitem after "
+ .((defined $DNS_Cache{$checkitem}{time}) ? ($now - $DNS_Cache{$checkitem}{time}) : '')
+ ." seconds (timeout: ".((defined $DNS_Cache{$checkitem}{ttl}) ? $DNS_Cache{$checkitem}{ttl} : '')."s)"
+ if ($opt_verbose > 1);
+ delete $DNS_Cache{$checkitem};
+ # remove timed out objects
+ } elsif ( ($now - $DNS_Cache{$checkitem}{time}) > $DNS_Cache{$checkitem}{ttl} ) {
+ mylogs $syslog_priority, "[CLEANUP] removing rbl-cache for $checkitem after "
+ .($now - $DNS_Cache{$checkitem}{time})." seconds (timeout: ".$DNS_Cache{$checkitem}{ttl}."s)"
+ if ($opt_verbose > 1);
+ delete $DNS_Cache{$checkitem};
+ };
+ };
+};
+#
# clean up request cache
#
-sub request_cache_cleanup {
+sub cleanup_request_cache {
my($now) = $_[0];
foreach my $checkitem (keys %Request_Cache) {
- if ( (($now - $Request_Cache{$checkitem}{"time"}) > $REQUEST_MAX_CACHE) ) {
+ if ( (($now - $Request_Cache{$checkitem}{time}) > $REQUEST_MAX_CACHE) ) {
mylogs $syslog_priority, "[CLEANUP] removing request-cache $checkitem after "
- .($now - $Request_Cache{$checkitem}{"time"})." seconds (timeout: ".$REQUEST_MAX_CACHE."s)"
+ .($now - $Request_Cache{$checkitem}{time})." seconds (timeout: ".$REQUEST_MAX_CACHE."s)"
if ($opt_verbose > 1);
delete $Request_Cache{$checkitem};
};
};
};
#
-# clean up RBL cache
-#
-sub rbl_cache_cleanup {
- my($now) = $_[0];
- foreach my $checkitem (keys %RBL_Cache) {
- if ( (($now - @{$RBL_Cache{$checkitem}}[1]) > @{$RBL_Cache{$checkitem}}[2]) ) {
- mylogs $syslog_priority, "[CLEANUP] removing rbl-cache for $checkitem after "
- .($now - @{$RBL_Cache{$checkitem}}[1])." seconds (timeout: ".@{$RBL_Cache{$checkitem}}[2]."s)"
- if ($opt_verbose > 1);
- delete $RBL_Cache{$checkitem};
- };
- };
-};
-#
# clean up rate cache
#
-sub rate_cache_cleanup {
+sub cleanup_rate_cache {
my($now) = $_[0];
foreach my $checkitem (keys %Rates) {
- if ( (($now - @{$Rates{$checkitem}}[4]) > @{$Rates{$checkitem}}[2]) ) {
+ if ( (($now - $Rates{$checkitem}{time}) > $Rates{$checkitem}{ttl}) ) {
mylogs $syslog_priority, "[CLEANUP] removing rate-cache for $checkitem after "
- .($now - @{$Rates{$checkitem}}[4])." seconds (timeout: ".@{$Rates{$checkitem}}[2]."s)"
+ .($now - $Rates{$checkitem}{time})." seconds (timeout: ".$Rates{$checkitem}{ttl}."s)"
if ($opt_verbose > 1);
delete $Rates{$checkitem};
};
@@ -335,6 +384,7 @@ sub modify_score {
#
sub show_stats {
my($now) = time;
+ $Counter_Requests ||= 0;
$Counter_Interval ||= 0;
$Counter_Top ||= 0;
$Counter_Hits ||= 0;
@@ -354,14 +404,43 @@ sub show_stats {
mylog "notice", "[STATS] Averages: %.2f overall, %.2f last interval, %.2f top",
$totalreqpermin, $lastreqpermin, $Counter_Top;
- mylog "notice", "[STATS] Contents: %d rules, %d cached requests, %d cached dnsbl results, %d rate limits",
- $#Rules, scalar keys %Request_Cache, scalar keys %RBL_Cache, scalar keys %Rates;
+ mylog "notice", "[STATS] Contents: %d rules, %d cached requests, %d cached dns results, %d rate limits",
+ $#Rules, scalar keys %Request_Cache, scalar keys %DNS_Cache, scalar keys %Rates;
# per rule stats
- map { mylogs "notice", "[STATS] Rule ID: $_ matched: $Matches{$_} times" } (sort keys %Matches);
+ map { mylogs "notice", "[STATS] Rule ID: $_ matched: $Matches{$_} times" } (sort keys %Matches)
+ unless $opt_no_rulestats;
$Counter_Interval = 0;
};
+#
+# 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}) {
+ $var = $request{$var};
+ # substitute dangerous characters
+ $var =~ s/([^-\w\s])/\\$1/g if ( $cmp =~ /~/ );
+ $myresult=$val=$pre.$var.$post;
+ };
+ mylogs $syslog_priority, "substitute : \"$myitem\" \"$cmp\" \"$val\""
+ if ($opt_verbose > 1);
+ };
+ return $myresult;
+};
### SUB configuration
@@ -462,9 +541,10 @@ sub read_config {
my(%myrule, @myruleset) = ();
my($mytype,$myitem,$config);
- undef @Rules;
- undef %Request_Cache;
- undef %Rates;
+ # init, cleanup cache and config vars
+ @Rules = %Rule_by_ID = %Request_Cache = %Rates = ();
+
+ # parse configurations
for $config (@Configs) {
($mytype,$myitem) = split '::', $config;
if ($mytype eq "r" or $mytype eq "rule") {
@@ -487,6 +567,8 @@ sub read_config {
};
};
};
+ # update Rule by ID hash
+ map { $Rule_by_ID{$Rules[$_]{$COMP_ID}} = $_ } (0 .. $#Rules);
}
#
# displays configuration
@@ -520,43 +602,68 @@ sub show_config {
## sub DNS
+#
+# checks for rbl timeouts
+#
+sub rbl_timeout {
+ my($myrbl) = shift;
+ return ( ($MAX_DNSBL_TIMEOUTS > 0) and (defined $Timeouts{$myrbl}) and ($Timeouts{$myrbl} > $MAX_DNSBL_TIMEOUTS) );
+};
#
# reads DNS answers
#
sub rbl_read_dns {
- my($myresult) = shift;
- my($que,$res) = undef;
- my($now) = time;
+ my($myresult) = shift;
+ my($now) = time;
+ my($que,$ttl,$res,$typ) = undef;
+ my(@addrs) = ();
if ( defined $myresult ) {
- # read question, for rbl cache id
+ # read question, for dns cache id
foreach ($myresult->question) {
- next unless ($_->qtype eq 'A');
- $que = $_->qname;
- };
+ $typ = $_->qtype;
+ next unless (($typ eq 'A') or ($typ eq 'TXT'));
+ if ($que = $_->qname) {
+ # some RBLs return CNAMEs, so the number of the questions
+ # is not necessarily the number of answers you get
+ foreach ($myresult->answer) {
+ if ($_->type eq 'A') {
+ push @addrs, $_->address if $_->address;
+ $ttl = $_->ttl;
+ } elsif ($_->type eq 'TXT') {
+ $res = join(" ", $_->char_str_list());
+ $ttl = $_->ttl;
+ };
+ };
- if (defined $que) {
-
- # some RBLs return CNAMEs, so the number of the questions
- # is not necessarily the number of answers you get
- foreach ($myresult->answer) {
- next unless ($_->type eq 'A');
- $res = $_->address;
- };
- $res ||= '';
-
- # save result in cache
- if ( exists($RBL_Cache{$que}) ) {
- mylogs $syslog_priority, "[DNSBL] object "
- .( (@{$RBL_Cache{$que}}[5] eq $COMP_RBL_KEY)
- ? join(".", reverse(split(/\./,@{$RBL_Cache{$que}}[4])))
- : @{$RBL_Cache{$que}}[4] )
- ." listed on ".@{$RBL_Cache{$que}}[5].":".@{$RBL_Cache{$que}}[3]
- ." (answer: $res, time: ".($now - @{$RBL_Cache{$que}}[1])."s)"
- if $res;
- @{$RBL_Cache{$que}} = ( $res, $now, @{$RBL_Cache{$que}}[2], @{$RBL_Cache{$que}}[3], @{$RBL_Cache{$que}}[4], @{$RBL_Cache{$que}}[5], ($now - @{$RBL_Cache{$que}}[1]) );
- } else {
- mylogs "notice", "[DNSBL] ignoring unknown query $que -> $res";
+ # save result in cache
+ if ( exists($DNS_Cache{$que}) ) {
+ if ($typ eq 'A') {
+ $ttl = ( $DNS_Cache{$que}{ttl} > ($ttl||=0) ) ? $DNS_Cache{$que}{ttl} : $ttl;
+ mylogs $syslog_priority, "[DNSBL] object "
+ .( ($DNS_Cache{$que}{type} eq $COMP_RBL_KEY)
+ ? join(".", reverse(split(/\./,$DNS_Cache{$que}{value})))
+ : $DNS_Cache{$que}{value} )
+ ." listed on ".$DNS_Cache{$que}{type}.":".$DNS_Cache{$que}{name}
+ ." (answer: ".(join ", ", @addrs)
+ .", time: ".($now - $DNS_Cache{$que}{starttime})."s"
+ .", ttl: ".$ttl."s)"
+ if ( @addrs and not($opt_nodnslog) );
+ @{$DNS_Cache{$que}{A}} = @addrs;
+ $DNS_Cache{$que}{time} = $now;
+ $DNS_Cache{$que}{ttl} = $ttl;
+ } elsif ($typ eq 'TXT') {
+ $res ||= '';
+ # ugly, commas need to be escaped for set() action
+ $res =~ s/,/ /g;
+ $ttl = ( $DNS_Cache{$que}{ttl} > ($ttl||=0) ) ? $DNS_Cache{$que}{ttl} : $ttl;
+ $DNS_Cache{$que}{TXT} = $res;
+ $DNS_Cache{$que}{endtime} = $now unless $DNS_Cache{$que}{endtime};
+ $DNS_Cache{$que}{ttl} = $ttl unless $DNS_Cache{$que}{ttl};
+ };
+ } else {
+ mylogs "notice", "[DNSBL] ignoring unknown query $que";
+ };
};
};
} else {
@@ -566,15 +673,14 @@ sub rbl_read_dns {
#
# fires DNS queries
#
-sub rbl_send_dns {
+sub rbl_prepare_lookups {
my($mytype, $myval, @myrbls) = @_;
- my($now) = time;
my($myresult) = undef;
my($cmp,$rblitem,$myquery);
+ my(@lookups) = ();
# removes duplicate lookups, but keeps the specified order
- undef my %uniq;
- @myrbls = grep(!$uniq{$_}++, @myrbls);
+ @myrbls = uniq(@myrbls);
RBLQUERY: foreach (@myrbls) {
@@ -583,11 +689,7 @@ sub rbl_send_dns {
next RBLQUERY unless $rblitem;
my($myrbl, $myrblans, $myrbltime) = split /\//, $rblitem;
next RBLQUERY unless $myrbl;
- if ( ($MAX_DNSBL_TIMEOUTS > 0) and (defined $Timeouts{$myrbl}) and ($Timeouts{$myrbl} > $MAX_DNSBL_TIMEOUTS) ) {
- mylogs "notice", "[DNSQUERY] skipping $myrbl - too much timeouts ("
- .$Timeouts{$myrbl}."/".$MAX_DNSBL_TIMEOUTS.")";
- next RBLQUERY;
- };
+ next RBLQUERY if rbl_timeout($myrbl);
$myrblans = $RBL_DEFAULT unless $myrblans;
$myrbltime = $RBL_MAX_CACHE unless $myrbltime;
@@ -595,18 +697,28 @@ sub rbl_send_dns {
$myquery = $myval.".".$myrbl;
# query our cache
- if ( exists($RBL_Cache{$myquery}) and not(@{$RBL_Cache{$myquery}}[0] eq '**query**') ) {
- my($myanswer, $mystart, $myend, $m1, $m2, $m3, $m4) = @{$RBL_Cache{$myquery}};
- $myresult = ( $myanswer =~ /$myrblans/ );
- mylogs $syslog_priority, "[DNSQUERY] cached $mytype: $myrbl $myval ($myquery - $myanswer)" if ( $myresult and $opt_verbose );
+ if ( exists($DNS_Cache{$myquery}) and exists($DNS_Cache{$myquery}{A}) ) {
+ ANSWER: foreach (@{$DNS_Cache{$myquery}{A}}) {
+ last ANSWER if $myresult = ( $_ =~ /$myrblans/ );
+ };
+ mylogs $syslog_priority, "[DNSQUERY] cached $mytype: $myrbl $myval ($myquery) - answer: \'".(join ", ", @{$DNS_Cache{$myquery}{A}})."\'"
+ if ( ($myresult and $opt_verbose) or ($opt_verbose > 1) );
- # not found -> send dns query
+ # not found -> prepare dns query
} else {
- @{$RBL_Cache{$myquery}} = ('**query**', $now, $myrbltime, $myrbl, $myval, $mytype, 0);
- mylogs $syslog_priority, "[DNSQUERY] query $mytype: $myrbl $myval ($myquery)" if $opt_verbose;
- $DNS->add (\&rbl_read_dns, $myquery);
+ $DNS_Cache{$myquery} = {
+ starttime => time,
+ ttl => $myrbltime,
+ name => $myrbl,
+ value => $myval,
+ type => $mytype,
+ };
+ mylogs $syslog_priority, "[DNSQUERY] query $mytype: $myrbl $myval ($myquery)" if ($opt_verbose > 1);
+ push @lookups, $myquery;
};
};
+ # return necessary lookups
+ return @lookups;
};
#
# checks RBL items
@@ -614,7 +726,7 @@ sub rbl_send_dns {
sub rbl_check {
my($mytype,$myrbl,$myval) = @_;
my($myanswer,$myrblans,$myrbltime,$myresult,$mystart,$myend);
- my($g1,$g2,$g3,$g4,$m1,$m2,$m3,$m4,$myquery,@addrs);
+ my($m1,$m2,$myrbltype,$m4,$myrbltxt,$myquery);
my($now) = time;
# separate rbl-name and answer
@@ -626,151 +738,84 @@ sub rbl_check {
$myquery = $myval.".".$myrbl;
# query our cache
- $myresult = ( exists($RBL_Cache{$myquery}) );
+ $myresult = ( exists($DNS_Cache{$myquery}) and ($#{$DNS_Cache{$myquery}{A}} >= 0) );
if ( $myresult ) {
- ($myanswer, $mystart, $myend, $m1, $m2, $m3, $m4) = @{$RBL_Cache{$myquery}};
- $myresult = ( $myanswer =~ /$myrblans/ );
- if ( $myresult ) {
- mylogs $syslog_priority, "[DNSBL] query $myval listed on ".uc($mytype).":$myrbl (answer: $myanswer, cached: ".($now - $mystart)."s ago)"
+ ANSWER: foreach (@{$DNS_Cache{$myquery}{A}}) {
+ if ( $myresult = ( ($_) and ($_ =~ /$myrblans/)) ) {
+ mylogs $syslog_priority, "[DNSBL] query $myval listed on "
+ .uc($mytype).":$myrbl (answer: ".(join ", ", @{$DNS_Cache{$myquery}{A}})
+ .", cached: ".($now - $DNS_Cache{$myquery}{time})."s ago)"
if $opt_verbose;
- } elsif (($myanswer eq '**query**') and ($MAX_DNSBL_TIMEOUTS > 0)) {
- $Timeouts{$myrbl} = (defined $Timeouts{$myrbl})
- ? $Timeouts{$myrbl} + 1
- : 1;
- mylogs "notice", "[DNSBL] timeout for query ".uc($mytype).":$myrbl after ".($now - $mystart)." seconds (object: "
- .(($mytype eq $COMP_RBL_KEY) ? join(".", reverse(split(/\./,$m2))) : $m2).")";
+ push @DNSBL_Text, $DNS_Cache{$myquery}{type}.':'.$DNS_Cache{$myquery}{name}.':<'.($DNS_Cache{$myquery}{TXT} || '').'>'
+ if (defined $DNS_Cache{$myquery}{type} and defined $DNS_Cache{$myquery}{name});
};
- } else {
- mylogs "notice", "[OLDDNSBL] can not find cache item. will use syncronous $mytype query: $myrbl $myval ($myquery)";
- $mystart = time;
- ($g1,$g2,$g3,$g4,@addrs) = gethostbyname($myquery);
- $myend = time;
- # compare
- $myanswer = ($addrs[0]) ? join (".", unpack('C4',$addrs[0])) : "_error_";
- $myresult = ( $myanswer =~ /$myrblans/ );
- mylogs $syslog_priority, "[OLDDNSBL] client $myval listed on ".uc($mytype).":$myrbl (answer: $myanswer, time: ".($myend - $mystart)."s)"
- if $myresult;
- @{$RBL_Cache{$myval}} = ($myanswer, $mystart, $myrbltime, $myrbl, $myval, $mytype, ($myend - $mystart));
+ };
};
return $myresult;
}
-## SUB pre_plugins
+## SUB plugins
#
-# these subroutines will integrate additional attributes to
+# these subroutines integrate additional attributes to
# a request before the ruleset is evaluated
-# call: %result = pre_plugin_sub{foo}(%request)
+# call: %result = postfwd_items{foo}(%request)
# save: $result{$_}
#
-our(%pre_plugin_sub) = (
- # enables the execution of rules on the
- # specified postfwd version
- "version" => sub {
+%postfwd_items = (
+ "__builtin__" => sub {
my(%request) = @_; my(%result) = ();
- $result{$_} = $NAME." ".$VERSION;
- return %result;
- },
- "sender_localpart" => sub {
- my(%request) = @_; my(%result) = ();
- $request{sender} =~ /(.*)@[^@]*$/;
- $result{$_} = $1;
- return %result;
- },
- "sender_domain" => sub {
- my(%request) = @_; my(%result) = ();
- $request{sender} =~ /@([^@]*)$/;
- $result{$_} = $1;
- return %result;
- },
- "recipient_localpart" => sub {
- my(%request) = @_; my(%result) = ();
- $request{recipient} =~ /(.*)@[^@]*$/;
- $result{$_} = $1;
- return %result;
- },
- "recipient_domain" => sub {
- my(%request) = @_; my(%result) = ();
- $request{recipient} =~ /@([^@]*)$/;
- $result{$_} = $1;
- return %result;
- },
- "reverse_address" => sub {
- my(%request) = @_; my(%result) = ();
- $result{$_} = (join(".", reverse(split(/\./,$request{client_address}))));
+ # postfwd version
+ $result{version} = $NAME." ".$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;
},
);
# returns additional request information
-# for all pre_plugins
-sub pre_plugin {
+# for all postfwd_items
+sub postfwd_items {
my(%request) = @_;
my(%result) = ();
- foreach (keys %pre_plugin_sub) {
- %result = (%result, &{$pre_plugin_sub{$_}}(%request))
- if (defined $pre_plugin_sub{$_});
- };
- map { $result{$_} = '' unless $result{$_} } (keys %result);
- return %result;
-};
-
-
-### SUB ruleset
-
-#
-# get a rule number by id
-#
-sub get_rule_by_id {
- my($id) = @_;
- my($matched,$myresult) = "";
- my($index);
-
- RULE: for $index (0 .. $#Rules) {
- next unless exists $Rules[$index];
- $matched = ( $id eq $Rules[$index]{$COMP_ID} );
- $myresult = $index if $matched;
- last RULE if $matched;
- };
- return $myresult;
-}
-#
-# 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 (defined $request{$var}) {
- $var = $request{$var};
- # substitute dangerous characters
- $var =~ s/([^-\w\s])/\\$1/g if ( $cmp =~ /~/ );
- $myresult=$val=$pre.$var.$post;
- };
- mylogs $syslog_priority, "substitute : \"$myitem\" \"$cmp\" \"$val\""
+ foreach (sort keys %postfwd_items) {
+ mylogs $syslog_priority, "[PLUGIN] executing postfwd-item ".$_
if ($opt_verbose > 1);
+ %result = (%result, &{$postfwd_items{$_}}((%request,%result)))
+ if (defined $postfwd_items{$_});
};
- return $myresult;
+ map { $result{$_} = '' unless $result{$_}; mylogs $syslog_priority, "[PLUGIN] Added key: $_=$result{$_}" if ($opt_verbose > 1) } (keys %result);
+ return %result;
};
#
# compare item subroutines
# must take compare_item_foo ( $COMPARE_TYPE, $RULEITEM, $REQUESTITEM, %REQUEST, %REQUESTINFO );
#
-%compare = (
+%postfwd_compare = (
"cidr" => sub {
my($cmp,$val,$myitem,%request) = @_;
- my($myresult) = undef;
+ my($myresult) = ($val and $myitem);
mylogs $syslog_priority, "type cidr : \"$myitem\" \"$cmp\" \"$val\"" if ($opt_verbose > 1);
- my $myref = Net::CIDR::Lite->new($val);
- $myresult = ( $myref->find($myitem) );
+ if ($myresult) {
+ $val .= '/32' unless ($val =~ /\/\d{1,2}$/);
+ $myresult = cidr_match((cidr_parse($val)),$myitem);
+ };
+ return $myresult;
+ },
+ # ip address lists, type 'cidr'
+ "cidrlist" => sub {
+ my($cmp,$val,$myitem,%request) = @_;
+ my($myresult) = undef;
+ REPLY: foreach (@{$myitem}) {
+ $myresult = ( &{$postfwd_compare{cidr}}(($cmp,$val,$_,%request)) );
+ last REPLY if $myresult;
+ };
return $myresult;
},
"numeric" => sub {
@@ -807,7 +852,7 @@ sub devar_item {
my($imon) = (split (',', $myitem))[4]; $imon ||= 0;
my($rmin,$rmax) = split ('-', $val);
$rmin = ($rmin) ? (($rmin =~ /^\d$/) ? $rmin : $months{$rmin}) : $imon;
- $rmax = ($rmax) ? (($rmax =~ /^\d$/) ? $rmax : $months{$rmax}) : $imon;
+ $rmax = ($rmax) ? (($rmax =~ /^\d$/) ? $rmax : $months{$rmax}) : (($val =~ /-/) ? $imon : $rmin);
mylogs $syslog_priority, "type months : \"$imon\" \"$cmp\" \"$rmin\"-\"$rmax\""
if ($opt_verbose > 1);
$myresult = (($rmin <= $imon) and ($rmax >= $imon));
@@ -819,7 +864,7 @@ sub devar_item {
my($iday) = (split (',', $myitem))[6]; $iday ||= 0;
my($rmin,$rmax) = split ('-', $val);
$rmin = ($rmin) ? (($rmin =~ /^\d$/) ? $rmin : $weekdays{$rmin}) : $iday;
- $rmax = ($rmax) ? (($rmax =~ /^\d$/) ? $rmax : $weekdays{$rmax}) : $iday;
+ $rmax = ($rmax) ? (($rmax =~ /^\d$/) ? $rmax : $weekdays{$rmax}) : (($val =~ /-/) ? $iday : $rmin);
mylogs $syslog_priority, "type days : \"$iday\" \"$cmp\" \"$rmin\"-\"$rmax\""
if ($opt_verbose > 1);
$myresult = (($rmin <= $iday) and ($rmax >= $iday));
@@ -829,10 +874,10 @@ sub devar_item {
my($cmp,$val,$myitem,%request) = @_;
my($myresult) = undef;
my($isec,$imin,$ihour,$iday,$imon,$iyear) = split (',', $myitem);
- my($rmin,$rmax) = split ('-', $val); my($idat);
- $idat = ($iyear + 1900) . ((($imon+1) < 10) ? '0'.($imon+1) : ($imon+1)) . (($iday < 10) ? '0'.$iday : $iday);
+ my($rmin,$rmax) = split ('-', $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)) : $idat;
+ $rmax = ($rmax) ? join ('', reverse split ('\.', $rmax)) : (($val =~ /-/) ? $idat : $rmin);
mylogs $syslog_priority, "type date : \"$idat\" \"$cmp\" \"$rmin\"-\"$rmax\""
if ($opt_verbose > 1);
$myresult = (($rmin <= $idat) and ($rmax >= $idat));
@@ -842,10 +887,10 @@ sub devar_item {
my($cmp,$val,$myitem,%request) = @_;
my($myresult) = undef;
my($isec,$imin,$ihour,$iday,$imon,$iyear) = split (',', $myitem);
- my($rmin,$rmax) = split ('-', $val); my($idat);
- $idat = (($ihour < 10) ? '0'.$ihour : $ihour) . (($imin < 10) ? '0'.$imin : $imin) . (($isec < 10) ? '0'.$isec : $isec);
+ my($rmin,$rmax) = split ('-', $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)) : $idat;
+ $rmax = ($rmax) ? join ('', split ('\:', $rmax)) : (($val =~ /-/) ? $idat : $rmin);
mylogs $syslog_priority, "type time : \"$idat\" \"$cmp\" \"$rmin\"-\"$rmax\""
if ($opt_verbose > 1);
$myresult = (($rmin <= $idat) and ($rmax >= $idat));
@@ -855,8 +900,6 @@ sub devar_item {
my($cmp,$val,$myitem,%request) = @_;
my($var,$myresult) = undef;
mylogs $syslog_priority, "type default : \"$myitem\" \"$cmp\" \"$val\"" if ($opt_verbose > 1);
- # substitute check for $$vars in action
- $val = $var if ( $var = devar_item ($cmp,$val,$myitem,%request) );
# backward compatibility
$cmp = '==' if ( ($var) and ($cmp eq '=') );
if ($cmp eq '==') {
@@ -872,28 +915,229 @@ sub devar_item {
};
return $myresult;
},
- "client_address" => sub { return &{$compare{"cidr"}}(@_); },
- "encryption_keysize" => sub { return &{$compare{"numeric"}}(@_); },
- "size" => sub { return &{$compare{"numeric"}}(@_); },
- "recipient_count" => sub { return &{$compare{"numeric"}}(@_); },
- "request_score" => sub { return &{$compare{"numeric"}}(@_); },
- $COMP_RHSBL_KEY_CLIENT => sub { return &{$compare{$COMP_RHSBL_KEY}}(@_); },
- $COMP_RHSBL_KEY_SENDER => sub { return &{$compare{$COMP_RHSBL_KEY}}(@_); },
- $COMP_RHSBL_KEY_RCLIENT => sub { return &{$compare{$COMP_RHSBL_KEY}}(@_); },
+ # string lists, type: 'default'
+ "deflist" => sub {
+ my($cmp,$val,$myitem,%request) = @_;
+ my($myresult) = undef;
+ REPLY: foreach (@{$myitem}) {
+ $myresult = ( &{$postfwd_compare{default}}(($cmp,$val,$_,%request)) );
+ last REPLY if $myresult;
+ };
+ 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) = $default_action; my($stop) = 0;
+ # ...
+ # return ($stop,$index,$myaction,$myline,%request);
+ # },
+ # jump() command
+ "jump" => sub {
+ my($index,$now,$mycmd,$myarg,$myline,%request) = @_;
+ my($myaction) = $default_action; my($stop) = 0;
+ if (defined $Rule_by_ID{$myarg}) {
+ my($ruleno) = $Rule_by_ID{$myarg};
+ mylogs $syslog_priority, "[RULES] ".$myline
+ .", jump to rule $ruleno (id $myarg)"
+ if $opt_verbose;
+ $index = $ruleno - 1;
+ } else {
+ 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) = $default_action; 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})
+ ? mylogs "notice", "[RULES] ".$myline.", redefining existing ".$r_var."=".$request{$r_var}." with ".$r_var."=".$m_val
+ : mylogs $syslog_priority, "[RULES] ".$myline.", defining ".$r_var."=".$m_val
+ if $opt_verbose;
+ $request{$r_var} = $m_val;
+ } else {
+ 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) = $default_action; 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?)?/ );
+ mylogs $syslog_priority, "[SCORE] ".$myline.", modifying score about ".$myarg." points to ". $score
+ if $opt_verbose;
+ $request{score} = $request{request_score} = $score;
+ } elsif ($myarg) {
+ warn "[RULES] ".$myline.", invalid value for score \"$myarg\" - ignoring";
+ };
+ MAXSCORE: foreach my $max_score (reverse sort keys %MAX_SCORES) {
+ if ( ($score >= $max_score) and ($MAX_SCORES{$max_score}) ) {
+ $myaction=$MAX_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) = $default_action; my($stop) = 0;
+ my($ratetype,$ratecount,$ratetime,$ratecmd) = split "/", $myarg, 4;
+ if ($ratetype and $ratecount and $ratetime and $ratecmd) {
+ unless ( defined $Rates{$ratetype} ) {
+ $Rates{$ratetype} = {
+ type => $mycmd,
+ maxcount => $ratecount,
+ ttl => $ratetime,
+ count => ( ($mycmd eq 'size') ? $request{size} : 1 ),
+ time => $now,
+ rule => $Rules[$index]{$COMP_ID},
+ action => $ratecmd,
+ };
+ mylogs $syslog_priority, "[RULES] ".$myline
+ .", creating rate object ".$ratetype
+ ." [type: ".$mycmd.", max: ".$ratecount.", time: ".$ratetime."s]"
+ if ($opt_verbose > 1);
+ };
+ } else {
+ mylogs "notice", "[RULES] ".$myline.", ignoring unknown ".$mycmd."() attribute \'".$myarg."\'";
+ };
+ return ($stop,$index,$myaction,$myline,%request);
+ },
+ # size() command
+ "size" => sub { return &{$postfwd_actions{rate}}(@_); },
+ # wait() command
+ "wait" => sub {
+ my($index,$now,$mycmd,$myarg,$myline,%request) = @_;
+ my($myaction) = $default_action; my($stop) = 0;
+ mylogs $syslog_priority, "[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) = $default_action; my($stop) = 0;
+ mylogs $syslog_priority, "[RULES] ".$myline." - note: ".$myarg if $myarg;
+ return ($stop,$index,$myaction,$myline,%request);
+ },
+ # quit() command
+ "quit" => sub {
+ my($index,$now,$mycmd,$myarg,$myline,%request) = @_;
+ warn "[RULES] ".$myline." - critical: quit (".$myarg.")";
+ end_program;
+ },
+ # file() command
+ "file" => sub {
+ my($index,$now,$mycmd,$myarg,$myline,%request) = @_;
+ my($myaction) = $default_action; my($stop) = 0;
+ warn "[RULES] ".$myline." - error: command ".$mycmd."() has not been implemented yet - ignoring";
+ 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 ) {
+ 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 { mylogs "notice", "[PLUGIN] overriding prior item \'".$_."\'" if (defined $postfwd_items{$_}) } (keys %postfwd_items_plugin);
+ map { mylogs "notice", "[PLUGIN] overriding prior compare function \'".$_."\'" if (defined $postfwd_compare{$_}) } (keys %postfwd_compare_plugin);
+ map { mylogs "notice", "[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;
+ mylogs $syslog_priority, $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,$cmp,$neg,$myresult,$compare_proc);
+ my($val,,$var,$cmp,$neg,$myresult,$postfwd_compare_proc);
my($rcount) = 0;
$mymin ||= 1;
#
# determine the right compare function
- $compare_proc = (defined $compare{$mykey}) ? $mykey : "default";
+ $postfwd_compare_proc = (defined $postfwd_compare{$mykey}) ? $mykey : "default";
#
# now compare request to every single item
ITEM: foreach (@{$mymask}) {
@@ -903,7 +1147,9 @@ sub compare_item {
$val = $neg if ($neg = deneg_item($val));
mylogs $syslog_priority, "deneg $mykey: \"$myitem\" \"$cmp\" \"$val\"" if ($neg and ($opt_verbose > 1));
next ITEM unless $val;
- $myresult = &{$compare{$compare_proc}}($cmp,$val,$myitem,%request);
+ # substitute check for $$vars in action
+ $val = $var if ( $var = devar_item ($cmp,$val,$myitem,%request) );
+ $myresult = &{$postfwd_compare{$postfwd_compare_proc}}($cmp,$val,$myitem,%request);
mylogs $syslog_priority, "match $mykey: ".($myresult ? "TRUE" : "FALSE") if ($opt_verbose > 1);
if ($neg) {
$myresult = not($myresult);
@@ -923,15 +1169,30 @@ sub compare_item {
#
sub compare_rule {
my($index,$date,%request) = @_;
+ 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})
+ exists($Rules[$index]{$COMP_RHSBL_KEY_CLIENT}) or exists($Rules[$index]{$COMP_RHSBL_KEY_SENDER}) or
+ exists($Rules[$index]{$COMP_RHSBL_KEY_HELO})
);
- my($hasdns) = ( not($opt_nodns) and ($has_rhl or exists($Rules[$index]{$COMP_RBL_KEY})) );
- my($mykey,$myitem,$val,$cmp,$res,$myrip,$myline) = undef;
+ 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($opt_nodns) and ($has_senderdns or $has_rhl or $has_rbl) );
+ my($mykey,$myitem,$val,$cmp,$res,$myline,$timed) = undef;
my(@myresult) = (0,0,0);
- my(@queries) = ();
+ my(@queries,@timedout) = ();
my($num) = 1;
+ undef @DNSBL_Text;
+
+ # prepare dns queries
+ my $ownres = Net::DNS::Resolver->new;
+ my $ownsel = IO::Select->new;
+ my %ownsock = ();
+ my @ownready = ();
+ my $bgsock = undef;
mylogs $syslog_priority, "rule: $index, id: $Rules[$index]{$COMP_ID}" if ($opt_verbose > 1);
@@ -940,20 +1201,41 @@ sub compare_rule {
# if they are not contained already,
# and $opt_nodns is not set
if ($hasdns) {
- if ( exists($Rules[$index]{$COMP_RBL_KEY}) ) {
- $myrip = (defined $request{"reverse_address"})
- ? $request{"reverse_address"}
- : (join(".", reverse(split(/\./,$request{"client_address"}))));
- rbl_send_dns ( $COMP_RBL_KEY, $myrip, @{$Rules[$index]{$COMP_RBL_KEY}} );
+
+ map { $timed .= (($timed) ? ", $_" : $_) if $Timeouts{$_} > $MAX_DNSBL_TIMEOUTS } (keys %Timeouts);
+ mylogs "notice", "[DNSQUERY] 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 ( exists($Rules[$index]{$COMP_RBL_KEY}) );
+ push @queries, rbl_prepare_lookups ( $COMP_RHSBL_KEY, $request{client_name}, @{$Rules[$index]{$COMP_RHSBL_KEY}} )
+ if ( exists($Rules[$index]{$COMP_RHSBL_KEY}) and not($request{client_name} eq "unknown") );
+ push @queries, rbl_prepare_lookups ( $COMP_RHSBL_KEY_CLIENT, $request{client_name}, @{$Rules[$index]{$COMP_RHSBL_KEY_CLIENT}} )
+ if ( exists($Rules[$index]{$COMP_RHSBL_KEY_CLIENT}) and not($request{client_name} eq "unknown") );
+ push @queries, rbl_prepare_lookups ( $COMP_RHSBL_KEY_SENDER, $request{sender_domain}, @{$Rules[$index]{$COMP_RHSBL_KEY_SENDER}} )
+ if ( exists($Rules[$index]{$COMP_RHSBL_KEY_SENDER}) and not($request{sender_domain} eq "") );
+ push @queries, rbl_prepare_lookups ( $COMP_RHSBL_KEY_HELO, $request{helo_name}, @{$Rules[$index]{$COMP_RHSBL_KEY_HELO}} )
+ if ( exists($Rules[$index]{$COMP_RHSBL_KEY_HELO}) and not($request{helo_name} eq "") );
+ push @queries, rbl_prepare_lookups ( $COMP_RHSBL_KEY_RCLIENT, $request{reverse_client_name}, @{$Rules[$index]{$COMP_RHSBL_KEY_RCLIENT}} )
+ if ( exists($Rules[$index]{$COMP_RHSBL_KEY_RCLIENT}) and not($request{reverse_client_name} eq "unknown") );
+
+ # send dns queries
+ if ( @queries ) {
+ @queries = uniq(@queries);
+ foreach my $query (@queries) {
+ mylogs $syslog_priority, "[SENDDNS] sending query \'$query\'"
+ if ($opt_verbose > 1);
+ # send A query
+ $bgsock = $ownres->bgsend($query, 'A');
+ $ownsel->add($bgsock);
+ $ownsock{$bgsock} = 'A:'.$query;
+ # send TXT query
+ $bgsock = $ownres->bgsend($query, 'TXT');
+ $ownsel->add($bgsock);
+ $ownsock{$bgsock} = 'TXT:'.$query;
+ };
+ mylogs $syslog_priority, "[SENDDNS] rule: $index, id: $Rules[$index]{$COMP_ID}, lookups: ".($#queries + 1)
+ if ($opt_verbose > 1);
};
- rbl_send_dns ( $COMP_RHSBL_KEY, $request{"client_name"}, @{$Rules[$index]{$COMP_RHSBL_KEY}} )
- if ( exists($Rules[$index]{$COMP_RHSBL_KEY}) and not($request{"client_name"} eq "unknown") );
- rbl_send_dns ( $COMP_RHSBL_KEY_CLIENT, $request{"client_name"}, @{$Rules[$index]{$COMP_RHSBL_KEY_CLIENT}} )
- if ( exists($Rules[$index]{$COMP_RHSBL_KEY_CLIENT}) and not($request{"client_name"} eq "unknown") );
- rbl_send_dns ( $COMP_RHSBL_KEY_SENDER, $request{"sender_domain"}, @{$Rules[$index]{$COMP_RHSBL_KEY_SENDER}} )
- if ( exists($Rules[$index]{$COMP_RHSBL_KEY_SENDER}) and not($request{"sender_domain"} eq "") );
- rbl_send_dns ( $COMP_RHSBL_KEY_RCLIENT, $request{"reverse_client_name"}, @{$Rules[$index]{$COMP_RHSBL_KEY_RCLIENT}} )
- if ( exists($Rules[$index]{$COMP_RHSBL_KEY_RCLIENT}) and not($request{"reverse_client_name"} eq "unknown") );
};
# COMPARE-ITEMS
@@ -966,7 +1248,7 @@ sub compare_rule {
};
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)) );
+ 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) {
@@ -987,14 +1269,43 @@ sub compare_rule {
# if all other items matched, run await()
# and check the results unless $opt_nodns
if ($hasdns) {
- $DNS->await();
+ my($ownstart) = time();
+ my($timout) = $dns_timeout || $def_dns_timeout;
+ while ((scalar keys %ownsock) and (@ownready = $ownsel->can_read($timout))) {
+ foreach my $sock (@ownready) {
+ if (defined $ownsock{$sock}) {
+ mylogs ('notice', "[DNSBL] ANSWER FROM ".$ownsock{$sock})
+ if ($opt_verbose > 1);
+ my $packet = $ownres->bgread($sock);
+ rbl_read_dns ($packet);
+ delete $ownsock{$sock};
+ } else {
+ $ownsel->remove($sock);
+ $sock = undef;
+ };
+ };
+ };
+ # timeout handling
+ map { push @timedout, (split ':', $ownsock{$_})[1] } (keys %ownsock);
+ foreach (uniq(@timedout)) {
+ # @{$DNS_Cache{$_}{A}} = ('__TIMEOUT__');
+ $DNS_Cache{$_}{endtime} = time();
+ $DNS_Cache{$_}{ttl} = $RBL_MAX_CACHE;
+ $Timeouts{$DNS_Cache{$_}{name}} = (defined $Timeouts{$DNS_Cache{$_}{name}})
+ ? $Timeouts{$DNS_Cache{$_}{name}} + 1
+ : 1
+ if ( $MAX_DNSBL_TIMEOUTS > 0 );
+ mylogs ('notice', "[DNSBL] warning: timeout (".$Timeouts{$DNS_Cache{$_}{name}}."/".$MAX_DNSBL_TIMEOUTS.") for ".$DNS_Cache{$_}{name}." after ".(time() - $ownstart)." seconds");
+ };
+
+ # 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),
- $myrip,
+ $request{reverse_address},
%request
);
$myresult[0] = ($res or ($Rules[$index]{$COMP_RBL_CNT} eq 'all')) ? ($myresult[0] + $res) : 0;
@@ -1003,7 +1314,7 @@ sub compare_rule {
if ( $has_rhl and ($myresult[0] > 0) ) {
if ( exists($Rules[$index]{$COMP_RHSBL_KEY}) ) {
- if ($request{"client_name"} eq "unknown") {
+ if ($request{client_name} eq "unknown") {
$myresult[0] = (defined $Rules[$index]{$COMP_RHSBL_CNT})
? ( ($Rules[$index]{$COMP_RHSBL_CNT} eq 'all') ? 1 : 0 )
: 0;
@@ -1012,7 +1323,7 @@ sub compare_rule {
$COMP_RHSBL_KEY,
$Rules[$index]{$COMP_RHSBL_KEY},
($Rules[$index]{$COMP_RHSBL_CNT} ||= 1),
- $request{"client_name"},
+ $request{client_name},
%request
);
$myresult[0] = ($res or ($Rules[$index]{$COMP_RHSBL_CNT} eq 'all')) ? ($myresult[0] + $res) : 0;
@@ -1020,7 +1331,7 @@ sub compare_rule {
};
};
if ( exists($Rules[$index]{$COMP_RHSBL_KEY_CLIENT}) ) {
- if ($request{"client_name"} eq "unknown") {
+ if ($request{client_name} eq "unknown") {
$myresult[0] = (defined $Rules[$index]{$COMP_RHSBL_CNT})
? ( ($Rules[$index]{$COMP_RHSBL_CNT} eq 'all') ? 1 : 0 )
: 0;
@@ -1029,7 +1340,7 @@ sub compare_rule {
$COMP_RHSBL_KEY_CLIENT,
$Rules[$index]{$COMP_RHSBL_KEY_CLIENT},
($Rules[$index]{$COMP_RHSBL_CNT} ||= 1),
- $request{"client_name"},
+ $request{client_name},
%request
);
$myresult[0] = ($res or ($Rules[$index]{$COMP_RHSBL_CNT} eq 'all')) ? ($myresult[0] + $res) : 0;
@@ -1037,7 +1348,7 @@ sub compare_rule {
};
};
if ( exists($Rules[$index]{$COMP_RHSBL_KEY_SENDER}) ) {
- if ($request{"sender_domain"} eq "") {
+ if ($request{sender_domain} eq "") {
$myresult[0] = (defined $Rules[$index]{$COMP_RHSBL_CNT})
? ( ($Rules[$index]{$COMP_RHSBL_CNT} eq 'all') ? 1 : 0 )
: 0;
@@ -1046,7 +1357,24 @@ sub compare_rule {
$COMP_RHSBL_KEY_SENDER,
$Rules[$index]{$COMP_RHSBL_KEY_SENDER},
($Rules[$index]{$COMP_RHSBL_CNT} ||= 1),
- $request{"sender_domain"},
+ $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}) ) {
+ if ($request{helo_domain} eq "") {
+ $myresult[0] = (defined $Rules[$index]{$COMP_RHSBL_CNT})
+ ? ( ($Rules[$index]{$COMP_RHSBL_CNT} eq 'all') ? 1 : 0 )
+ : 0;
+ } else {
+ $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;
@@ -1054,7 +1382,7 @@ sub compare_rule {
};
};
if ( exists($Rules[$index]{$COMP_RHSBL_KEY_RCLIENT}) ) {
- if ($request{"reverse_client_name"} eq "unknown") {
+ if ($request{reverse_client_name} eq "unknown") {
$myresult[0] = (defined $Rules[$index]{$COMP_RHSBL_CNT})
? ( ($Rules[$index]{$COMP_RHSBL_CNT} eq 'all') ? 1 : 0 )
: 0;
@@ -1063,7 +1391,7 @@ sub compare_rule {
$COMP_RHSBL_KEY_RCLIENT,
$Rules[$index]{$COMP_RHSBL_KEY_RCLIENT},
($Rules[$index]{$COMP_RHSBL_CNT} ||= 1),
- $request{"reverse_client_name"},
+ $request{reverse_client_name},
%request
);
$myresult[0] = ($res or ($Rules[$index]{$COMP_RHSBL_CNT} eq 'all')) ? ($myresult[0] + $res) : 0;
@@ -1076,6 +1404,7 @@ sub compare_rule {
$myline = "[RULES] RULE: ".$index." MATCHES: ".((($myresult[0] - 2) > 0) ? ($myresult[0] - 2) : 0);
$myline .= " RBLCOUNT: ".$myresult[1] if ($myresult[1] > 0);
$myline .= " RHSBLCOUNT: ".$myresult[2] if ($myresult[2] > 0);
+ $myline .= " DNSBLTEXT: ".(join ("; ", @DNSBL_Text)) if ( (defined @DNSBL_Text) and (($myresult[1] > 0) or ($myresult[2] > 0)) );
mylogs $syslog_priority, $myline;
};
return @myresult;
@@ -1088,18 +1417,22 @@ sub compare_rule {
# access policy routine
#
sub smtpd_access_policy {
- my(%myattr) = @_;
- my($myaction) = $default_action;
- my($index) = 1;
- my($now) = time;
- my($date) = join(',', localtime($now));
- my(@hits) = ();
- my($matched,$rblcnt,$rhlcnt,$t1,$t2,$t3) = 0;
- my($mykey,$cacheid,$myline,$checkreq) = "";
- my($rdat,$rcnt,$ratecount,$ratetime,$ratecmd,$rateid,$ratetype,$ratehit) = undef;
+ my(%request) = @_;
+ my($myaction) = $default_action;
+ my($index) = 1;
+ my($now) = time;
+ my($date) = join(',', localtime($now));
+ my($matched,$rblcnt,$rhlcnt,$t1,$t2,$t3,$stop) = 0;
+ my($mykey,$cacheid,$myline,$checkreq,$var,$ratehit) = "";
# replace empty sender with <>
- $myattr{"sender"} = '<>' unless ($myattr{"sender"});
+ $request{sender} = '<>' unless ($request{sender});
+
+ # load postfwd_items attributes
+ if ( my(%postfwd_items_attr) = postfwd_items (%request) ) {
+ %request = (%request, %postfwd_items_attr);
+ };
+
# check for HUP signal
if ( $Reload_Conf ) {
@@ -1119,7 +1452,7 @@ sub smtpd_access_policy {
if ( ($CLEANUP_RATE_CACHE > 0) and (scalar keys %Rates > 0) and (($now - $Cleanup_Rates) > $CLEANUP_RATE_CACHE) ) {
$t1 = time;
$t3 = scalar keys %Rates;
- rate_cache_cleanup($now);
+ cleanup_rate_cache($now);
$t2 = time;
mylogs $syslog_priority, "[CLEANUP] needed ".($t2 - $t1)
." seconds for rate cleanup of "
@@ -1130,19 +1463,27 @@ sub smtpd_access_policy {
};
# increase rate limits
- RATES: foreach $checkreq (keys %myattr) {
- next RATES unless ( $myattr{$checkreq} ); next RATES unless ( defined $Rates{$myattr{$checkreq}} );
- ($ratetype, $ratecount, $ratetime, $rcnt, $rdat, $rateid, $ratecmd) = @{$Rates{$myattr{$checkreq}}};
- if ( ($now - $rdat) > $ratetime ) {
- $rcnt = 0;
- $Rates{$myattr{$checkreq}} = [ $ratetype, $ratecount, $ratetime, ( ($ratetype eq 'size') ? $myattr{"size"} : 1 ), $now, $rateid, $ratecmd ];
- mylogs $syslog_priority, "[RATE] renewing rate object ".$myattr{$checkreq}." [type: ".$ratetype.", max: ".$ratecount.", time: ".$ratetime."s]"
+ RATES: foreach $checkreq (keys %request) {
+ next RATES unless ( $request{$checkreq} and (defined $Rates{$request{$checkreq}}) );
+ if ( ($now - $Rates{$request{$checkreq}}{time}) > $Rates{$request{$checkreq}}{ttl} ) {
+ # renew rate
+ $Rates{$request{$checkreq}}{count} = ( ($Rates{$request{$checkreq}}{type} eq 'size') ? $request{size} : 1 );
+ $Rates{$request{$checkreq}}{time} = $now;
+ mylogs $syslog_priority, "[RATE] renewing rate object ".$request{$checkreq}
+ ." [type: ".$Rates{$request{$checkreq}}{type}
+ .", max: ".$Rates{$request{$checkreq}}{maxcount}
+ .", time: ".$Rates{$request{$checkreq}}{ttl}."s]"
if ($opt_verbose > 1);
} else {
- $Rates{$myattr{$checkreq}} = [ $ratetype, $ratecount, $ratetime, ( ($ratetype eq 'size') ? ($rcnt+=$myattr{"size"}) : ++$rcnt ), $rdat, $rateid, $ratecmd ];
- mylogs $syslog_priority, "[RATE] increasing rate object ".$myattr{$checkreq}." to ".$rcnt." [type: ".$ratetype.", max: ".$ratecount.", time: ".$ratetime."s]"
+ # increase rate
+ $Rates{$request{$checkreq}}{count} += ( ($Rates{$request{$checkreq}}{type} eq 'size') ? $request{size} : 1 );
+ mylogs $syslog_priority, "[RATE] increasing rate object ".$request{$checkreq}
+ ." to ".$Rates{$request{$checkreq}}{count}
+ ." [type: ".$Rates{$request{$checkreq}}{type}
+ .", max: ".$Rates{$request{$checkreq}}{maxcount}
+ .", time: ".$Rates{$request{$checkreq}}{ttl}."s]"
if ($opt_verbose > 1);
- $ratehit = $checkreq if ($rcnt > $ratecount);
+ $ratehit = $checkreq if ($Rates{$request{$checkreq}}{count} > $Rates{$request{$checkreq}}{maxcount});
last RATES if $ratehit;
};
};
@@ -1152,19 +1493,17 @@ sub smtpd_access_policy {
# construct cache identifier
if (@CacheID) {
- map { $cacheid .= $myattr{$_}.";" if (defined $myattr{$_}) } @CacheID;
+ map { $cacheid .= $request{$_}.";" if (defined $request{$_}) } @CacheID;
} else {
- REQITEM: foreach $checkreq (sort keys %myattr) {
- next REQITEM unless $myattr{$checkreq};
+ REQITEM: foreach $checkreq (sort keys %request) {
+ next REQITEM unless $request{$checkreq};
next REQITEM if ( ($checkreq eq "instance") or ($checkreq eq "queue_id") );
- next REQITEM if ( defined $pre_plugin_sub{$checkreq} );
next REQITEM if ( $opt_cache_no_size and ($checkreq eq "size") );
next REQITEM if ( $opt_cache_no_sender and ($checkreq eq "sender") );
if ( $opt_cache_rdomain_only and ($checkreq eq "recipient") ) {
- $myattr{$checkreq} =~ /@([^@]+)$/;
- $cacheid .= $1.";" if $1;
+ $cacheid .= $request{recipient_domain}.";";
} else {
- $cacheid .= $myattr{$checkreq}.";";
+ $cacheid .= $request{$checkreq}.";";
};
};
};
@@ -1174,7 +1513,7 @@ sub smtpd_access_policy {
if ( (scalar keys %Request_Cache > 0) and (($now - $Cleanup_Requests) > $CLEANUP_REQUEST_CACHE) ) {
$t1 = time;
$t3 = scalar keys %Request_Cache;
- request_cache_cleanup($now);
+ cleanup_request_cache($now);
$t2 = time;
mylogs $syslog_priority, "[CLEANUP] needed ".($t2 - $t1)
." seconds for request cleanup of "
@@ -1189,46 +1528,47 @@ sub smtpd_access_policy {
if ( $ratehit ) {
$Counter_Rates++;
- $Matches{$rateid}++;
- $myaction = $ratecmd;
- mylogs $syslog_priority, "[RATE] rule=".get_rule_by_id ($rateid)
- . ", id=".$rateid
- . ", client=".$myattr{"client_name"}."[".$myattr{"client_address"}."]"
- . ", sender=<".(($myattr{"sender"} eq '<>') ? "" : $myattr{"sender"}).">"
- . ", recipient=<".$myattr{"recipient"}.">"
- . ", helo=<".$myattr{"helo_name"}.">"
- . ", proto=".$myattr{"protocol_name"}
- . ", state=".$myattr{"protocol_state"}
+ $Matches{$Rates{$request{$ratehit}}{rule}}++;
+ $myaction = $Rates{$request{$ratehit}}{action};
+ mylogs $syslog_priority, "[RATE] rule=".$Rule_by_ID{$Rates{$request{$ratehit}}{rule}}
+ . ", id=".$Rates{$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: ".$myattr{$ratehit}.", type: ".$ratetype.", count: ".$rcnt."/".$ratecount.", time: ".($now - $rdat)."/".$ratetime."s)";
+ . ", action=".$myaction." (item: ".$request{$ratehit}
+ . ", type: ".$Rates{$request{$ratehit}}{type}
+ . ", count: ".$Rates{$request{$ratehit}}{count}."/".$Rates{$request{$ratehit}}{maxcount}
+ . ", time: ".($now - $Rates{$request{$ratehit}}{time})."/".$Rates{$request{$ratehit}}{ttl}."s)";
# check cache
} elsif ( ($REQUEST_MAX_CACHE > 0)
- and ((exists($Request_Cache{$cacheid}{$COMP_ACTION})) and (($now - $Request_Cache{$cacheid}{"time"}) <= $REQUEST_MAX_CACHE)) ) {
+ and ((exists($Request_Cache{$cacheid}{$COMP_ACTION})) and (($now - $Request_Cache{$cacheid}{time}) <= $REQUEST_MAX_CACHE)) ) {
$Counter_Hits++;
$myaction = $Request_Cache{$cacheid}{$COMP_ACTION};
- if ( $Request_Cache{$cacheid}{"hit"} ) {
+ if ( $Request_Cache{$cacheid}{hit} ) {
$Matches{$Request_Cache{$cacheid}{$COMP_ID}}++;
- mylogs $syslog_priority, "[CACHE] rule=".get_rule_by_id ($Request_Cache{$cacheid}{$COMP_ID})
+ mylogs $syslog_priority, "[CACHE] rule=".$Rule_by_ID{$Request_Cache{$cacheid}{$COMP_ID}}
. ", id=".$Request_Cache{$cacheid}{$COMP_ID}
- . ", client=".$myattr{"client_name"}."[".$myattr{"client_address"}."]"
- . ", sender=<".(($myattr{"sender"} eq '<>') ? "" : $myattr{"sender"}).">"
- . ", recipient=<".$myattr{"recipient"}.">"
- . ", helo=<".$myattr{"helo_name"}.">"
- . ", proto=".$myattr{"protocol_name"}
- . ", state=".$myattr{"protocol_state"}
+ . ", 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"}
+ . ", hits=".$Request_Cache{$cacheid}{hits}
. ", action=".$Request_Cache{$cacheid}{$COMP_ACTION};
};
# check rules
} else {
- my($score) = 0; my($var) = '';
-
# refresh config if '-I' was set
read_config if $opt_instantconfig;
@@ -1237,182 +1577,69 @@ sub smtpd_access_policy {
} else {
- # load pre_plugin attributes
- if ( my(%pre_plugin_attr) = pre_plugin (%myattr) ) {
- %myattr = (%myattr, %pre_plugin_attr);
- map {mylogs $syslog_priority, "[PRE-PLUGIN] Add key: $_=$pre_plugin_attr{$_}" } (keys %pre_plugin_attr)
- if ($opt_verbose > 1);
- };
-
# clean up rbl cache
- if ( not($opt_nodns) and (scalar keys %RBL_Cache > 0) and (($now - $Cleanup_RBLs) > $CLEANUP_RBL_CACHE) ) {
+ if ( not($opt_nodns) and (scalar keys %DNS_Cache > 0) and (($now - $Cleanup_RBLs) > $CLEANUP_RBL_CACHE) ) {
$t1 = time;
- $t3 = scalar keys %RBL_Cache;
- rbl_cache_cleanup($now);
+ $t3 = scalar keys %DNS_Cache;
+ cleanup_dns_cache($now);
$t2 = time;
mylogs $syslog_priority, "[CLEANUP] needed ".($t2 - $t1)
." seconds for rbl cleanup of "
- .($t3 - scalar keys %RBL_Cache)." out of ".$t3
+ .($t3 - scalar keys %DNS_Cache)." out of ".$t3
." cached items after ".($now - $Cleanup_RBLs)
." seconds (min ".$CLEANUP_RBL_CACHE."s)" if ( $opt_verbose or (($t2 - $t1) > 0) );
$Cleanup_RBLs = $t1;
};
# prepares hit counters
- $myattr{$COMP_MATCHES} = 0;
- $myattr{$COMP_RBL_CNT} = 0;
- $myattr{$COMP_RHSBL_CNT} = 0;
+ $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) = compare_rule ($index, $date, %myattr);
+ ($matched,$rblcnt,$rhlcnt) = compare_rule ($index, $date, %request);
# enables/overrides hit counters for later use
- $myattr{$COMP_MATCHES} = $matched;
- $myattr{$COMP_RBL_CNT} = $rblcnt;
- $myattr{$COMP_RHSBL_CNT} = $rhlcnt;
+ $request{$COMP_MATCHES} = $matched;
+ $request{$COMP_RBL_CNT} = $rblcnt;
+ $request{$COMP_RHSBL_CNT} = $rhlcnt;
# matched? prepare logline, increase counters
if ($matched > 0) {
$myaction = $Rules[$index]{$COMP_ACTION};
$Matches{$Rules[$index]{$COMP_ID}}++;
- push ( @hits, $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",%myattr) );
+ $myaction = $var if ( $var = devar_item ("==",$myaction,"action",%request) );
$myline = "rule=".$index
. ", id=".$Rules[$index]{$COMP_ID}
- . ", client=".$myattr{"client_name"}."[".$myattr{"client_address"}."]"
- . ", sender=<".(($myattr{"sender"} eq '<>') ? "" : $myattr{"sender"}).">"
- . ", recipient=<".$myattr{"recipient"}.">"
- . ", helo=<".$myattr{"helo_name"}.">"
- . ", proto=".$myattr{"protocol_name"}
- . ", state=".$myattr{"protocol_state"};
+ . ", 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 =~ /^([a-zA-Z]{3,5})\(([^\)]*)\)$/) {
+ if ($myaction =~ /^(\w[\-\w]+)\s*\(\s*(.*?)\s*\)$/) {
my($mycmd,$myarg) = ($1, $2);
-
- # jump() command
- if ($mycmd eq "jump") {
- my($ruleno) = get_rule_by_id ($myarg);
- if ($ruleno) {
- mylogs $syslog_priority, "[RULES] ".$myline.", jump to rule $ruleno (id $myarg)"
- unless $opt_shortlog;
- $index = $ruleno - 1;
- } else {
- warn "[RULES] ".$myline." - error: jump failed, can not find rule-id ".$myarg." - ignoring";
- };
- $myaction = $default_action;
- # set() command
- } elsif ($mycmd eq "set") {
- foreach ( split (",", $myarg) ) {
- if ( /^\s*([^=]+?)\s*([\.\-\*\/\+=]=|=[\.\-\*\/\+=]|=)\s*(.*?)\s*$/ ) {
- my($r_var, $mod, $r_val) = ($1, $2, $3);
- my($m_val) = (defined $myattr{$r_var}) ? $myattr{$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 $myattr{$r_var})
- ? mylogs "notice", "[RULES] ".$myline.", redefining existing ".$r_var."=".$myattr{$r_var}." with ".$r_var."=".$m_val
- : mylogs $syslog_priority, "[RULES] ".$myline.", defining ".$r_var."=".$m_val
- unless $opt_shortlog;
- $myattr{$r_var} = $m_val;
- } else {
- warn "[RULES] ".$myline.", ignoring unknown set() attribute ".$_;
- };
- };
- $myaction = $default_action;
- # rate() and size() command
- } elsif (($mycmd eq "rate") or ($mycmd eq "size")) {
- ($ratetype,$ratecount,$ratetime,$ratecmd) = split "/", $myarg, 4;
- if ($ratetype and $ratecount and $ratetime and $ratecmd) {
- unless ( defined $Rates{$ratetype} ) {
- $Rates{$ratetype} = [ $mycmd, $ratecount, $ratetime, ( ($mycmd eq 'size') ? $myattr{"size"} : 1 ), $now, $Rules[$index]{$COMP_ID}, $ratecmd ];
- mylogs $syslog_priority, "[RULES] ".$myline
- .", creating rate object ".$ratetype
- ." [type: ".$mycmd.", max: ".$ratecount.", time: ".$ratetime."s]"
- if ($opt_verbose > 1);
- };
- } else {
- mylogs "notice", "[RULES] ".$myline.", ignoring unknown rate() attribute ".$myarg;
- };
- $myaction = $default_action;
- # wait() command
- } elsif ($mycmd eq "wait") {
- mylogs $syslog_priority, "[RULES] ".$myline.", delaying for $myarg seconds";
- sleep $myarg;
- $myaction = $default_action;
- # score() command
- } elsif ($mycmd eq "score") {
- $myaction = $default_action;
- 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;
- } elsif ($mod eq '=') {
- $score = $val;
- } else {
- $score += $val;
- };
- $score = $1.((defined $2) ? $2 : '.0') if ( $score =~ /^(\-?\d+)([\.,]\d\d?)?/ );
- mylogs $syslog_priority, "[SCORE] ".$myline.", modifying score about ".$myarg." points to ". $score
- unless $opt_shortlog;
- $myattr{"score"} = $myattr{"request_score"} = $score;
- my($max_score);
- foreach $max_score (reverse sort keys %MAX_SCORES) {
- if ( ($score >= $max_score) and ($MAX_SCORES{$max_score}) ) {
- $myaction=$MAX_SCORES{$max_score};
- $myline .= ", delay=".(time - $now)."s, hits=".(join (";", @hits)).", action=".$myaction." (score ".$score."/".$max_score.")";
- mylogs $syslog_priority, "[RULES] ".$myline;
- last RULE;
- };
- };
- } else {
- warn "[RULES] ".$myline.", invalid value for score \"$myarg\" - ignoring";
- };
- # note() command
- } elsif ($mycmd eq "note") {
- mylogs $syslog_priority, "[RULES] ".$myline." - note: ".$myarg if $myarg;
- $myaction = $default_action;
- # quit() command
- } elsif ($mycmd eq "quit") {
- warn "[RULES] ".$myline." - critical: quit (".$myarg.")";
- exit($myarg);
- # file() command
- } elsif ($mycmd eq "file") {
- warn "[RULES] ".$myline." - error: command file() has not been implemented yet - ignoring";
- $myaction = $default_action;
- # exec() command
- } elsif ($mycmd eq "exec") {
- warn "[RULES] ".$myline." - error: command exec() has not been implemented yet - ignoring";
- $myaction = $default_action;
+ if (defined $postfwd_actions{$mycmd}) {
+ mylogs $syslog_priority, "[PLUGIN] executing postfwd-action $mycmd" if ($opt_verbose > 1);
+ ($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 {
warn "[RULES] ".$myline." - error: unknown command \"".$1."\" - ignoring";
$myaction = $default_action;
};
# normal rule. returns $action.
- } else {
- $myline .= ", delay=".(time - $now)."s, hits=".(join (";", @hits)).", action=".$myaction;
+ } else { $stop = 1; };
+ if ($stop) {
+ $myline .= ", delay=".(time - $now)."s, hits=".$request{$COMP_HITS}.", action=".$myaction;
mylogs $syslog_priority, "[RULES] ".$myline;
last RULE;
};
@@ -1421,10 +1648,10 @@ sub smtpd_access_policy {
};
# update cache
if ( $REQUEST_MAX_CACHE > 0 ) {
- $Request_Cache{$cacheid}{"time"} = $now;
+ $Request_Cache{$cacheid}{time} = $now;
$Request_Cache{$cacheid}{$COMP_ACTION} = $myaction;
- $Request_Cache{$cacheid}{"hit"} = $matched;
- $Request_Cache{$cacheid}{"hits"} = join (";", @hits);
+ $Request_Cache{$cacheid}{hit} = $matched;
+ $Request_Cache{$cacheid}{hits} = $request{$COMP_HITS};
$Request_Cache{$cacheid}{$COMP_ID} = $Rules[$index]{$COMP_ID} if ($matched > 0);
};
};
@@ -1432,15 +1659,21 @@ sub smtpd_access_policy {
return $myaction;
};
+sub process_input {
+}
+
#### MAIN ####
# parse command-line
GetOptions ( 't|test' => \$opt_test,
'v|verbose' => sub { $opt_verbose++ },
- 'shortlog' => \$opt_shortlog,
'l|logname=s' => \$syslog_name,
+ 'facility=s' => \$syslog_facility,
+ 'loglen=i' => \$syslog_maxlen,
'n|nodns' => \$opt_nodns,
+ 'nodnslog' => \$opt_nodnslog,
+ 'shortlog' => \$opt_shortlog, # for compatibility
'd|daemon' => \$opt_daemon,
'I|instantcfg' => \$opt_instantconfig,
'P|perfmon' => \$opt_perfmon,
@@ -1452,8 +1685,8 @@ GetOptions ( 't|test' => \$opt_test,
'u|user=s' => \$net_user,
'g|group=s' => \$net_group,
'dns_queuesize=s' => \$dns_queuesize,
- 'dns_retries=s' => \$dns_retries,
- 'dns_timeout=s' => \$dns_timeout,
+ 'dns_retries=i' => \$dns_retries,
+ 'dns_timeout=i' => \$dns_timeout,
'dns_timeout_max=i' => \$MAX_DNSBL_TIMEOUTS,
'dns_timeout_interval=i' => \$MAX_DNSBL_INTERVAL,
'c|cache=i' => \$REQUEST_MAX_CACHE,
@@ -1467,9 +1700,11 @@ GetOptions ( 't|test' => \$opt_test,
'cleanup-rbls=i' => \$CLEANUP_RBL_CACHE,
'cleanup-rates=i' => \$CLEANUP_RATE_CACHE,
'S|summary:i' => \$opt_summary,
+ 'no-rulestats' => \$opt_no_rulestats,
's|scores=s' => \%opt_scores,
'f|file=s' => sub{ my($opt,$value) = @_; push (@Configs, $opt.'::'.$value) },
'r|rule=s' => sub{ my($opt,$value) = @_; push (@Configs, $opt.'::'.$value) },
+ 'plugins=s' => \@Plugins,
'V|version' => sub{ print "$NAME $VERSION\n"; exit 1; },
'C|showconfig' => \$opt_showconfig,
'h|H|?|help|Help|HELP' => sub{ pod2usage (-msg => "\nPlease see \"".$NAME." -m\" for detailed instructions.\n", -verbose => 1); },
@@ -1486,6 +1721,8 @@ setlogsock $syslog_socktype;
$syslog_options = 'cons,pid' unless $opt_daemon;
openlog $syslog_name, $syslog_options, $syslog_facility;
+mylogs "notice", $NAME." ".$VERSION." starting" if $opt_daemon;
+
# read configuration
read_config;
if ($opt_showconfig) {
@@ -1512,6 +1749,9 @@ $Startdate = strftime("%a, %d %b %Y %T %Z", localtime);
$Cleanup_Timeouts = $Cleanup_Requests = $Cleanup_RBLs = $Cleanup_Rates = $Starttime = time;
mylogs $syslog_priority, "Overriding cacheid itemlist with: ".(join ",", @CacheID) if ( @CacheID );
+# load plugin-items
+get_plugins (@Plugins) if @Plugins;
+
# de-taint arguments
$net_interface ||= $def_net_interface;
$net_port ||= $def_net_port;
@@ -1534,13 +1774,6 @@ $dns_retries = ( $dns_retries =~ /^(\d+)$/ ) ? $1 : $dns_retries;
$dns_timeout = ( $dns_timeout =~ /^(\d+)$/ ) ? $1 : $dns_timeout;
$syslog_name = ( $syslog_name =~ /^(.+)$/ ) ? $1 : $NAME;
-# create Net::DNS::Async object
-$DNS = new Net::DNS::Async (
- QueueSize => $dns_queuesize,
- Retries => $dns_retries,
- Timeout => $dns_timeout
-);
-
# Unbuffer standard output.
select((select(STDOUT), $| = 1)[0]);
@@ -1565,7 +1798,7 @@ if ($opt_daemon) {
chroot => $net_chroot ? $net_chroot : undef,
setsid => $opt_daemon ? 1 : undef,
pid_file => $net_pid ? $net_pid : undef,
- log_level => $opt_perfmon ? 0 : ($opt_verbose ? 3 : 2),
+ log_level => $opt_perfmon ? 0 : ($opt_verbose + 2),
log_file => $opt_perfmon ? undef : 'Sys::Syslog',
syslog_logsock => $syslog_socktype,
syslog_facility => $syslog_facility,
@@ -1576,6 +1809,9 @@ if ($opt_daemon) {
## run the servers main loop
$server->run;
+ # ignore syslog failures
+ sub handle_syslog_error {};
+
# set $Reload_Conf marker on HUP signal
# does not call read_config directly, to avoid
# possible race conditions when caches are cleared
@@ -1635,10 +1871,10 @@ if ($opt_daemon) {
mylogs $syslog_priority, "Client: $client Attribute: $_=$myattr{$_}";
};
};
- unless ($myattr{"request"} eq "smtpd_access_policy") {
- warn "ignoring unrecognized request type: '$myattr{request}'"
+ unless ( (defined $myattr{request}) and ($myattr{request} eq "smtpd_access_policy") ) {
+ warn "ignoring unrecognized request type: '".($myattr{request} || '')."'";
} else {
- my($action) = smtpd_access_policy(%myattr);
+ my($action) = substr ( smtpd_access_policy(%myattr), 0, $reply_maxlen ) if $reply_maxlen;
mylogs $syslog_priority, "Client: $client Action: $action" if $opt_verbose;
print $client "action=$action\n\n";
$Counter_Requests++; $Counter_Interval++;
@@ -1669,10 +1905,10 @@ if ($opt_daemon) {
mylogs $syslog_priority, "Attribute: $_=$myattr{$_}";
};
};
- unless ($myattr{"request"} eq "smtpd_access_policy") {
+ unless ($myattr{request} eq "smtpd_access_policy") {
warn "ignoring unrecognized request type: '$myattr{request}'"
} else {
- my($action) = smtpd_access_policy(%myattr);
+ my($action) = substr ( smtpd_access_policy(%myattr), 0, $reply_maxlen ) if $reply_maxlen;
mylogs $syslog_priority, "Action: $action" if $opt_verbose;
myprint "action=$action\n\n";
$Counter_Requests++; $Counter_Interval++;
@@ -1715,8 +1951,9 @@ postfwd [OPTIONS] [SOURCE1, SOURCE2, ...]
-u, --user set uid to user
-g, --group set gid to group
-R, --chroot chroot the daemon to
- -l, --logname label for syslog messages
--pidfile create pidfile under
+ -l, --logname label for syslog messages
+ --loglen truncates syslogs after chars
Caching:
-c, --cache sets the request-cache timeout to seconds
@@ -1733,17 +1970,16 @@ postfwd [OPTIONS] [SOURCE1, SOURCE2, ...]
Optional:
-t, --test testing, always returns "dunno"
-v, --verbose verbose logging, use twice (-vv) to increase level
- --shortlog disables logging of some postfwd commands
-S, --summary show some usage statistics every seconds
+ --no-rulestats disables per rule statistics
-n, --nodns disable dns
- --dns_queuesize sets the queue size for asynchonous dns queries
- --dns_retries how many retries for a single asynchonous dns query
+ --nodnslog disable dns logging
--dns_timeout timeout in seconds for asynchonous dns queries
--dns_timeout_max maximum of dns timeouts until a dnsbl will be deactivated
--dns_timeout_interval interval in seconds for dns timeout maximum counter
-I, --instantcfg re-reads rulefiles for every new request
- Informational (use only at command-line, not with postfix!):
+ Informational (use only at command-line!):
-C, --showconfig shows ruleset summary, -v for verbose
-L, --stdoutlog redirect syslog messages to stdout
-P, --perfmon no syslogging, no stdout
@@ -1751,6 +1987,9 @@ postfwd [OPTIONS] [SOURCE1, SOURCE2, ...]
-h, --help shows usage
-m, --manual shows program manual
+ Plugins:
+ --plugins loads plugins from
+
=head1 DESCRIPTION
@@ -1833,6 +2072,15 @@ Rules can span multiple lines by adding a trailing backslash "\" character:
ids also serve as targets for the "jump" command.
date, time - a time or date range within the specified rule shall hit
+ # FORMAT:
+ # Feb, 29th
+ date=29.02.2008
+ # Dec, 24th - 26th
+ date=24.12.2008-26.12.2008
+ # from today until Nov, 23rd
+ date=-23.09.2008
+ # from April, 1st until today
+ date=01.04.2008-
days, months - a range of weekdays (Sun-Sat) or months (Jan-Dec)
within the specified rule shall hit
@@ -2045,20 +2293,26 @@ These special attributes will be reset for any new rule:
rblcount - contains the number of RBL answers
rhsblcount - contains the number of RHSBL answers
matches - contains the number of matched items
+ dnsbltext - contains the dns TXT part of all RBL and RHSBL replies in the form
+ rbltype:rblname:; rbltype:rblname:; ...
-This means that you must save them, if you plan to use these values in later rules:
+These special attributes will be changed for any matching rule:
+
+ request_hits - contains ids of all matching rules
+
+This means that it might be necessary to save them, if you plan to use these values in later rules:
# set vals
id=RBL01 ; rhsblcount=all ; rblcount=all ; \
rbl=list.dsbl.org, bl.spamcop.net, dnsbl.sorbs.net, zen.spamhaus.org ; \
rhsbl_client=rddn.dnsbl.net.au, rhsbl.ahbl.org, rhsbl.sorbs.net ; \
rhsbl_sender=rddn.dnsbl.net.au, rhsbl.ahbl.org, rhsbl.sorbs.net ; \
- action=set(HIT_rhls=$$rhsblcount,HIT_rbls=$$rblcount)
+ action=set(HIT_rhls=$$rhsblcount,HIT_rbls=$$rblcount,HIT_txt=$$dnsbltext)
# compare
- id=RBL02 ; HIT_rhls>=1 ; HIT_rbls>=1 ; action=554 5.7.1 blocked using $$HIT_rhls RHSBLs and $$HIT_rbls RBLs
- id=RBL03 ; HIT_rhls>=2 ; action=554 5.7.1 blocked using $$HIT_rhls RHSBLs
- id=RBL04 ; HIT_rbls>=2 ; action=554 5.7.1 blocked using $$HIT_rbls RBLs
+ id=RBL02 ; HIT_rhls>=1 ; HIT_rbls>=1 ; action=554 5.7.1 blocked using $$HIT_rhls RHSBLs and $$HIT_rbls RBLs [INFO: $$HIT_txt]
+ id=RBL03 ; HIT_rhls>=2 ; action=554 5.7.1 blocked using $$HIT_rhls RHSBLs [INFO: $$HIT_txt]
+ id=RBL04 ; HIT_rbls>=2 ; action=554 5.7.1 blocked using $$HIT_rbls RBLs [INFO: $$HIT_txt]
=head2 MACROS/ACLS
@@ -2105,6 +2359,11 @@ Macros can contain macros, too:
Basically macros are simple text substitutions - see the L section for more information.
+=head2 PLUGINS
+
+Please visit L
+
+
=head2 COMMAND LINE
I
@@ -2120,6 +2379,12 @@ that at least one of the following is required for postfwd to work.
Adds to ruleset. Remember that you might have to quote
strings that contain whitespaces or shell characters.
+I
+
+ --plugins
+ A file containing plugin routines for postfwd. Please see the
+ PLUGINS section for more information.
+
I
-s, --scores =
@@ -2127,7 +2392,7 @@ I
Multiple usage is allowed. Just chain your arguments, like:
- postfwd -r "- =
;action=" -f -f ...
+ postfwd -r "- =
;action=" -f -f --plugins ...
or
postfwd --scores 4.5="WARN high score" --scores 5.0="REJECT postfwd score too high" ...
@@ -2159,12 +2424,15 @@ The following arguments will control it's behaviour in this case.
Chroot the process to the specified path.
Test this before using - you might need some libs there.
+ --pidfile
+ The process id will be saved in the specified file.
+
-l, --logname
Labels the syslog messages. Useful when running multiple
instances of postfwd.
- --pidfile
- The process id will be saved in the specified file.
+ --loglen
+ Truncates any syslog message after characters.
I
@@ -2241,13 +2509,13 @@ These parameters influence the way postfwd is working. Any of them can be combin
Aug 19 12:39:45 mail1 postfwd[666]: [STATS] Rule ID: R-003 matched: 3116 times
...
+ --no-rulestats
+ Disables per rule statistics. Keeps your log clean, if you do not use them.
+ This option has no effect without --summary or --verbose set.
+
-L, --stdoutlog
Redirects all syslog messages to stdout for debugging. Never use this with postfix!
- --shortlog
- As postfwd now logs all hits for a request, you might find it unecessary to log the
- postfwd actions jump(), set() and score(). You may disable it with this option.
-
-t, --test
In test mode postfwd always returns "dunno", but logs according
to it`s ruleset. -v will be set automatically with this option.
@@ -2256,15 +2524,10 @@ These parameters influence the way postfwd is working. Any of them can be combin
Disables all DNS based checks like RBL checks. Rules containing
such elements will be ignored.
- --dns_queuesize (default: 100)
- Sets the queue size for asynchonous dns queries. If the query exceeds this value,
- postfwd waits for answers of timeouts for previous queries.
+ -n, --nodnslog
+ Disables logging of dns events.
- --dns_retries (default: 3)
- Sets the retry counter for asynchonous dns queries. This value will apply to
- every single query.
-
- --dns_timeout (default: 7)
+ --dns_timeout (default: 14)
Sets the timeout for asynchonous dns queries in seconds. This value will apply to
all dns items in a rule.
@@ -2431,11 +2694,11 @@ the '-I' switch to have your configuration refreshed for every request postfwd r
...
};
&&MAINTENANCE { \
- date=15.01.2007 ; \
- date=15.04.2007 ; \
- date=15.07.2007 ; \
- date=15.10.2007 ; \
- time=03:00:00-04:00:00 ; \
+ date=15.01.2007 ; \
+ date=15.04.2007 ; \
+ date=15.07.2007 ; \
+ date=15.10.2007 ; \
+ time=03:00:00 - 04:00:00 ; \
};
# rules
id=COMBINED ; &&RBLS ; &&DYNAMIC ; action=REJECT dynamic client and listed on RBL
@@ -2450,14 +2713,11 @@ the '-I' switch to have your configuration refreshed for every request postfwd r
id=REJECT02 ; HIT_rbls==1 ; HIT_dyna==1 ; action=REJECT please see http://some.org/info?reject=02 for more info
id=REJECT03 ; HIT_helo==1 ; HIT_dyna==1 ; action=REJECT please see http://some.org/info?reject=03 for more info
- # combined with enhanced rbl features
- # set vals
+ ## combined with enhanced rbl features
+ #
id=RBL01 ; rhsblcount=all ; rblcount=all ; &&RBLS ; &&RHSBLS ; \
- action=set(HIT_rhls=$$rhsblcount,HIT_rbls=$$rblcount)
- # compare
- id=RBL02 ; HIT_rhls>=1 ; HIT_rbls>=1 ; action=554 5.7.1 blocked using $$HIT_rhls RHSBLs and $$HIT_rbls RBLs
- id=RBL03 ; HIT_rhls>=2 ; action=554 5.7.1 blocked using $$HIT_rhls RHSBLs
- id=RBL04 ; HIT_rbls>=2 ; action=554 5.7.1 blocked using $$HIT_rbls RBLs
+ action=set(HIT_dnsbls=$$rhsblcount,HIT_dnsbls+=$$rblcount,HIT_dnstxt=$$dnsbltext)
+ id=RBL02 ; HIT_dnsbls>=2 ; action=554 5.7.1 blocked using $$HIT_dnsbls DNSBLs [INFO: $$HIT_dnstxt]
=head2 PARSER
@@ -2514,7 +2774,7 @@ equals to
Lists will be evaluated in the specified order. This allows to place faster expressions at first:
- postfwd -vv -L -r "id=RBL001; rbl=localrbl.local zen.spamhaus.org; action=REJECT" /root/request.sample
+ postfwd -vv -L -r "id=RBL001; rbl=localrbl.local zen.spamhaus.org; action=REJECT" /some/where/request.sample
produces the following
@@ -2532,7 +2792,7 @@ produces the following
The negation operator !!() has the highest priority and therefore will be evaluated first. Then variable substitutions are performed:
- postfwd -vv -L -r "id=TEST; action=REJECT; client_name=!!($$heloname)" /root/request.sample
+ postfwd -vv -L -r "id=TEST; action=REJECT; client_name=!!($$heloname)" /some/where/request.sample
will give
@@ -2677,13 +2937,13 @@ listening on the specified network settings.
Some of these proposals might not match your environment. Please check your requirements and test new options carefully!
-- use caching options
-- use the correct match operator ==, <=, >=
-- use ^ and $ in regular expressions
-- use item lists (faster than single rules)
-- use set() action on repeated item lists
-- use jump action
-- use pre-lookup rule for rbl/rhsbls with empty note() action
+ - use caching options
+ - use the correct match operator ==, <=, >=
+ - use ^ and/or $ in regular expressions
+ - use item lists (faster than single rules)
+ - use set() action on repeated item lists
+ - use jumps and rate limits
+ - use a pre-lookup rule for rbl/rhsbls with empty note() action
=head2 SEE ALSO
diff --git a/tools/README.txt b/tools/README.txt
index 20b9cbc..e10c179 100644
--- a/tools/README.txt
+++ b/tools/README.txt
@@ -3,9 +3,6 @@ Directory contents:
- lograte.sh [OPTIONS]
generates per minute stats for generic syslog files
-- postfwd-rblcheck.pl [ ...]
- queries a bunch of dnsbls for the given host(s)
-
- request.sample
a sample policy delegation request. you may test your postfwd config with
postfwd -f request.sample
diff --git a/tools/postfwd-rblcheck.pl b/tools/postfwd-rblcheck.pl
deleted file mode 100755
index 9be1f20..0000000
--- a/tools/postfwd-rblcheck.pl
+++ /dev/null
@@ -1,174 +0,0 @@
-#!/usr/bin/perl -T -w
-#
-# Tool to query a bunch of dnsbls. Usage:
-#
-# postfwd-rblcheck.pl [ ...]
-#
-# by JPK
-
-
-use Net::DNS::Async;
-use strict;
-
-# length of screen
-my $mylen = 79;
-
-# RBLs (ip based)
-my @rbls = qw(
- query.bondedsender.org
- exemptions.ahbl.org
- spf.trusted-forwarder.org
- list.dnswl.org
- zz.countries.nerd.dk
- zen.spamhaus.org
- bl.spamcop.net
- list.dsbl.org
- multihop.dsbl.org
- unconfirmed.dsbl.org
- combined.njabl.org
- dnsbl.sorbs.net
- dnsbl.ahbl.org
- ix.dnsbl.manitu.net
- dnsbl-1.uceprotect.net
- dnsbl-2.uceprotect.net
- dnsbl-3.uceprotect.net
- ips.backscatterer.org
- sorbs.dnsbl.net.au
- korea.services.net
- blackholes.five-ten-sg.com
- cbl.anti-spam.org.cn
- cblplus.anti-spam.org.cn
- cblless.anti-spam.org.cn
- bogons.cymru.com
- dynamic.tqmrbl.com
- relays.tqmrbl.com
- clients.tqmrbl.com
- hostkarma.junkemailfilter.com
-);
-
-# RHSBLs (domain based)
-my @rhsbls = qw(
- rhsbl.sorbs.net
- rhsbl.ahbl.org
- multi.surbl.org
- dsn.rfc-ignorant.org
- abuse.rfc-ignorant.org
- whois.rfc-ignorant.org
- bogusmx.rfc-ignorant.org
- blackhole.securitysage.com
- ex.dnsbl.org
- rddn.dnsbl.net.au
- block.rhs.mailpolice.com
- dynamic.rhs.mailpolice.com
- dnsbl.cyberlogic.net
- hostkarma.junkemailfilter.com
-);
-
-# async dns object
-my $DNS = new Net::DNS::Async ( QueueSize => 100, Retries => 3, Timeout => 20 );
-our %RBLres = ();
-
-# async dns callback method
-sub callback {
- my $myresponse = shift;
- my $query = ''; my $result = '';
-
- # get query
- if ( defined $myresponse ) {
- foreach ($myresponse->question) {
- next unless (($_->qtype eq 'A') or ($_->qtype eq 'TXT'));
- $query = $_->qname;
- };
-
- # get answer and fill result hash
- if ( defined $query ) {
- foreach ($myresponse->answer) {
- if ($_->type eq 'A') {
- $result = $_->address;
- $query ||= ''; $result ||= '';
- $RBLres{$query}{result} = $result;
- $RBLres{$query}{end} = time;
- } elsif ($_->type eq 'TXT') {
- $RBLres{$query}{text} = join(" ", $_->char_str_list());
- $RBLres{$query}{end} = time;
- };
- };
- };
- };
-};
-
-# main, parse argument list
-foreach (@ARGV) {
- my $query = $_;
- my $now = time;
- my @lookups = ();
- my $name = my $addr = my $res = 'unknown';
- my $rblcount = my $rhlcount = 0;
-
- # clear result hash
- %RBLres = ();
-
- # lookup hostname or ip address, remove localpart if email address
- if ($query =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) {
- $addr = $query;
- $name = $res
- if ( defined($res = gethostbyaddr (pack ('C4', (split /\./, $addr)), 2)) );
- } else {
- $name = ($query =~ /@([^@]+)$/) ? $1 : $query;
- $addr = ( join ".", (unpack ('C4', $res)) )
- if ( defined ($res = gethostbyname ($name.".")) );
- };
-
- # header
- print "\n", "=" x $mylen, "\n";
- print "QUERY: ", $query, " NAME: ", $name, " ADDR: ", $addr, "\n";
-
- # prepare rbl lookups
- unless ($addr eq 'unknown') {
- $addr = join ".", reverse split /\./, $addr;
- foreach my $rbl (@rbls) {
- $RBLres{$addr.".".$rbl}{query} = $rbl;
- $RBLres{$addr.".".$rbl}{type} = 'RBL';
- $RBLres{$addr.".".$rbl}{start} = time;
- push @lookups, $addr.".".$rbl;
- #print "query ", $RBLres{$addr.".".$rbl}{query}, " for ", $addr.".".$rbl, "\n";
- };
- };
-
- # prepare rhsbl lookups
- unless ($name eq 'unknown') {
- foreach my $rhsbl (@rhsbls) {
- $RBLres{$name.".".$rhsbl}{query} = $rhsbl;
- $RBLres{$name.".".$rhsbl}{type} = 'RHSBL';
- $RBLres{$name.".".$rhsbl}{start} = time;
- push @lookups, $name.".".$rhsbl;
- #print "name ", $RBLres{$name.".".$rhsbl}{query}, " for ", $name.".".$rhsbl, "\n";
- };
- };
-
- # perform lookups
- map { $DNS->add (\&callback, $_) } @lookups;
- map { $DNS->add (\&callback, $_, 'TXT') } @lookups;
- $DNS->await();
-
- # evaluate results
- foreach $query (sort keys %RBLres) {
- if ($query and (defined $RBLres{$query}{result})) {
- print " ", "-" x ($mylen - 4), "\n";
- printf " listed on %s:%s, result: %s, time: %ds\n %s\n",
- $RBLres{$query}{type},
- $RBLres{$query}{query}, $RBLres{$query}{result},
- ($RBLres{$query}{end} - $RBLres{$query}{start}),
- ((defined $RBLres{$query}{text}) ? "\"".$RBLres{$query}{text}."\"" : '');
- $rblcount++ if $RBLres{$query}{type} eq 'RBL';
- $rhlcount++ if $RBLres{$query}{type} eq 'RHSBL';
- };
- };
-
- # footer
- print " ", "-" x ($mylen - 4), "\n";
- printf "%d of %d RBLs, ", $rblcount, $#rbls if ($rblcount > 0);
- printf "%d of %d RHSBLs, ", $rhlcount, $#rhsbls if ($rhlcount > 0);
- printf "Finished after %d seconds\n", (time - $now);
- print "=" x $mylen, "\n\n";
-};