Imported Upstream version 0.1.15.1 upstream upstream/0.1.15.1
authorChris Butler <chrisb@debian.org>
Fri, 11 Feb 2011 20:59:28 +0000 (20:59 +0000)
committerChris Butler <chrisb@debian.org>
Fri, 11 Feb 2011 20:59:28 +0000 (20:59 +0000)
COPYING [new file with mode: 0644]
LICENSE [new file with mode: 0644]
Makefile [new file with mode: 0644]
changes.txt [new file with mode: 0644]
contrib/rc-scripts/freebsd/policyd-weight.sh [new file with mode: 0755]
documentation.txt [new file with mode: 0644]
man/man5/policyd-weight.conf.5 [new file with mode: 0644]
man/man8/policyd-weight.8 [new file with mode: 0644]
policyd-weight [new file with mode: 0755]
policyd-weight.conf.sample [new file with mode: 0644]
todo.txt [new file with mode: 0644]

diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..dde5e15
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,20 @@
+# Copyright 2005 Robert Felber (Autohaus Erich Kuttendreier, Munich)
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+#
+# A copy of the GPL can be found at http://www.gnu.org/licenses/gpl.txt
+
+# Parts of code based on postfix-policyd-spf by Meng Wen Wong, version 1.06, 
+# see http://spf.pobox.com/
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..3912109
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,340 @@
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                       51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+\f
+                   GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+\f
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+\f
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+\f
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                           NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
+\f
+           How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..1458a0f
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,4 @@
+all:
+clean:
+distclean:
+install:
diff --git a/changes.txt b/changes.txt
new file mode 100644 (file)
index 0000000..2f2f5bc
--- /dev/null
@@ -0,0 +1,658 @@
+0.1.14 beta-17
+
+- (security)  Using File::Spec->canonpath for normalization (trailing slashes)
+              Check ownership of real directories to avoid race attacks
+              for symlinks.
+              Thanks to Robert Buchholz.       
+
+
+0.1.14 beta-16 (not released)
+
+- (security)  The check for symlinked directories was half complete.
+              perl ignores -l if the argument has a trailung slash.
+              Thanks to Andrej Kacian.
+
+0.1.14 beta-15
+
+- (security)  $LOCKPATH and its contents weren't checked for being
+              a symlink which. Thanks to Chris Howells and Andrej Kacian.
+
+- (fix)       "dedicated" added to the exclusion list for dialup
+              checks. A better approach would be to let the user
+              configure dialup and exclude patterns.
+
+
+0.1.14 beta-14
+
+- (change)    rbls.org link changed to robtext.com
+
+- (change)    results with 'rc:' as action are not cached
+
+- (fix)       regexp check for dynamic helo/client did hit also some
+              clients with "static"
+
+- (fix)       helo numeric check was too fuzzy.
+
+- (fix)       master didn't read config after policyd-weight reload
+
+
+- (fix)       HELO_SEEMS_DIALUP may have scored even if the IP is listed
+              for the sender domain.
+
+- (fix)       An interrupt of policyd-weight -s may cause a SIGPIPE
+              which killed the cache
+
+
+- (change)    Implemented $NS list. Useful for users with split
+              horizon DNS
+
+- (fix)       don't cache rejections which were deferred (4xx and friends)
+
+
+- (fix)       helo_numeric_score didn't catch [n.n.n.n] helos
+
+
+- (fix)       Header was not included if $dnsbl_checks_only = 1; and
+              $ADD_X_HEADER = 1; - Thanks to J. Genannt
+
+- (fix)       Corrected handling of [n.n.n.n] HELOs and address-literals
+              as sender (long standing issue)
+
+- (change)    Introduced @dnsbl_checks_only_regexps in order to skip
+              DNS checks for certain client hostnames
+
+- (change)    Added -D (Don't detach) switch for daemon-tools/runit users
+
+- (change)    Added signals handlers for most of signals so that they are
+              at least logged, also, provide a perl backtrace.
+
+- (change)    prerequisite steps for providing coredumps (build coredump
+              directories, chdir) - coredumps are non-trivial:
+              we start as root, change uid. At this moment coredumps
+              are denied by kernel in order to protect root-data. The only 
+              workaround would be, to start cache and master via system() 
+              after changing uid
+
+- (change)    In daemon mode wrongly crafted policy requests don't lead
+              to a child-exit anymore, only the connection is closed
+
+- (change)    log-facilities other than 'info' are now mentioned in log-lines
+
+- (change)    SMTP information such as client, helo, sender and to are now
+              logged in each log-message. If $DEBUG is set this also logs
+              the instance variable.
+
+- (fix)       rbl_lookup used sometimes 65536 as packet id which appeared
+              to cause problems
+
+- (fix)       Check for syslog absence. If syslog is not available then
+              log temporarily to $LOCKPATH/polw-emergency.log
+
+- (tmpfix)    Introduced $TRY_BALANCE which closes connections to smtpds after
+              they got their response in order to avoid too many established
+              smtpd->policyd-weight (child) connections.
+
+
+0.1.14 beta-5
+
+- (fix)       A lookups were not performed if MX lookups returned
+              NXDOMAIN. Thanks to H. Krohns.
+
+- (change)    Reverse IP == dynhost check corrected such that hosts
+              which have smtp, mail, mx, or static in their FQDN are not
+              treated as dynamic clients. Also the regex for rev.*(home|ip)
+              was corrected. Thanks to H. Krohns.
+
+0.1.14 beta-4
+
+- (fix)       Corrected handling of SERVFAIL and timeouts
+              Thanks to H. Krohns
+
+- (fix)       DNSERRMSG wasn't re-initialized, thus leading to
+              subsequent concatenated responses.
+              Thanks to H. Krohns
+
+0.1.14 beta-3
+
+- (change)    Replaced dynablock.njabl.org with pbl.spamhaus.org
+
+0.1.14 beta-2
+
+- (fix)       FreeBSD:
+                rc cannot handle programs with "-" (dash) in its name
+                correctly. Init script for FBSD does now provide its own
+                stop|start functions.
+
+- (change)    removed dnsbl_hits increasements where the check in question
+              was not a dnsbl check, same goes for total_dnsbl_score
+
+- (change)    Termination of daemon changed. The daemon tries to terminate
+              childrens himself.
+
+- (fix)       rbl_lookup used sometimes 0 for the random DNS packet identifier.
+              It also checked the presence and correctness of this field wrong.
+              (non critical)
+
+- (new)       $enforce_dyndns_score added
+              With this one can control how much he wants to avoid dynamic
+              clients which do not use DynDNS
+
+- (change)    The cache now uses only one query to ask for HAM|SPAM.
+              Thanks to H. Krohns.
+
+- (change)    the $dnsbl_hits score gets only increases if the RBL is a
+              blacklist. This is required to use whitelists in the $dnsbl_score
+              list. Thanks to 'Steve'.
+
+- (change)    The cache can now return hard-whitelists.
+              Enforce N days/hours RBL checks for HAM clients.
+              After N days do each P hours or each R requests a RBL check for
+              HAM cached clients. This should save a good amount of RBL DNS
+              traffic.
+
+- (change)    The delay in seconds for each policy request is now logged via
+              "decided action"
+
+- (change)    The time required for a cache cleanup is now logged, too.
+
+- (scores)    @helo_seems_dialup = (1.5,0)
+              'dsn.rfc-ignorant.org', 3.5, 0, 'DSN_RFCI',
+              ordb.org removed
+
+- (fix)       alarms didn't work on every platform, leading to timeouts
+
+- (fix)       BAD_MX might score even though the sender has an A record
+              which matches the client IP
+
+- (cleanup)   use modules at the beginning, usage() printed as here-doc
+              Suggested by Francis Galiegue
+
+- (cleanup)   Use A queries instead of TXT queries for RBL lookups
+
+- (new)       Cache only the IP if the client was too much DNSBL listed
+              or the client seems to be a dialup or the helo/from verification
+              against IP and subnets failed totally
+              Should help against Dictionary Attacks
+
+- (fix)       On some plattforms setting correct user and groupmemberships
+              is a hard task, we try now two ways to accomplish this
+
+- (new)       One can define to return a restriction class by setting the
+              appropriate messages to "rc:your_restriction_class"
+              Policyd-weight ignores trailing garbage after rc:text messages
+              Example: $REJECTMSG = "rc:greylist";
+
+- (new)       Non-responsive RBLs are skipped for a certain amount of queries.
+
+0.1.14 beta
+
+- (fix)       $< enabled taint mode - which made policyd-weight crash in
+              case of troublesome config files
+
+- (change)    Messages due to die() were reported not appropriate.
+              Old $! variables may have lead to false assumptions.
+
+
+- (change)    FROM_MATCHES_NOT_HELO now also checks whether the SENDER MX
+              lists a host which matches HELO domain (minimizing false 
+              positives or inappropriate spam-scoring)
+
+- (vuln fix)  When $DEBUG = 1; is used then policyd-weight might be vulnerable
+              to a remote format-string attack if the MTA does not avoid it.
+              Also it is open for a local format-string attack in case of
+              $DEBUG = 1;.
+              A syslog message of the cache query could have taken foreign
+              format-string sequences which would have been executed with
+              old Sys::Syslog modules.
+
+- (change)    Floating point numbers sanitized
+
+- (change)    Kids didn't die verbose
+
+- (fix)       call close on DNS sockets only if a socket exists and is 
+              connected
+
+
+- (change)    Initialization of syslog socket exits now informative if it cannot
+              be setup.
+
+- (change)    RHSBL lookups are now half-dnsbl influenced.
+
+
+- (fix)       The perl POSIX module of SuSe 9.0 is buggy. We don't use
+              POSIX for setting UID/EUID/GID/EGID anymore but the provided
+              perl vars from man perlvar | less +/UID
+
+- (change)    -f switch added to hand a configuration file to policyd-weight.
+
+
+- (fix)       FROM_NOT_HELO was too heavily DNSBL influenced
+
+
+- (fix)       Communication between parent and childs wasn't portable
+              Solaris versions of perl didn't unterstand $sock->send
+              for socketpair() created IPC-channels
+
+- (change)    RFCI postmaster and abuse list changed from 1 to 0.1
+              rhsbl_penalty_score changed from 3.3 to 3.1
+
+- (change)    MAXIDLE changed from 120 to 240
+              MIN_PROC changed from 2 to 3
+              POCACHEMAXSIZE changed from 380 to 480
+              CACHEMAXSIZE changed from 380 to 480
+
+
+- (change)    Scores are now computed once, and not twice each check. 
+              Which is just a bit more CPU friendly.
+
+
+- (change)    HELO which couldn't be verified to match IP weren't DNSBL
+              influenced, i.e. the score increases now when also DNSBL-listed.
+
+
+- (change)    syslogs the used config file
+- (change)    shows GROUP infos in debug mode
+
+- (new)       Added "default" action. 'policyd-weight default' will show
+              its defaults and exit.
+              Patch supplied by Philipp Koller, thanks.
+
+
+- (fix)       FROM_MULTIPARTED check made less aggressive.
+              Hosts whose sender or helo verify to the client-ip are
+              not checked for multiple lables. This is a cure for yahoo
+              groups
+
+- (new)       inet/daemon mode  support added, default port 12525
+
+- (new)       checks for bogus MXes of MAIL FROM arg added 
+              (empty, 127/8, 192.168/16, 10/8, 172.16/12)
+              if this check gives true then the mail is DEFERed instead of 
+              REJECTed
+              This check also increases results of other checks
+
+- (new)       Checks for randomized sender addresses added
+
+- (change)    rhsbl check changed as such, that all rhslb entries are queried 
+              now
+
+- (change)    surbl.org added to the default rhsbl hosts
+
+
+- (fix)       The routine to terminate cache versions prior to 0.1.12 beta
+              was buggy. fixed.
+
+- (change)    Included the possibility to DEFER instead of REJECT mail on 
+              configurable strings if the overall score is below DEFER_LEVEL.
+              This way one can DEFER mail on e.g. " IN_SPAMCOP=" listings.
+
+- (fix)       Log entry for FROM_MATCHES_NOT_HELO corrected
+
+- (change/new)  Log outputs also recipient
+
+- (new)       own rbl_lookup routine implemented (for reference see 
+              http://www.policyd-weight.org/rbl_lookup.html)
+              This reduces about 85% of total time and 95% CPU time for RBL 
+              Lookups. If the routine seems to give buggy results you might 
+              try to set $USE_NET_DNS = 1; although it might still be buggy.
+              It uses Net::DNS automatically for perl versions prior to 5.8
+
+- (change/fix)  Cache queries made more reliable. Timeouts implemented
+
+- (fix)         PTR vs HELO/FROM was case-sensitive
+
+
+--------------------------------------------------------------------------------
+0.1.12 beta-4
+
+- don't perform multiparted check of sender domain if client is an MX for that
+  domain (fix)
+
+- cache cleanup process optimized by 80%
+
+- HELO vs FROM check made more reliable (fix)
+
+- CVERSION introduced which replaces the "detect if script has changed" routine
+  which means, that the cache is only being killed on updated when CVERSION 
+  changes.
+
+--------------------------------------------------------------------------------
+0.1.12 beta
+
+- improved multirecipient awareness. It is now possible to build up restriction
+  classes within postfix to either explicitly say "check policy service" or to
+  make user exceptions. This is important for ISP. This was not possible with 
+  previous versions.
+
+- -d debug switch added. In debug mode nothing is sent to syslog but STDOUT
+  also it turns on Net::DNS debugging
+  It prints some perl/OS/Net:DNS/policyd-weight version infos and configuration
+  this switch is NOT FOR USE IN MASTER.CF
+
+- permission/accessibility checks for configuration files added. Syslog if
+  either permission denied, or config is world-writeable. Recommended mode is
+  0644 and owner root, group root (or wheel on bsd).
+
+- cache outsourced to an own cache daemon. Decreases drastically frequent DNS
+  lookups and thus network delays and CPU time.
+  For security reasons policyd-weight must not run as nobody or root. Set up
+  an own user for that and update master.cf (user=$your_user)
+  Several configuration items for the cache have been added
+
+- some scores adjusted to let pass DynDNS MX users with a envelope of 
+  foo@bar.dyndns.org
+  Also the spamcop score has been lowered
+
+- helo_from_mx_eq_ip_score added
+
+- some more scores adjusted
+
+- FROM Domain vs HELO regex check adjusted
+
+- Process UID check added, policyd-weight must have it's own user. Update
+  master.cf
+
+- dynmaic clients whose score cause a REJECT will be rejected with a note:
+  "; please relay via your ISP ($from_domain)"
+
+- critical fix: First perform Sender Domain MX lookups. If the Client is a
+  MX for that Domain, don't do HELO vs FROM pattern matching.
+
+- Halved the weight of RBL results agains HELO/FROM pattern mismatches.
+
+- removed scoring for HELO == dynamic host regexp check if client address ==
+  dynhost check was true. This might (and will) permit more spam to get through.
+  But also some dynamic host MTAs which don't use dyndns possibilities.
+
+--------------------------------------------------------------------------------
+0.1.11 beta
+
+- (fix) Using of appropriate methods for fetching truncated packets via TCP
+  Net::DNS version < 0.50: igntc() (ignore truncated packets)
+  Net::DNS version >= 0.50 force_v4() (force IPv4 usage)
+
+- X-policyd-weight header for multirecipient mail is now inserted only once
+
+- Caching of spam-results happens only if no DNS error (timeout) occured
+
+- RHSBL results are appended at the reject-message
+
+- Messages to STDERR end now in nirvana to don't confuse the SMTPD
+  STDERR messages caused by a die() end up in syslog
+
+- Config errors end in syslog, if config file couldn't be loaded due to a syntax
+  error then we fall back to builtin defaults and append a message to 
+  X-policyd-weight header.
+
+- Scores for from_match_regex_unverified_helo and helo_ip_in_cl16_subnet 
+  adjusted to let pass msn.com mail relayed via hotmail.com
+
+- Order and scores for RHSBL entries adjusted
+
+- (fix) The special recipients postmaster and abuse pass now with DUNNO instant.
+  This was the case for virtual domains.
+
+- (fix) The array for the reverse IP lookup result was build wrong, in some
+  circumstances this may lead to an empty array and thus some _badly_ configured
+  mailer with incorrect DNS (those with broken forward DNS) may have been 
+  blocked.
+
+- (fix) NULL (<>) Sender now pass (RFC compliance)
+
+- LOG_BAD_RBL_ONLY added which logs only successfull RBL hits. If there was
+  no RBL hit, but the "good" score was not equal zero, it is logged though.
+  Default is 1 (ON).
+
+
+--------------------------------------------------------------------------------
+0.1.10 beta
+
+- Caching of positive and negative results added
+
+- (fix) improved error-handling on DNS timeouts and empty objects.
+
+- code optimizations
+  DNS Resolver is created in main
+  reverse IP records get fetched only one time
+
+- cosmetic changes (leading tabs substituted with blanks)
+
+
+--------------------------------------------------------------------------------
+0.1.9 beta
+
+- RHSBL support added
+
+- dnsbl_checks_only switch added
+
+- X-policyd-weight: header on/off switchable
+
+- DNSBLMAXSCORE added
+
+- config file support added
+
+- multipart FROM check/scoring added
+
+- Reverse IP == dynhost check added
+
+- Net::DNS retries and retry interval changed
+
+- Net::DNS support for persistant udp sockets added
+
+- Net::DNS igntc option set to on (0.53 has bugs with truncated packets and
+  tcp connections)
+
+- minor code cleanups (loops removed, regexps optimized, etc) for
+  speedup
+
+- FreeBSD: first GPLed version
+
+
+--------------------------------------------------------------------------------
+0.1.8.1 beta
+
+- set under GPL (http://www.gnu.org/licenses/gpl.txt)
+
+
+--------------------------------------------------------------------------------
+0.1.8 beta
+
+- Return DUNNO in case of IPv6 Clients
+
+- Splitted NJABL to treat dyn RBL listed clients different
+
+- some regex made case-insensitive
+
+- More details for the foreign MTA if HELO checks failed
+
+- Little cleanups for better reading
+
+
+--------------------------------------------------------------------------------
+0.1.7 beta
+
+- REV_IP_EQ_HELO_DOMAIN regex corrected again
+
+- DNSBL scores adjusted
+
+- $total_dnsbl_score added which holds the overall score of positive
+  DNSBL scores. This affects HELO/IP verification
+
+- Return message for too many DNSBL hits changed, rbl.org link added
+  to this message
+
+- Mails pass now with PREPEND instead of DUNNO and adds a X-policyd-weight
+  header containing the detailed score evaluation plus rate
+
+
+--------------------------------------------------------------------------------
+0.1.6 beta
+
+- if HELO IP is in /24 of Client IP then it is treated as helo_ok
+  (this cause less false positives for MTAs which use a different HELO
+   hostname/IP than MTA's hostname/IP; but are in the same
+   domain/subnet - badly written/administrated www mail interfaces are such a 
+   candidate)
+
+
+--------------------------------------------------------------------------------
+0.1.5 beta
+
+- Cleanup (@array[0] changed to $array[0])
+
+- regexp for REV_IP_EQ_HELO_DOMAIN corrected (again)
+
+- typos fixed
+
+- HELO_IP_IN_CL_SUBNET made configurable
+
+
+--------------------------------------------------------------------------------
+0.1.4 beta
+
+- checks for dialup HELOs added
+
+- failed HELO checks for dialup HELOs now increase dnsb_hits counter
+
+
+--------------------------------------------------------------------------------
+0.1.3 beta
+
+- regexp for REV_IP_EQ_HELO_DOMAIN corrected
+
+
+--------------------------------------------------------------------------------
+0.1.2 beta
+
+- REV_IP_EQ_HELO_DOMAIN check rewritten. It checks now only the part before
+  TLD.
+  
+  HELO foo.bar.com
+  Client Host: blah.bar.com
+
+  It checks now, whether the client or HELO "bar" matches against HELO or client
+  "bar".
+
+
+--------------------------------------------------------------------------------
+0.1.1 beta
+
+- REV_IP_EQ_HELO_DOMAIN did not really a domain check, now it does.
+
+
+--------------------------------------------------------------------------------
+0.1.0 beta
+
+- state changed to beta
+
+- some planned knobs removed
+
+- name changed to policyd-weight
+
+
+--------------------------------------------------------------------------------
+0.0.18 alpha 
+
+- changed /24 score to -0.6
+
+- FROM_MATCHES_NOT_HELO gets extra score per DNSBL hit
+
+- if correct MX record for helo, it gets plus -0.5
+
+
+--------------------------------------------------------------------------------
+0.0.17 alpha
+
+- using now MAXDNSBLHITS. Above this level the mail gets REJECTed immediately.
+
+- checking client IP against helo IPs now also tries a /24 check as last resort.
+  The results of this check may reduce the score by -0.20.
+  A CIDR check will never be performed as this is too expensive.
+
+
+--------------------------------------------------------------------------------
+0.0.16 alpha
+
+- added ix.dnsbl.manitu.net
+
+
+--------------------------------------------------------------------------------
+0.0.15 alpha
+
+- (fix) gettings MX/A records now also asks the MAIL FROM: domain/host
+  (reducing "false positives" if client messed up HELO but the from
+   domain has correct DNS records and matches client IP)
+
+
+--------------------------------------------------------------------------------
+0.0.14 alpha
+
+- If MX/A query failed, it gets lower scored than MX/A forged
+
+- More verbose output
+
+- If _ALL_ DNS queries returned NXDOMAIN then return with 450 and DNSERRMSG
+  when not too much dnsbl listed
+
+
+--------------------------------------------------------------------------------
+0.0.13 alpha
+
+- (fix) getting MX/A records of HELO now also asks parent domains
+
+
+--------------------------------------------------------------------------------
+0.0.12 alpha
+
+- (fix) perl DNS module caused warnings and server misconfigured
+  errors when MX record pointed to a CNAME and we treated it
+  as A-record (CNAME RR: print $foo->address == error)
+
+
+--------------------------------------------------------------------------------
+0.0.11 alpha
+
+- added dnsbl.org
+
+
+--------------------------------------------------------------------------------
+0.0.10 alpha
+
+- set $VERBOSE to default 0
+
+
+--------------------------------------------------------------------------------
+0.0.9 alpha
+
+- removed all other handlers, since the
+  # push @foo, "bar";
+  seems to be ignored on some systems (NOTE: '#`-lines should NEVER
+  get parsed by perl)
+  NOTE: I am dumb. VERBOSE was default 1, and my syslog debug ends not
+        in maillog. I thought it ignored the commenting of "testing". 
+
+
+--------------------------------------------------------------------------------
+0.0.8 alpha
+
+- Changed spamcop back to 4 since it would outweight legitimate mails if
+  they are accidentially listed in spamcop (happened in the past (gmx, web.de))
+  And that is not the purpose of this script.
+
+
+--------------------------------------------------------------------------------
+0.0.7 alpha
+
+- Gave spamcop a score of 8 because it seems reasonable and updated fast
+  and is not a DUL list
+
+
+--------------------------------------------------------------------------------
+0.0.6 alpha
+
+- Client IPs which had no MX, A, PTR record at all did not get scored extra.
+
+- tuned scores some more
+
+- unneeded handlers removed from code (cleanup)
diff --git a/contrib/rc-scripts/freebsd/policyd-weight.sh b/contrib/rc-scripts/freebsd/policyd-weight.sh
new file mode 100755 (executable)
index 0000000..e592a72
--- /dev/null
@@ -0,0 +1,44 @@
+#!/bin/sh
+#
+#
+# PROVIDE: policyd-weight
+# REQUIRE: LOGIN cleanvar
+# KEYWORD: shutdown
+#
+# Add the following lines to /etc/rc.conf to enable policyd-weight:
+# policyd_weight_enable (bool):        
+#       Set it to "YES" to enable policyd-weight.
+#              Default is "NO".
+
+
+. /etc/rc.subr
+
+name="policyd-weight"
+rcvar=policyd_weight_enable
+
+start_cmd=policyd_weight_start
+stop_cmd=policyd_weight_stop
+
+# defaults
+policyd_weight_enable=${policyd_weight_enable:-"NO"}
+
+load_rc_config "policyd_weight"
+
+case "$policyd_weight_enable" in
+    [Yy][Ee][Ss] | 1 | [Oo][Nn] | [Tt][Rr][Uu][Ee]) ;;
+    *) echo "To make use of $name set $rcvar=\"YES\" in /etc/rc.conf" ;;
+esac
+
+command="/usr/local/libexec/postfix/policyd-weight"
+pidfile=/var/run/policyd-weight.pid
+
+policyd_weight_start() {
+    /usr/local/libexec/postfix/policyd-weight start
+}
+
+policyd_weight_stop() {
+    echo "Stopping $name"
+    /usr/local/libexec/postfix/policyd-weight stop
+}
+
+run_rc_command "$1"
diff --git a/documentation.txt b/documentation.txt
new file mode 100644 (file)
index 0000000..c8eb432
--- /dev/null
@@ -0,0 +1,215 @@
+0.1.13                                                        September 02, 2006
+
+
+                          policyd-weight documentation
+
+
+                    1.0 ............ What is policyd-weight
+                    1.1 ........ What is policyd-weight not
+                    1.2 ..... Who should use policyd-weight
+                    1.3 ...................... Requirements 
+                    
+                    2.0 .................. How does it work
+                    2.1 .................. How to set it up
+                    2.2 ... How to read/understand the logs
+                    
+                    3.0 ............................ Thanks
+
+
+
+
+
+
+
+
+
+First things first. This documentation IS INCOMPLETE and does not always  
+reflect the current state of the project which is still beta. To get an 
+additional picture I suggest to also read the changelog (changes.txt). 
+Documentation will be updated as soon as I have a satisfactory stable release 
+in terms of: optimal usage of possible techniques, no more techniques left, 
+optimal resource usage, full reliability.
+
+I invite everyone to help on the documentation part, as this is the most 
+difficult and laborious - and my English is only good enough to get a drink at 
+the local pub or to ask for the right bus station; you'll know what I mean 
+sooner or later  :-)
+
+--
+rob
+
+
+
+
+
+
+                                      --
+
+
+
+
+
+
+1.0 What is policyd-weight
+
+    policyd-weight is a policy server for Postfix written in Perl to score
+    
+    - DNSBLs/RHSBLs
+    - HELO argument
+    - MAIL FROM: argument
+    - Client IP address
+    - DNS client/HELO/FROM entries (A/16 A/24 A/32, PTR/FQDN and Parent Domains
+      MX/16 MX/24 MX/32 for their correctness respectively whether they match.
+
+    Most MTAs have checks for these things built-in, but unfortunately, those 
+    checks are often too restrictive, one hit will cause important mails to 
+    get rejected. Thus most companies are forced to have a rather non-
+    restrictive and even insecure MTA setup so they don't lose important 
+    mails. policyd-weight is intented to be used right after the RCPT TO 
+    command. This way neither the mail headers nor the mail body must be 
+    received. This behaviour is different from other filters that must parse 
+    (and receive) the complete mail.
+
+    With the policyd-weight approach we can reject obviously faked mails and 
+    MTAs that are listed in too many DNSBLs or are poorly configured. To avoid 
+    using extra bandwidth for DNS queries policyd-weight caches the most     
+    frequent client/sender combinations. Also, if DNS lookups are necessary 
+    it does this intentionally serialized to keep lookups to a minimum.
+
+
+    NOTE: It takes some time for new SPAM mailers on the Internet to get listed 
+          in DNSBLs, if they behave well and don't forge everything 
+          SPAM may appear as normal mail. 
+          Filters such as SpamAssassin or amavisd will parse the mail and can 
+          report it to DNSBLs if set up this way (consult your SPAM/virus  
+          scanner's manual).
+
+
+1.1 What is policyd-weight not
+
+    policyd-weight is NOT a SPAM or Virus Filter - as it doesn't parse
+    the contents of the mail.
+
+    Also policyd-weight is not able to reject Mails bounced or forwarded by 
+    correct MTAs.
+    Example: you have an account at yahoo.com, and a have set it to forward
+    mail to your company account. yahoo.com sends with correct MTAs, and thus
+    SPAM received from your yahoo.com account will pass this filter.
+
+
+1.2 Who should use policyd-weight
+
+    For now: for Postfix users that receive or relay mail via SMTP and for 
+    people that 
+    - receive lots of e-mails caught by SpamAssassin or amavisd 
+      (I'm talking about +300/day). 
+    - want to reduce bandwidth-usage caused by bogus mails 
+      (forged SPAM/virus mails)
+    - want to reduce CPU usage caused by scanning bogus mails.
+    - don't want to lose legitimate mails due to overly restrictive 
+      header checks
+    - want to reduce bounce mails from internal servers or filters
+
+
+1.3 Requirements
+
+    Postfix       version 2.1 or higher (tested with 2.1.5 and 2.3.1)
+    Perl 5.8      version 5.8 recommended, 5.6 might work, too
+    Perl modules  Fcntl (standard in Perl 5.8.8)
+                  Sys::Syslog
+                  Net::DNS
+
+    Mail must be accepted directly from the Internet (aka first in line)
+    A fast caching DNS server in your network is highly recommended!
+   
+
+2.0 How does it work
+
+    Well, slightly different from beta to beta. 
+    -> to be continued after a stable release
+
+
+2.1 How to set it up
+
+    - copy policyd-weight to the proper location for your OS, i.e.
+      /usr/libexec/postfix/policyd-weight
+
+    - set correct permissions:
+      chown root /usr/libexec/postfix/policyd-weight
+      chgrp wheel /usr/libexec/postfix/policyd-weight
+      chmod 0755 /usr/libexec/postfix/policyd-weight
+
+    - create a Unix system account for user and group "polw",
+      the user does not need a shell or a home directory
+
+    - create an rc init script or manage otherwise so that
+      "/usr/libexec/postfix/policyd-weight start" gets executed before
+      Postfix at boot-time
+
+    - remove unnecessary reject_rbl_client and reject_rhsbl_client checks 
+      from Postfix' main.cf
+
+    - edit:
+    [main.cf]:
+
+    smtpd_recipient_restrictions =
+        permit_mynetworks,
+        ...
+        reject_unauth_destination,
+        check_policy_service inet:127.0.0.1:12525
+        ...
+
+    Important, keep your old SASL permits (permit_sasl_authenticated), they 
+    must come before check_policy_service
+
+    If you are using Postfix servers on different hosts you can let other 
+    Postfix instances ask the server on which policyd-weight runs by using
+    in their main.cf:
+    
+    smtpd_recipient_restrictions =
+        ...
+        reject_unauth_destination
+        check_policy_service inet:$POLICY_SERVER_IP:12525
+    
+    where $POLICY_SERVER_IP needs to be replaced with the IP of the server
+    which runs policyd-weight.
+
+    Also you need to set $BIND_ADDRESS = 'all'; in /policyd-weight.conf
+    Make sure that only your own Postfix servers are allowed to connect to
+    that port by adjusting your firewall rules. policyd-weight has NO ACL
+    mechanism for that due to performance and anti-DoS reasons. 
+
+
+    For adjusting scores or other policyd-weight parameters you can create
+    /etc/policyd-weight.conf and insert the changed parameter/value there.
+    To see the available configuration options execute
+    "policyd-weight defaults". It is good practice to only add changed 
+    parameters to the config file and omit the defaults, so the file
+    can more easily be maintained.
+
+    "policyd-weight --help" gives a short help on how to use the 
+    command-line switches.
+
+
+2.2 How to read/understand the logs
+
+    To see mails rejected by policyd-weight:
+
+       grep "policyd-weight.*action=" /var/log/maillog | grep -v DUNNO
+    
+    To see mails accepted by policyd-weight:
+
+       grep "policyd-weight.*action=" /var/log/maillog | grep DUNNO
+
+    ...to be continued.
+
+
+3.0 Thanks
+
+    Ralf Hildebrandt, it was him who set me on fire, also for his tests.
+    Bob Tito, for testing and feeding me with results
+    Philipp Koller for his patches and help on Solaris, documentation and 
+    website.
+    All Spammers that provided me with food and enlargement pills.
+    To the mailing-list users which reported bugs and odd behavior.
+
diff --git a/man/man5/policyd-weight.conf.5 b/man/man5/policyd-weight.conf.5
new file mode 100644 (file)
index 0000000..e080e26
--- /dev/null
@@ -0,0 +1,387 @@
+.TH policyd-weight.conf 5 "Aug 25th, 2006"
+.ad
+.fi
+.SH "NAME"
+policyd-weight.conf
+\-
+policyd-weight configuration parameters
+.SH "STATUS"
+Beta, Documentation incomplete
+
+.SH "DESCRIPTION"
+.ad
+.fi
+\fBpolicyd-weight\fR uses a \fBperl\fR(1) style configuration file which it
+reads on startup. The cache re-reads the configuration after 
+\fB$MAINTENANCE_LEVEL\fR (default: 5) queries. If \fB-f\fR is not specified, 
+it searches for configuration files on following locations:
+.P
+ /etc/policyd-weight.conf
+.br 
+ /usr/local/etc/policyd-weight.conf
+.br
+ ./policyd-weight.conf
+
+.SH "CACHE SETTINGS"
+.ad
+.fi
+.IP "\fB$CACHESIZE\fR (default: 2000)"
+Set the minimum size of the SPAM cache.
+
+.IP "\fB$CACHEMAXSIZE\fR (default: 4000)"
+Set the maximum size of the SPAM cache.
+
+.IP "\fB$CACHEREJECTMSG\fR 
+.br
+(default: 550 temporarily blocked because of previous errors)"
+
+Set the SMTP status code and a explanatory message for rejected mails due to
+cached results
+
+.IP "\fB$NTTL\fR (default: 1)
+The client is penalized for that many retries.
+
+.IP "\fB$NTIME\fR (default: 30)
+The \fB$NTTL\fR counter will only be decremented if the client waits at least
+\fB$NTIME\fR seconds.
+
+.IP "\fB$POSCACHESIZE\fR (default: 1000)"
+Set the minimum size of the HAM cache.
+
+.IP "\fB$POSCACHEMAXSIZE\fR (default: 2000)"
+Set the maximum size of the HAM cache.
+
+.IP "\fB$PTTL\fR (default: 60)"
+After that many queries the HAM entry must succeed one run through the
+RBL checks again.
+
+.IP "\fB$PTIME\fR (default: 3h)"
+after $PTIME in HAM Cache the client must pass one time the RBL checks again.
+Values must be nonfractal. Accepted time-units: s(econds), m(inutes), 
+h(ours), d(ays)
+
+.IP "\fB$TEMP_PTIME\fR (default: 1d)"
+The client must pass this time the RBL 
+checks in order to be listed as hard-HAM. After this time the client will pass
+immediately for PTTL within PTIME. Values must be non-fractal.
+Accepted time-units: s(econds), m(inutes), h(ours), d(ays)
+
+.SH "DEBUG SETTINGS"
+.ad
+.fi
+.IP "\fB$DEBUG\fR (default: 0)"
+Turn debugging on (1) or off (0)
+
+.SH "DNS SETTINGS"
+.ad
+.fi
+.IP "\fB$DNS_RETRIES\fR (default: 2)"
+.br
+How many times a single DNS query may be repeated
+.IP "\fB$DNS_RETRY_IVAL\fR (default: 2)"
+.br
+Retry a query without response after that many seconds
+.IP "\fB$MAXDNSERR\fR (default: 3)"
+.br
+If that many queries fail, the mail is accepted with \fB$MAXDNSERRMSG\fR.
+.br
+In total DNS queries this means: $MAXDNSERR * $DNS_RETRIES
+
+.SH "MISC SETTINGS"
+.ad
+.fi
+.IP "\fB$MAINTENANCE_LEVEL\fR (default: 5)"
+After that many policy requests the cache (and in daemon mode childs)
+checks for configuration file changes
+
+.IP "\fB$MAXIDLECACHE\fR (default: 60)"
+After that many seconds of being idle the cache checks for configuration
+file changes.
+
+.IP "\fB$PIDFILE\fR (default: /var/run/policyd-weight.pid)"
+Path and filename to store the master pid (daemon mode)
+
+.IP "\fB$LOCKPATH\fR (default: /tmp/.policyd-weight/)"
+Directory where policyd-weight stores sockets and lock-files/directories. Its
+argument must contain a trailing slash.
+
+.IP "\fB$SPATH\fR (default: $LOCKPATH.'/polw.sock')"
+Path and filename which the cache has to use for communication.
+
+.IP "\fB$TCP_PORT\fR (default: 12525)"
+TCP port on which the policy server listens (daemon mode)
+
+.IP "\fB$BIND_ADDRESS\fR (default: '127.0.0.1')"
+IP Address on which policyd-weight binds. Currently either only one or all
+IPs are supported. Specify 'all' if you want to listen on all IPs.
+
+.IP "\fB$SOMAXCONN\fR (default: 1024)"
+Maximum connections which policyd-weight accepts. This is set high enough to
+cover most scenarios.
+
+.IP "\fB$USER\fR (default: polw)"
+Set the user under which policyd-weight runs
+.IP "\fB$GROUP\fR (default: $USER)"
+Set the group under which policyd-weight runs
+
+.SH "OUTPUT AND LOG SETTINGS"
+.ad
+.fi
+.IP "\fB$ADD_X_HEADER\fR (default: 1)"
+Insert a X-policyd-weight: header with evaluation messages.
+.br
+1 = on, 0 = off
+
+.IP "\fB$LOG_BAD_RBL_ONLY\fR (default: 1)"
+Insert only RBL results in logging strings if the RBL score changes the overall
+score. Thus RBLs with a GOOD SCORE of 0 don't appear in logging strings if the
+RBL returned no BAD hit.
+.br
+1 = on, 0 = off
+
+.IP "\fB$MAXDNSBLMSG\fR (default: 550 Your MTA is listed in too many DNSBLs)"
+The message sent to the client if it was reject due to \fB$MAXDNSBLHITS\fR 
+and/or \fB$MAXDNSBLSCORE\fR.
+
+.IP "\fB$REJECTMSG\fR (default: 550 Mail appeared to be SPAM or forged. Ask your Mail/DNS-Adminisrator to correct HELO and DNS MX settings or to get removed from DNSBLs)"
+.br
+
+Set the SMTP status code for rejected mails and a message why the action was 
+taken
+
+.SH "RESOURCE AND OPTIMIZATIONS"
+.ad
+.fi
+
+.IP "\fB$CHILDIDLE\fR (default: 120)"
+How many seconds a child may be idle before it dies (daemon mode)
+
+.IP "\fB$MAX_PROC\fR (default: 50)"
+Process limit on how many processes policyd-weight will spawn (daemon mode)
+
+.IP "\fB$MIN_PROC\fR (default: 2)"
+Minimum childs which are kept alive in idle times (daemon mode)
+
+.IP "\fB$PUDP\fR (default: 0)"
+.br
+Set persistent UDP connections used for DNS queries on (1) or off (0).
+
+
+.SH "SCORE SETTINGS"
+.ad
+.fi
+Positive values indicate a bad (SPAM) score, negative values indicate a 
+good (HAM) score.
+
+.IP "\fB@bogus_mx_score\fR (2.1, 0)"
+If the sender domain has neither MX nor A records or these records resolve
+to a bogus IP-Address (for instance private networks) then this check asigns
+the full score of bogus_mx_score. If there is no MX but an A record of the
+sender domain then it receives a penalty only if DNSBL-listed.
+
+Log Entries: 
+
+\fBBOGUS_MX\fR
+.in +1
+The sender A and MX records are bogus or empty.
+.in -1
+
+\fBBAD_MX\fR
+.in +1
+The sender domain has an empty or bogus MX record and the client is DNSBL 
+listed.
+.in -1
+
+
+Related RFCs:
+
+[1918] Address Allocation for Private Internets
+.br
+[2821] Simple Mail Transfer Protocol (Sect 3.6 and Sect 5)
+
+
+.IP "\fB@client_ip_eq_helo_score\fR (1.5, -1.25)"
+Define scores for the match of the reverse record (hostname) against the
+HELO argument. Reverse lookups are done, if the forward lookups failed and are
+not trusted.
+
+Log Entries: 
+
+\fBREV_IP_EQ_HELO\fR
+.in +1
+The  Client's  PTR  matched  the  HELO  argument.
+.in -1
+
+\fBREV_IP_EQ_HELO_DOMAIN\fR
+.in +1
+Domain portions  of Client PTR and HELO argument matched.
+.in -1
+
+\fBRESOLVED_IP_IS_NOT_HELO\fR
+.in +1
+Client  PTRs  found   but  did  not  match  HELO argument.
+.in -1
+
+.IP "\fB@helo_score\fR (1.5, -2)"
+Define scores for the match of the Client IP and its /24 subnet against the A 
+records of HELO or MAIL FROM domain/host. It also holds the bad score for MX 
+verifications.
+
+Log Entries:
+
+\fBCL_IP_EQ_HELO_NUMERIC\fR
+.in +1
+Client IP matches the [IPv4] HELO.
+.in -1
+
+\fBCL_IP_EQ_FROM_IP\fR
+.in +1
+Client IP matches  the A record of the MAIL FROM sender domain/host.
+.in -1
+
+\fBCL_IP_EQ_HELO_IP\fR
+.in +1
+Client  IP  matches  the  A  record  of the HELO argument.
+.in -1
+
+\fBCL_IP_NE_HELO\fR
+.in +1
+The IP and  the /24  subnet did  not  match A/MX records  of  HELO  and MAIL
+FROM  arguments and their subdomains.
+.in -1
+
+.IP "\fB@helo_from_mx_eq_ip_score\fR (1.5, -3.1)"
+Define scores for the match of Client IP against MX records. Positive (SPAM) 
+values are used in case the MAIL FROM matches not the HELO argument 
+\fBAND\fR the client seems to be dynamic \fBAND\fR the client is no MX for HELO
+and MAIL FROM arguments. The total DNSBL score is added to its bad score.
+
+Log Entries:
+
+\fBCL_IP_EQ_FROM_MX\fR
+.in +1
+Client IP  matches  the MAIL FROM domain/host MX record
+.in -1
+
+\fBCL_IP_EQ_HELO_MX\fR
+.in +1
+Client IP matches the HELO domain/host MX record
+.in -1
+
+\fBCLIENT_NOT_MX/A_FROM_DOMAIN\fR
+.in +1
+Client is not a verified  HELO and doesn't match A/MX records of MAIL FROM 
+argument
+.in -1
+
+\fBCLIENT/24_NOT_MX/A_FROM_DOMAIN\fR
+.in +1
+Client's subnet does  not  match A/MX records of the MAIL FROM argument
+.in -1
+
+.IP "\fB$dnsbl_checks_only\fR (default: 0)"
+Disable HELO/RHSBL verifications and the like. Do only RBL checks. 
+.br
+1 = on, 0 = off
+.IP "\fB@dnsbl_score\fR (default: see below)"
+A list of RBLs to be checked. If you want that a host is not being evaluated
+any further if it is listed on several lists or a very trustworthy list you
+can control a immediate REJECT with \fB$MAXDNSBLHITS\fR and/or 
+\fB$MAXDNSBLSCORE\fR. A list of RBLs must be build as follows:
+.br
+
+@dnsbl_score = (
+.br
+    RBLHOST1,   HIT SCORE,  MISS SCORE,     LOG NAME,
+.br
+    RBLHOST2,   HIT SCORE,  MISS SCORE,     LOG NAME,
+.br
+    ...
+.br
+);
+.br
+The default is:
+
+@dnsbl_score = (
+    "pbl.spamhaus.org",     3.25,   0,      "DYN_PBL_SPAMHAUS",
+    "dnsbl.njabl.org",      4.25,   -1.5,   "BL_NJABL",
+    "bl.spamcop.net",       1.75,   -1.5,   "SPAMCOP",
+    "sbl-xbl.spamhaus.org", 4.35,   -1.5,   "SBL_XBL_SPAMHAUS",
+    "list.dsbl.org",        4.35,   0,      "DSBL_ORG",
+    "ix.dnsbl.manitu.net",  4.35,   0,      "IX_MANITU",
+    "relays.ordb.org",      3.25,   0,      "ORDB_ORG"
+.br
+);
+
+.IP "\fB@rhsbl_score\fR (default: see below)"
+Define a list of RHSBL host which are queried for the sender domain. Results
+get additionaly scores of 0.5 * DNSBL results and \fB@rhsbl_penalty_score\fR.
+A list of RHSBL hosts to be queried must be build as follows:
+.br
+
+@rhsbl_score = (
+.br 
+    RHSBLHOST1,  HIT SCORE,  MISS SCORE,     LOG NAME,
+.br
+    RHSBLHOST2,  HIT SCORE,  MISS SCORE,     LOG NAME,
+.br
+    ...
+.br
+);
+.br
+The default is:
+
+@rhsbl_score = (
+    "rhsbl.ahbl.org",              1.8,     0,  "AHBL",
+    "dsn.rfc-ignorant.org",        3.2,     0,  "DSN_RFCI",
+    "postmaster.rfc-ignorant.org", 1 ,      0,  "PM_RFCI",
+    "abuse.rfc-ignorant.org",      1,       0,  "ABUSE_RFCI"
+.br
+);
+
+.IP "\fB@rhsbl_penalty_score\fR (3.1, 0)"
+This score will be added to each RHSBL hit if following criterias are met:
+
+    Sender has a random local-part (i.e. yztrzgb@example.tld)
+
+ or MX records of sender domain are bogus
+
+ or FROM matches not HELO
+
+ or HELO is untrusted (Forward record matched, reverse record 
+    did not match)
+
+.IP "\fB$MAXDNSBLHITS\fR (default: 2)"
+If the client is listed in more than $MAXDNSBLHITS RBLs it will be rejected
+immediately with \fB$MAXDNSBLMSG\fR and without further evaluation. Results
+are cached by default.
+
+.IP "\fB$MAXDNSBLSCORE\fR (default: 8)"
+If the BAD SCOREs of \fB@dnsbl_score\fR listed RBLs reach a level greater than 
+$MAXDNSBLSCORE the client will be rejected immediately with \fB$MAXDNSBLMSG\fR 
+and without further evaluation. Results are cached by default.
+
+.IP "\fB$REJECTLEVEL\fR (default: 1)"
+Score results equal or greater than this level will be rejected with 
+\fB$REJECTMSG\fR
+
+
+
+.SH "SEE ALSO"
+.na
+.nf
+policyd-weight(8), Policyd-weight daemon
+perl(1), Practical Extraction and Report Language
+perlsyn(1), Perl syntax
+access(5), Postfix SMTP access control table
+.IP
+.SH "LICENSE"
+.na
+.nf
+GNU General Public License
+.SH "AUTHOR"
+.na
+.nf
+Robert Felber <r.felber@ek-muc.de>
+Autohaus Erich Kuttendreier
+81827 Munich, Germany
diff --git a/man/man8/policyd-weight.8 b/man/man8/policyd-weight.8
new file mode 100644 (file)
index 0000000..bde846d
--- /dev/null
@@ -0,0 +1,169 @@
+.TH policyd-weight 8 "Aug 25th, 2006"
+.SH "NAME"
+policyd-weight \- weighted SMTP policy daemon
+
+.SH "STATUS"
+Beta, Documentation incomplete
+
+.SH "SYNOPSIS"
+.na
+.nf
+.fi
+\fBpolicyd-weight\fR [\fB-option\fR] [\fB-option2 <arg>\fR] \fIcommand\fR
+
+.SH "DESCRIPTION"
+.ad
+.fi
+\fBpolicyd-weight\fR(8) is a SMTP policy daemon written in \fBperl\fR(1) for 
+\fBpostfix\fR(1). It
+evaluates based on RBL/RHSBL results, HELO and MAIL FROM domain and subdomain
+arguments and the client IP address the possibility of forgery or SPAM. It is
+designed to be called before the SMTP DATA command at the RCPT TO stage. 
+This way it is a) possible
+to reject a mail attempt before the body has been received and b) to keep
+multirecipient mail intact, i.e. provide the functionality of selective usage
+based on recipients.
+
+To make \fBpolicyd-weight\fR(8) work with \fBpostfix\fR(1), it is required to 
+add a system 
+account for $USER (default: polw)
+
+Policyd-weight can operate in master.cf \fBor\fR daemon mode. In master.cf
+mode it uses postfix' \fBspawn\fR(8), which results in number of simultanous 
+requests perl instances. In daemon mode it uses shared memory and forks on 
+load, and only if all childs are busy.
+
+At the time of writing the man-pages for policyd-weight assume a postfix
+installation. It has been reported that policyd-weight works with other MTAs
+like Exim, too.
+
+.SH "SETUP"
+.SH "master.cf mode:"
+.IP "\fBmaster.cf:\fR"
+.in -7
+policy   unix   -   n   n   -   -   spawn   user=polw    
+.br    
+\ \ \ argv=/usr/bin/perl /usr/local/bin/policyd-weight
+.IP "\fBmain.cf:\fR"
+.in -7
+smtpd_recipient_restrictions =
+.br
+\ \ \ permit_mynetworks, 
+.br
+\ \ \ ... authenticated permits ...
+.br
+\ \ \ reject_unauth_destination,
+.br
+\ \ \ ... whitelists, role accounts, clients ...
+.br
+\ \ \ check_policy_service unix:private/policy
+.br
+.in 7
+
+.SH "daemon mode:"
+start the daemon with \fBpolicyd-weight start\fR. Poliyd-weight then listens
+on $TCP_PORT (default: 12525)  for policy requests.
+To make postfix talk to that port do following changes to main.cf:
+.IP "\fBmain.cf:\fR"
+.in -7
+smtpd_recipient_restrictions =
+.br
+\ \ \ permit_mynetworks,
+.br
+\ \ \ ... authenticated permits ...
+.br
+\ \ \ reject_unauth_destination,
+.br
+\ \ \ ... whitelists, role accounts, clients ...
+.br
+\ \ \ check_policy_service inet:127.0.0.1:12525
+.br
+.in 7
+
+It is possible to have more than one postfix server talk to the 
+\fBdaemonized\fR policyd-weight by configuring each postfix machine to query
+the policy server with check_policy_service inet:IP:12525 where IP is the
+host on which policyd-weight runs.
+
+
+Please note that \fBcheck_policy_service\fR should come at last, or at least
+\fBafter\fR reject_unauth_destination, or else you may become an open relay.
+.SH "COMMANDS"
+.ad
+.fi
+Following commands exist and are reserved for daemon mode only:
+.IP "\fBstart\fR     start the policy server"
+.IP "\fBstop\fR      stop the policy server"
+.IP "\fBrestart\fR   restart the policy server"
+.IP "\fBreload\fR    tells the policy server to reload its configuration"
+.IP "\fBdefaults\fR  prints the default settings to STDOUT and exits"
+
+.SH "OPTIONS"
+.ad
+.fi
+.IP "\fB-d\fR operate in debug mode"
+\fBNot for use in master.cf\fR.
+In debug mode everything is reported on STDOUT instead of \fBsyslog\fR(3).
+Also an own debug cache daemon will be spawned. The socket-file is
+named after the value of $SPATH with ".debug" as suffix.
+
+.IP "\fB-f\fR /path/to/file 
+Pass a configuration file to policyd-weight
+
+.IP "\fB-h\fR show help"
+
+.IP "\fB-k\fR kill cache daemon"
+\fBNot for use in master.cf\fR.
+Together with \fB-d\fR this kills the debug cache daemon. Without \fB-d\fR it
+kills the global running cache daemon.
+
+.IP "\fB-s\fR show cache entries"
+\fBNot for use in master.cf\fR.
+
+.IP "\fB-v\fR show version"
+
+.SH "LOGGING"
+.ad
+.fi
+Logging is done via \fBsyslog\fR(3) with facility "mail" and priority 
+"info". For a complete list of log entries and their correspondending configuration parameters refer to \fBpolicyd-weight.conf\fR(5).
+.SH "BUGS"
+.na
+.nf
+Please report bugs to r.felber@ek-muc.de
+
+.SH "HISTORY"
+.ad
+.fi
+.IP "March 2005"
+Ralf Hildebrandt (Author of the Book of Postfix) is the spiritual father
+of policyd-weight. It was his idea to have a scored RBL evaluation, I've
+added the weighted MAIL FROM/HELO DNS-evaluation. For that purpose I used
+Meng Wong's spf.pl which was shipped with the postfix source as example.
+
+.SH "FILES"
+.na
+.nf
+/etc/policyd-weight.conf, Policyd-weight configuration file
+/etc/postfix/main.cf, Postfix configuration parameters
+/etc/postfix/master.cf, Postfix daemon processes
+.fi
+
+.SH "SEE ALSO"
+.na
+.nf
+policyd-weight.conf(5), Policyd-weight configuration file
+master(5), Postfix master.cf file syntax
+postconf(5), Postfix main.cf file syntax
+access(5), Postfix SMTP access control table
+
+.SH "LICENSE"
+.na
+.nf
+GNU General Public License 
+.SH "AUTHOR"
+.na
+.nf
+Robert Felber <r.felber@ek-muc.de>
+Autohaus Erich Kuttendreier
+81827 Munich, Germany
diff --git a/policyd-weight b/policyd-weight
new file mode 100755 (executable)
index 0000000..aaa3054
--- /dev/null
@@ -0,0 +1,3812 @@
+#!/usr/bin/env perl
+#
+# Copyright (c) 2005-2006 Robert Felber 
+# (Autohaus Erich Kuttendreier, Munich, http://www.kuttendreier.de)
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# A copy of the GPL can be found at http://www.gnu.org/licenses/gpl.txt
+#
+# Parts of code based on postfix-policyd-spf by Meng Wen Wong, version 1.06,
+# see http://spf.pobox.com/
+#
+# AUTHOR:  r.felber@ek-muc.de
+# DATE:    Wed Sep 24 10:49:10 CET 2008
+# NAME:    policyd-weight
+# VERSION: 0.1.15 devel-1
+# URL:     http://www.policyd-weight.org/
+
+
+# ----------------------------------------------------------
+#           minimal documentation
+# ----------------------------------------------------------
+
+#
+# Weighted Postfix SMTPD policy server.
+# 
+# This program assumes you have read Postfix' 
+# README_FILES/SMTPD_POLICY_README
+# If not, head to:
+# http://www.postfix.org/SMTPD_POLICY_README.html
+#
+#
+#
+# Logging is sent to syslogd.
+#
+# ----------------------------------------------------------------------
+# To run this in init mode:
+#
+#    % /path/to/policyd-weight start
+#
+# /etc/postfix/main.cf:
+#
+#    smtpd_recipient_restrictions =
+#    ...
+#    reject_unauth_destination
+#    ...
+#    check_policy_service inet:127.0.0.1:12525
+#
+# 
+# NOTE: specify check_policy_service AFTER reject_unauth_destination
+# or else your system can become an open relay.
+
+# begin
+use strict;
+use Fcntl;
+use File::Spec;
+use Sys::Syslog qw(:DEFAULT setlogsock);
+use Net::DNS;
+use Net::IP;
+use Net::DNS::Packet qw(dn_expand);
+use IO::Socket::INET;
+use IO::Socket::UNIX;
+use IO::Select;
+use Config;
+use POSIX;
+use Carp qw(cluck longmess);
+
+
+use vars qw($csock $s $tcp_socket $sock $new_sock $old_mtime);
+
+our $VERSION   = "0.1.15 devel-1";
+our $CVERSION  = 5;                 # cache interface version
+our $CMD_DEBUG = 0;                 # -d switch 
+our $KILL;                          # -k switch
+our $STATS;                         # -s switch
+our $DAEMONIZE;                     # start   action
+our $RESTART;                       # restart action
+our $RELOAD;                        # reload  action
+our $STOP;                          # stop    action
+our $FOREGROUND;
+my  $run_action;                    # marker whether any action has been used
+my  $conf;                          # path to config file
+
+my $arg_iter;
+my $ignore;
+for(@ARGV)
+{
+    $arg_iter++;
+    next if ($_ eq $ignore);
+    $ignore = '';
+    if($_ eq "-d")
+    {
+        $^W        = 1;
+        $CMD_DEBUG = 1;
+    }
+    elsif($_ eq '-f')
+    {
+        if( -f $ARGV[$arg_iter])
+        {
+            $conf = $ARGV[$arg_iter];
+            $ignore = $ARGV[$arg_iter];
+            next;
+        }
+        else
+        {
+            print "configfile ".$ARGV[$arg_iter]." doesn't exist\n";
+            exit "-1";
+        }
+    }
+    elsif($_ eq '-k')
+    {
+        $KILL  = 1;   
+    }
+    elsif($_ eq '-s')
+    {
+        $STATS = 1;
+    }
+    elsif($_ eq '-D')
+    {
+        $FOREGROUND = 1;
+    }
+    elsif($_ =~ /-[-]*h/)
+    {
+        usage();
+    }
+    elsif($_ =~ /-[-]*v/)
+    {
+        my $net_dns_ver = Net::DNS->version;
+        my $os          = `uname -rs`;
+        print <<EOF;
+policyd-weight version: $VERSION, CacheVer: $CVERSION
+Perl version:           $]
+Net::DNS version:       $net_dns_ver
+OS:                     $os
+EOF
+        exit;
+    }
+    elsif($_ eq "start")
+    {
+        usage() if ($run_action);
+
+        if(!($< == 0 || $CMD_DEBUG))
+        {
+            die "You must be root in order to use \"start\"!\n";
+        }
+
+        $DAEMONIZE  = 1;
+        $run_action = 1;
+    }
+    elsif($_ eq "defaults")
+    {
+        my $del;
+        open(POLW, "<$0") || die "open: $0: $!\n";
+           print "# ----------------------------------------------------------------\n";
+           print "#  policyd-weight configuration (defaults) Version $VERSION \n";
+           print "# ----------------------------------------------------------------\n";
+        while (<POLW>)
+        {
+            if (/^#--BEGIN_CONFDEF/) 
+            {
+                $del = 1;
+                next;
+            }
+            if ($del) 
+            {
+                if (/^#--END_CONFDEF/) 
+                {
+                     last;
+                } 
+                else 
+                {
+                     $_ =~ s/^my /   /;
+                     print $_;
+                }
+             }
+        }
+        close(POLW);
+        exit;
+    }
+    elsif($_ eq "restart")
+    {
+        usage() if ($run_action);
+
+        if(!($< == 0 || $CMD_DEBUG))
+        { 
+            die "You must be root in order to use \"restart\"!\n";
+        }
+
+        $STOP       = 1;
+        $RESTART    = 1;
+        $DAEMONIZE  = 1;
+        $run_action = 1;
+    }
+    elsif($_ eq "stop")
+    {
+        usage() if ($run_action);
+
+        if(!($< == 0 || $CMD_DEBUG))
+        {
+            die "You must be root in order to use \"stop\"!\n";
+        }
+
+        $DAEMONIZE  = 1;
+        $STOP       = 1;
+        $run_action = 1;
+    }
+    elsif($_ eq "reload")
+    {
+        usage() if ($run_action);
+
+        if(!($< == 0 || $CMD_DEBUG))
+        {
+            die "You must be root in order to use \"reload\"!\n";
+        }
+
+        $RELOAD = 1;
+    }
+    else
+    {
+        print "policyd-weight: unknown option $_\n";
+        usage(1);
+    }
+    
+}
+
+
+sub usage
+{
+    my $ret = shift;
+
+    print <<EOF;
+Usage: policyd-weight [-option -option2 <arg>] [stop|start|restart|defaults]
+Args in [ ] are optional.
+
+Options
+    -D                   Don't detach master - run master in foreground
+    -d                   Debug, don't daemonize, log to STDOUT
+    -f /path/to/file     Specify a configuration file
+    -h                   This help
+    -k                   Kill cache instance
+    -s                   Show  cache entries and exit. With -d show debug
+                         cache entries
+    -v                   Show version and exit
+
+Actions
+    stop                 Stops the policyd-weight  daemon, add -k to also
+                         Stop the cache. In addition with -d -k it stops
+                         the debug cache.
+
+    start                Starts the policyd-weight daemon. Add -d to start a 
+                         debug session in foregorund.
+
+    restart              Restarts  policyd-weight. Together with -d it
+                         restarts a debug session in foreground.
+
+    reload               Reload the configuration file
+
+    defaults             Output default configuration
+
+If no action is given it waits for data on STDIN.
+WARNING: do NOT use options or actions in master.cf!
+EOF
+
+    exit($ret);
+}
+
+if($CMD_DEBUG)
+{
+    $^W = 1;
+    print "policyd-weight version: ".$VERSION.", CacheVer: $CVERSION\nSystem: ";
+    system("uname -a");
+    print "Perl version: ".$]."\n";
+}
+
+#
+# store signal-name to number conversions for better accessibility
+#
+our %sig_list;
+my  $i;
+foreach(split(' ', $Config{sig_name})) 
+{
+    $sig_list{$_} = $i++;
+}
+
+
+#
+# Print Module Versions if -d requested
+#
+if($CMD_DEBUG)
+{
+    print "Net::DNS version: " . Net::DNS->version . "\n";
+}
+
+
+# don't let warnings confuse the SMTP, feed die() lines to syslog
+$SIG{__DIE__} = sub {
+    mylog(warning=>"err: @_");
+};
+
+# ----------------------------------------------------------
+#           configuration (defaults)
+# ----------------------------------------------------------
+# don't make changes here, instead use/create /etc/policyd-weight.conf
+# NOTE: use perl syntax inclusive `;' in configuration files.
+#
+#--BEGIN_CONFDEF
+
+
+my $DEBUG        = 0;               # 1 or 0 - don't comment
+
+my $REJECTMSG    = "550 Mail appeared to be SPAM or forged. Ask your Mail/DNS-Administrator to correct HELO and DNS MX settings or to get removed from DNSBLs";
+
+my $REJECTLEVEL  = 1;               # Mails with scores which exceed this
+                                    # REJECTLEVEL will be rejected
+
+my $DEFER_STRING = 'IN_SPAMCOP= BOGUS_MX='; 
+                                    # A space separated case-sensitive list of
+                                    # strings on which if found in the $RET
+                                    # logging-string policyd-weight changes
+                                    # its action to $DEFER_ACTION in case
+                                    # of rejects.
+                                    # USE WITH CAUTION!
+                                    # DEFAULT: "IN_SPAMCOP= BOGUS_MX="
+
+
+my $DEFER_ACTION = '450';           # Possible values: DEFER_IF_PERMIT,
+                                    # DEFER_IF_REJECT, 
+                                    # 4xx response codes. See also access(5)
+                                    # DEFAULT: 450
+
+my $DEFER_LEVEL  = 5;               # DEFER mail only up to this level
+                                    # scores greater than DEFER_LEVEL will be
+                                    # rejected
+                                    # DEFAULT: 5
+
+my $DNSERRMSG         = '450 No DNS entries for your MTA, HELO and Domain. Contact YOUR administrator';
+
+my $dnsbl_checks_only = 0;          # 1: ON, 0: OFF (default)
+                                    # If ON request that ALL clients are only
+                                    # checked against RBLs
+
+my @dnsbl_checks_only_regexps = (
+    # qr/[^.]*(exch|smtp|mx|mail).*\..*\../,
+    # qr/yahoo.com$/
+);                                  # specify a comma-separated list of regexps
+                                    # for client hostnames which shall only
+                                    # be RBL checked. This does not work for
+                                    # postfix' "unknown" clients.
+                                    # The usage of this should not be the norm
+                                    # and is a tool for people which like to
+                                    # shoot in their own foot.
+                                    # DEFAULT: empty
+                                    
+
+my $LOG_BAD_RBL_ONLY  = 1;          # 1: ON (default), 0: OFF
+                                    # When set to ON it logs only RBLs which
+                                    # affect scoring (positive or negative)
+                                    
+## DNSBL settings
+my @dnsbl_score = (
+#    HOST,                    HIT SCORE,  MISS SCORE,  LOG NAME
+    'pbl.spamhaus.org',       3.25,          0,        'DYN_PBL_SPAMHAUS',
+    'sbl-xbl.spamhaus.org',   4.35,       -1.5,        'SBL_XBL_SPAMHAUS',
+    'bl.spamcop.net',         3.75,       -1.5,        'SPAMCOP',
+    'dnsbl.njabl.org',        4.25,       -1.5,        'BL_NJABL',
+    'ix.dnsbl.manitu.net',    4.35,          0,        'IX_MANITU',
+    'rbl.ipv6-world.net',     4.25,          0,        'IPv6_RBL'
+);
+
+my $MAXDNSBLHITS  = 2;  # If Client IP is listed in MORE
+                        # DNSBLS than this var, it gets
+                        # REJECTed immediately
+
+my $MAXDNSBLSCORE = 8;  # alternatively, if the score of
+                        # DNSBLs is ABOVE this
+                        # level, reject immediately
+
+my $MAXDNSBLMSG   = '550 Your MTA is listed in too many DNSBLs';
+
+## RHSBL settings
+my @rhsbl_score = (
+    'multi.surbl.org',             4,        0,        'SURBL',
+    'rhsbl.ahbl.org',              4,        0,        'AHBL',
+    'dsn.rfc-ignorant.org',        3.5,      0,        'DSN_RFCI',
+    'postmaster.rfc-ignorant.org', 0.1,      0,        'PM_RFCI',
+    'abuse.rfc-ignorant.org',      0.1,      0,        'ABUSE_RFCI'
+);
+
+my $BL_ERROR_SKIP     = 2;  # skip a RBL if this RBL had this many continuous
+                            # errors
+
+my $BL_SKIP_RELEASE   = 10; # skip a RBL for that many times
+
+## cache stuff
+my $LOCKPATH          = '/tmp/.policyd-weight/';    # must be a directory (add
+                                                    # trailing slash)
+
+my $SPATH             = $LOCKPATH.'/polw.sock';     # socket path for the cache
+                                                    # daemon. 
+
+my $MAXIDLECACHE      = 60; # how many seconds the cache may be idle
+                            # before starting maintenance routines
+                            # NOTE: standard maintenance jobs happen
+                            # regardless of this setting.
+
+my $MAINTENANCE_LEVEL = 5;  # after this number of requests do following
+                            # maintenance jobs:
+                            # checking for config changes
+
+# negative (i.e. SPAM) result cache settings ##################################
+
+my $CACHESIZE       = 2000; # set to 0 to disable caching for spam results. 
+                            # To this level the cache will be cleaned.
+
+my $CACHEMAXSIZE    = 4000; # at this number of entries cleanup takes place
+
+my $CACHEREJECTMSG  = '550 temporarily blocked because of previous errors';
+
+my $NTTL            = 1;    # after NTTL retries the cache entry is deleted
+
+my $NTIME           = 30;   # client MUST NOT retry within this seconds in order
+                            # to decrease TTL counter
+
+
+# positve (i.,e. HAM) result cache settings ###################################
+
+my $POSCACHESIZE    = 1000; # set to 0 to disable caching of HAM. To this number
+                            # of entries the cache will be cleaned
+
+my $POSCACHEMAXSIZE = 2000; # at this number of entries cleanup takes place
+
+my $POSCACHEMSG     = 'using cached result';
+
+my $PTTL            = 60;   # after PTTL requests the HAM entry must
+                            # succeed one time the RBL checks again
+
+my $PTIME           = '3h'; # after $PTIME in HAM Cache the client
+                            # must pass one time the RBL checks again.
+                            # Values must be nonfractal. Accepted
+                            # time-units: s, m, h, d
+
+my $TEMP_PTIME      = '1d'; # The client must pass this time the RBL
+                            # checks in order to be listed as hard-HAM
+                            # After this time the client will pass
+                            # immediately for PTTL within PTIME
+
+
+## DNS settings
+my $DNS_RETRIES     = 2;    # Retries for ONE DNS-Lookup
+
+my $DNS_RETRY_IVAL  = 2;    # Retry-interval for ONE DNS-Lookup
+
+my $MAXDNSERR       = 3;    # max error count for unresponded queries
+                            # in a complete policy query
+
+my $MAXDNSERRMSG    = 'passed - too many local DNS-errors';
+
+my $PUDP            = 0;    # persistent udp connection for DNS queries.
+                            # broken in Net::DNS version 0.51. Works with
+                            # Net::DNS 0.53; DEFAULT: off
+
+my $USE_NET_DNS     = 0;    # Force the usage of Net::DNS for RBL lookups.
+                            # Normally policyd-weight tries to use a faster
+                            # RBL lookup routine instead of Net::DNS
+
+
+my $NS              = '';   # A list of space separated NS IPs
+                            # This overrides resolv.conf settings
+                            # Example: $NS = '1.2.3.4 1.2.3.5';
+                            # DEFAULT: empty
+
+
+my $IPC_TIMEOUT     = 2;    # timeout for receiving from cache instance
+
+my $TRY_BALANCE     = 0;    # If set to 1 policyd-weight closes connections
+                            # to smtpd clients in order to avoid too many
+                            # established connections to one policyd-weight
+                            # child
+
+# scores for checks, WARNING: they may manipulate eachother
+# or be factors for other scores.
+#                                       HIT score, MISS Score
+my @client_ip_eq_helo_score          = (1.5,       -1.25 );
+my @helo_score                       = (1.5,       -2    );
+my @helo_from_mx_eq_ip_score         = (1.5,       -3.1  );
+my @helo_numeric_score               = (2.5,        0    );
+my @from_match_regex_verified_helo   = (1,         -2    );
+my @from_match_regex_unverified_helo = (1.6,       -1.5  );
+my @from_match_regex_failed_helo     = (2.5,        0    );
+my @helo_seems_dialup                = (1.5,        0    );
+my @failed_helo_seems_dialup         = (2,          0    );
+my @helo_ip_in_client_subnet         = (0,         -1.2  );
+my @helo_ip_in_cl16_subnet           = (0,         -0.41 );
+my @client_seems_dialup_score        = (3.75,       0    );
+my @from_multiparted                 = (1.09,       0    );
+my @from_anon                        = (1.17,       0    );
+my @bogus_mx_score                   = (2.1,        0    );
+my @random_sender_score              = (0.25,       0    );
+my @rhsbl_penalty_score              = (3.1,        0    );
+my @enforce_dyndns_score             = (3,          0    );
+
+
+my $VERBOSE = 0;
+
+my $ADD_X_HEADER        = 1;    # Switch on or off an additional 
+                                # X-policyd-weight: header
+                                # DEFAULT: on
+
+
+my $DEFAULT_RESPONSE    = 'DUNNO default'; # Fallback response in case
+                                           # the weighted check didn't
+                                           # return any response (should never
+                                           # appear).
+
+
+
+#
+# Syslogging options for verbose mode and for fatal errors.
+# NOTE: comment out the $syslog_socktype line if syslogging does not
+# work on your system.
+#
+
+my $syslog_socktype = 'unix';   # inet, unix, stream, console
+
+my $syslog_facility = "mail";
+my $syslog_options  = "pid";
+my $syslog_priority = "info";
+my $syslog_ident    = "postfix/policyd-weight";
+
+
+#
+# Process Options
+#
+my $USER            = "polw";      # User must be a username, no UID
+
+my $GROUP           = "";          # specify GROUP if necessary
+                                   # DEFAULT: empty, will be initialized as 
+                                   # $USER
+
+my $MAX_PROC        = 50;          # Upper limit if child processes
+my $MIN_PROC        = 3;           # keep that minimum processes alive
+
+my $TCP_PORT        = 12525;       # The TCP port on which policyd-weight 
+                                   # listens for policy requests from postfix
+
+my $BIND_ADDRESS    = '127.0.0.1'; # IP-Address on which policyd-weight will
+                                   # listen for requests.
+                                   # You may only list ONE IP here, if you want
+                                   # to listen on all IPs you need to say 'all'
+                                   # here. Default is '127.0.0.1'.
+                                   # You need to restart policyd-weight if you
+                                   # change this.
+
+my $SOMAXCONN       = 1024;        # Maximum of client connections 
+                                   # policyd-weight accepts
+                                   # Default: 1024
+                                   
+
+my $CHILDIDLE       = 240;         # how many seconds a child may be idle before
+                                   # it dies.
+
+my $PIDFILE         = "/var/run/policyd-weight.pid";
+
+#--END_CONFDEF
+
+
+$0 = "policyd-weight (master)";
+my %cache;
+my %poscache;
+my $my_PTIME;
+my $my_TEMP_PTIME;
+
+if(!($conf))
+{
+    if( -f "/etc/policyd-weight.conf")
+    {
+        $conf = "/etc/policyd-weight.conf";
+    }
+    elsif( -f "/etc/postfix/policyd-weight.cf")
+    {
+        $conf = "/etc/postfix/policyd-weight.cf";
+    }
+    elsif( -f "/usr/local/etc/policyd-weight.conf")
+    {
+        $conf = "/usr/local/etc/policyd-weight.conf";
+    }
+    elsif( -f "policyd-weight.conf")
+    {
+        $conf = "policyd-weight.conf";
+    }
+}
+
+my $conf_err;
+my $conf_str;
+our $old_mtime;
+if($conf ne "")
+{
+    if(sprintf("%04o",(stat($conf))[2]) !~ /(7|6|3|2)$/)
+    {
+        if(open(CONF, $conf))
+        {
+            read(CONF,$conf_str,-s CONF);
+            close(CONF);
+
+            #XXX taint $conf_str as $< enables taint mode
+            ($conf_str) = $conf_str =~ m/(.*)/s;
+
+            eval $conf_str;
+            if($@)
+            {
+                $conf_err = "syntax error in file $conf: ".$@;
+            }
+            else
+            {
+                $old_mtime = (stat($conf))[9];
+            }
+        }
+        else
+        {
+            $conf_err = "could not open $conf: $!";
+        }
+    }
+    else
+    {
+        $conf_err = "$conf is world-writeable!";
+    }
+}
+else
+{
+    $conf = "default settings"; # don't change! required by cache maintenance
+}
+
+
+our $STAYALIVE;
+
+# set group to user if no group has been defined
+$GROUP = $USER unless $GROUP;
+
+
+if($CMD_DEBUG == 1)
+{
+    $DEBUG = 1;
+    $conf_str =~ s/\#.*?(\n)/$1/gs;
+    $conf_str =~ s/\n+/\n/g;
+    print "config: $conf\n".$conf_str."\n"; 
+    $SPATH   .= ".debug";
+    
+    # chose /tmp for debug pidfiles only if user is not root
+    # if root would store debug pids also in /tmp we would be
+    # open to race attacks
+    if($< != 0)
+    {
+        $PIDFILE = "/tmp/policyd-weight.pid.debug";
+    }
+    else
+    {
+        $PIDFILE .= ".debug";
+    }
+
+    print "debug: using port ".++$TCP_PORT."\n";
+    print "debug: USER:  $USER\n";
+    print "debug: GROUP: $GROUP\n";
+    print "debug: issuing user:  ".getpwuid($<)."\n";
+    print "debug: issuing group: ".getpwuid($()."\n";
+}
+
+$conf_str = "";
+
+
+
+#
+# check for nasty symlinks
+#
+check_symlnk('master: init:',
+    $LOCKPATH, $PIDFILE, $SPATH, "$LOCKPATH/cache_lock");
+
+
+# send HUP to kids if $RELOAD
+if($RELOAD)
+{
+    local $SIG{HUP} = 'IGNORE';
+
+    open(PF, $PIDFILE) or die "Couldn't open $PIDFILE: $!";
+    my $pid = <PF>;
+    close(PF);
+
+    if(!($pid > 0)) { die "pid $pid seems to be wrong" };
+
+    print "sending ".-$sig_list{HUP}." to $pid\n";
+
+    kill (-$sig_list{HUP}, $pid) or die "err: $!";
+
+    exit;
+}
+
+
+# ----------------------------------------------------------
+#                initialization
+# ----------------------------------------------------------
+
+
+#
+# This process runs as a daemon, so it can't log to a terminal. Use
+# syslog so that people can actually see our messages.
+#
+if($CMD_DEBUG != 1)
+{
+    setlogsock($syslog_socktype) or die 
+        "setlogsock: $syslog_socktype: $!. If you are on Solaris you might want to set \$syslog_socktype = 'stream';";
+    openlog($syslog_ident, $syslog_options, $syslog_facility) or die "openlog: $!. If you are on Solaris you might want to set \$syslog_socktype = 'stream';";
+}
+
+if($KILL)
+{
+
+    if((-S $SPATH) && ($csock = IO::Socket::UNIX->new($SPATH)))
+    {
+        cache_query("kill");
+        $csock->close if ($csock && $csock->connected);
+        unlink $SPATH;
+    }
+    if(-S $SPATH)
+    {
+        mylog(warning=>"-k action but $SPATH still exists, deleting it");
+        print STDERR "warning: -k action but $SPATH still exists, deleting it\n";
+        unlink $SPATH or die $!;
+    }
+    exit unless $STOP or $DAEMONIZE;
+}
+
+if($STATS)
+{
+    print "*** querying cache for content stats:\n";
+    cache_query("stats");
+    exit;
+}
+
+
+if(!($run_action))
+{
+    # don't unlink PIDFILE if policyd-weight
+    # got called without arguments
+    $STAYALIVE = 1;
+}
+
+
+# re-arrange signal handlers
+$SIG{__DIE__} = sub {
+    die @_ if index($_[0], 'ETIMEOUT') == 0;
+    mylog(warning=>"err: init: @_");
+    unlink $PIDFILE unless $STAYALIVE;
+};
+$SIG{'TERM'}  = sub { unlink $PIDFILE unless $STAYALIVE;
+                      mylog(warning=>'Got SIGTERM. Daemon terminated.'); exit };
+
+$SIG{'QUIT'}  = sub { unlink $PIDFILE unless $STAYALIVE;
+                      mylog(warning=>"Got SIG@_. Daemon terminated."); exit };
+
+$SIG{'INT'}   = sub { unlink $PIDFILE unless $STAYALIVE;
+                      mylog(warning=>"Got SIG@_. Daemon terminated."); exit };
+
+$SIG{'PIPE'}  = sub { unlink $PIDFILE;
+                      mylog(warning=>"Got SIG@_. Daemon terminated."); exit };
+
+$SIG{'SYS'}   = sub { unlink $PIDFILE;
+                      mylog(warning=>"Got SIG@_. Daemon terminated."); exit }; 
+
+$SIG{'USR1'}  = sub { unlink $PIDFILE;
+                      mylog(warning=>"Got SIG@_. Daemon terminated."); exit };
+
+$SIG{'USR2'}  = sub { unlink $PIDFILE;
+                      mylog(warning=>"Got SIG@_. Daemon terminated."); exit };
+
+if($SIG{'POLL'}) {
+$SIG{'POLL'}   = sub { unlink $PIDFILE;
+                      mylog(warning=>"Got SIG@_. Daemon terminated."); exit };
+}
+
+if($SIG{'UNUSED'})
+{
+$SIG{'UNUSED'} = sub { unlink $PIDFILE;
+                      mylog(info=>"Got SIG@_. Daemon terminated."); exit };
+}
+
+
+#####
+## core dumpers
+
+$SIG{'SEGV'}  = sub { 
+                      $SIG{'ABRT'} = '';
+                      unlink $PIDFILE;
+                      mylog(warning=>"Got @_:".longmess().
+                        ". Daemon terminated.");
+                      CORE::dump(); exit };
+
+$SIG{'ILL'}   = sub { 
+                      $SIG{"ABRT"} = '';
+                      unlink $PIDFILE;
+                      mylog(warning=>"Got @_:".longmess().
+                        ". Daemon terminated."); 
+                      CORE::dump; exit };
+
+$SIG{'ABRT'}  = sub { unlink $PIDFILE;
+                      mylog(warning=>"Got @_:".longmess().
+                        ". Daemon terminated."); 
+                      CORE::dump; exit };
+
+$SIG{'FPE'}   = sub { 
+                      $SIG{"ABRT"} = '';
+                      unlink $PIDFILE;
+                      mylog(warning=>"Got @_:".longmess().
+                        ". Daemon terminated."); 
+                      CORE::dump; exit };
+
+$SIG{'BUS'}   = sub { 
+                      $SIG{"ABRT"} = '';
+                      unlink $PIDFILE;
+                      mylog(warning=>"Got @_:".longmess().
+                        ". Daemon terminated."); 
+                      CORE::dump; exit };
+
+$SIG{'HUP'}   = sub { conf_check('master'); };
+
+
+#
+# Log an error and abort.
+#
+sub fatal_exit {
+  mylog(warning => "fatal_exit: @_");
+  die "fatal: @_";
+}
+
+#
+# Unbuffer standard output.
+#
+select((select(STDOUT), $| = 1)[0]);
+
+
+if($VERBOSE == 1)
+{
+    mylog(debug=>"startup: using $conf");
+}
+
+my $RETANSW;
+
+if($ADD_X_HEADER == 1)
+{
+    $RETANSW = "PREPEND X-policyd-weight:";
+}
+else
+{
+    $RETANSW = "DUNNO ";
+}
+
+if($conf_err)
+{
+    mylog(warning=>"conf-err: ".$conf_err);
+    mylog(warning=>"conf-err: falling back to builtin defaults");
+    $RETANSW = $RETANSW." using builtin defaults due to config-error";
+}
+
+
+
+our $res=Net::DNS::Resolver->new;
+
+    $res->retrans($DNS_RETRY_IVAL) unless $DNS_RETRY_IVAL eq "";
+    $res->retry  ($DNS_RETRIES)    unless $DNS_RETRIES    eq "";
+    $res->debug  (1)               if     ($CMD_DEBUG == 1);
+
+if($NS && $NS =~ /\d/)
+{
+    my @ns = split(' ', $NS);
+    $res->nameservers(@ns);
+}
+
+
+# watch the version string, I'm afraid that they change to x.x.x notation
+if(Net::DNS->version() >= 0.50)
+{
+    $res->force_v4(1);  # force ipv4 usage, autodetection is broken till
+                        # Net::DNS 0.53
+}
+else
+{
+    $res->igntc(1);    # ignore truncated packets if Net-DNS version is
+                       # lower than 0.50
+}
+
+
+# keep udp socket open, don't waste time for socket creation.
+# works with Net::DNS 0.53
+$res->persistent_udp(1) if $PUDP == 1;
+
+our %RTYPES = ( 'A' => 1, 'TXT' => 16 ); # see RFC 1035
+our $s;
+
+if($res)
+{
+    my $ns = (($res->nameserver)[0]);
+    if(!($s = IO::Socket::INET->new( 
+                                PeerAddr => $ns,
+                                PeerPort => '53',
+                                Proto    => 'udp'
+                              )
+    ))
+    {
+        mylog(warning=>"could not open RBL Lookup Socket to $ns: $@ $!");
+        $USE_NET_DNS = 1;
+    }
+}
+
+# ----------------------------------------------------------
+#                 main
+# ----------------------------------------------------------
+
+#
+# Receive a bunch of attributes, evaluate the policy, send the result.
+#
+
+our $accepted     = "UNDEF";
+our $blocked      = "UNDEF";
+our $my_REJECTMSG = $REJECTMSG;
+our %bl_err;
+our $skip_rel;
+
+
+# cd to a coredump dir, if it exists 
+
+chdir "$LOCKPATH/cores/master";
+
+
+my %attr;
+if(!($DAEMONIZE))
+{
+    while (<STDIN>)
+    {
+        my $string = $_;
+        my $action = parse_input($string);
+        
+        if($action)
+        {
+            print STDOUT $action;
+            %attr = ();
+        }
+    }
+}
+else
+{
+
+##############################################################################
+#
+# DAEMON
+#
+##############################################################################
+    if($STOP && (!(-f $PIDFILE)))
+    {
+        print STDERR "No pidfile, expected it at $PIDFILE!\n";
+        exit 1;
+    }
+
+    if( -f $PIDFILE) 
+    {
+        open(PF, $PIDFILE) || die  $!;
+        my $oldpid = <PF>;
+        close(PF);
+
+        if($STOP && $oldpid)
+        {
+            if(my $ret = kill(-$sig_list{'TERM'}, $oldpid))
+            {
+                print "terminating ";
+                mylog(info=>"Daemon terminated.");
+            }
+            else
+            {
+                kill(-$sig_list{'KILL'}, $oldpid);
+                print "killed\n";
+                mylog(info=>"Abnormal exit. Daemon killed forcingly.");
+            }
+
+            unlink $PIDFILE;
+            if(!$RESTART)
+            {
+                print " ... done\n";
+                exit;
+            }
+        }
+
+        my $i;
+
+        while($oldpid && kill(0, $oldpid))
+        {
+            if(!$RESTART)
+            {
+                mylog(warning=>"Process already running");
+                print STDERR "Process already running\n";
+                exit -1;
+            }
+            autoflush STDOUT 1;
+            print ".";
+
+            if($i++ > 5)
+            {
+                $STAYALIVE = 1;
+                mylog(warning=>"Couldn't remove $PIDFILE, a process with pid $oldpid exists! Use \"restart\" to force.\n");
+                die "Couldn't remove $PIDFILE, a process with pid $oldpid exists! Use \"restart\" to force.\n";
+            }
+            sleep 1;
+        }
+        print " done\n";
+    }
+
+    create_lockpath("daemon");
+
+    if($BIND_ADDRESS && $BIND_ADDRESS !~ /^[ \t]*all[ \t]*$/i)
+    {
+        $tcp_socket = IO::Socket::INET->new(    Proto       => 'tcp',
+                                                LocalHost   => $BIND_ADDRESS,
+                                                LocalPort   => $TCP_PORT,
+                                                Listen      => $SOMAXCONN,
+                                                Reuse       => 1,
+                                                Blocking    => 0) or 
+                                        die "master: bind $TCP_PORT: $@ $!";
+    }
+    else
+    {
+        $tcp_socket = IO::Socket::INET->new(    Proto       => 'tcp',
+                                                LocalPort   => $TCP_PORT,
+                                                Listen      => $SOMAXCONN,
+                                                Reuse       => 1,
+                                                Blocking    => 0) or 
+                                        die "master: bind $TCP_PORT: $@ $!";
+    }
+
+    # XXX: do we really need that? I used it for a chance of closing
+    # sockets when spawning caches and the like. 
+    fcntl($tcp_socket, F_SETFD, FD_CLOEXEC); 
+
+    open(PF, ">".$PIDFILE) or die $!;
+
+# drop privileges
+    if(!($CMD_DEBUG))
+    {
+
+        my $uname  = getpwnam($USER)  or die "User $USER doesn't exist!";
+        my $gname  = getgrnam($GROUP) or die "Group $GROUP doesn't exist!";
+
+        my $runame = getpwuid($<)     or die $!;
+        my $rgname = getgrgid($()     or die $!;
+
+
+        # XXX: You'll get nightmares if you change stuff here! *voodoospell*
+        $! = '';
+        
+        # this first variant uses different approaches on plattforms.
+        # freebsd/linux uses setresgid + setgroups, other bsd, Mac OS X use 
+        # obviously setregid + setgroups
+        ($(,$)) = ($gname, "$gname $gname");
+        if($!)
+        {
+            
+            $! = '';
+            # last try. Implementation variant not clear on all plattforms
+            $( = $gname;
+                die "($<)($>): set GID to $gname: $!" if $!;
+            
+            $) = "$gname $gname";
+                die "($<)($>): set EGID to $gname: $!" if $!;
+        }
+        
+        ($<, $>) = ($uname, $uname);
+        if($!)
+        {
+            $! = '';
+
+            # this turns on taint mode, too. see man perlsec!
+            $< = $uname;
+                die "set UID to $uname: $!" if $!;
+
+            $> = $uname;
+                die "set EUID to $uname: $!" if $!;
+        }
+
+
+        # create directories for chdir in order
+        # to find core dumps an such
+        if(!(-d "$LOCKPATH/cores/"))
+        {
+            mkdir "$LOCKPATH/cores/" or die
+            "master: error while creating $LOCKPATH/cores/: $!";
+        }
+        if(!(-d "$LOCKPATH/cores/master"))
+        {
+            mkdir "$LOCKPATH/cores/master" or die
+            "master: error while creating $LOCKPATH/cores/master: $!";
+        }
+        if(!(-d "$LOCKPATH/cores/cache"))
+        {
+            mkdir "$LOCKPATH/cores/cache" or die
+            "master: error while creating $LOCKPATH/cores/cache: $!";
+        }
+
+
+        chdir "$LOCKPATH/cores/master" or 
+            die "master: chdir $LOCKPATH/cores/master: $!";
+
+        if(!($FOREGROUND))
+        {
+            defined(my $pid = fork)   or die "Can't fork: $!";
+            exit if $pid;
+
+# daemonized
+
+            setsid                    or die "Can't start a new session: $!";
+            open STDIN, '/dev/null'   or die "Can't read /dev/null: $!";
+            open STDERR, '>/dev/null' or die "Can't write to /dev/null: $!";
+            open STDOUT, '>/dev/null' or die "Can't write to /dev/null: $!"; 
+        }
+        
+        mylog(info=>"policyd-weight $VERSION started and daemonized. " .
+                    "conf:$conf; "                    . 
+                    "GID:$( EGID:$) UID:$< EUID:$>; " .
+                    "taint mode: " . ${^TAINT}
+              );
+    }
+
+    print PF $$ or die "err $!\n";
+    close PF    or die "err $!\n";
+
+    my %childs;     # maintenance hash for cleaning up children
+    my %avail;      # hash to know which client is available
+    my %pipes;      # hash to maintain pid -> pipe associations
+
+    cache_query("start"); # pre-launch cache
+    our $select_to;
+
+   
+    my $readable_handles = new IO::Select();
+    my $new_tcp_readable;
+
+    $tcp_socket->autoflush(1);
+    $readable_handles->add($tcp_socket);
+
+    my $waitedpid;
+    my $parentpid = $$;
+
+    sub REAPER {
+        my $waitedpid;
+        while($waitedpid = waitpid(-1, WNOHANG))
+        {
+            last if $waitedpid == -1;
+            mylog(info=>"master: child $waitedpid exited");
+            delete($childs{$waitedpid});
+            delete($avail{$waitedpid});
+            delete($pipes{$waitedpid});
+            if(!(keys(%avail) > 0))
+            {
+                $readable_handles->add($tcp_socket);
+            }
+        }
+        $SIG{CHLD} = \&REAPER;
+    }
+    $SIG{CHLD} = \&REAPER;
+
+
+    $SIG{'TERM'}  = sub { 
+        foreach(keys(%childs))
+        {
+            kill($sig_list{TERM}, $_);
+        }
+        unlink $PIDFILE;
+        exit 0; 
+    };
+
+    use vars qw/$child/;
+    use vars qw/$parent/;
+    my $sigset;
+    my $old_sigset;
+
+    while(1)
+    {
+        # process SIGCHLD signals
+        if($old_sigset)
+        {
+            unless (defined sigprocmask(SIG_UNBLOCK, $old_sigset)) 
+            {
+                mylog(warning=>"master: Could not unblock SIGCHLD");
+            }
+        }
+
+        # wait for data on all sockets
+        ($new_tcp_readable) =
+            IO::Select->select($readable_handles, undef, undef, undef);
+        
+        # block SIGCHLD signals, avoid raceconditions and coredumps
+        $sigset     = POSIX::SigSet->new(SIGCHLD);
+        $old_sigset = POSIX::SigSet->new;
+
+        unless (defined sigprocmask(SIG_BLOCK, $sigset, $old_sigset))
+        {
+            mylog(warning=>"master: Could not block SIGCHLD");
+        }
+
+        my $max_proc_msg;        
+
+        # process socket data
+        foreach my $sock (@$new_tcp_readable)
+        {
+            if($sock == $tcp_socket)
+            {
+                # let children handle it if they are available
+                if (keys(%avail) > 0)
+                {
+                    $readable_handles->remove($tcp_socket);
+                    next;
+                }
+
+                # don't spawn new children if MAX_PROC reached
+                if(keys %childs >= $MAX_PROC)
+                { 
+                    if( (!($max_proc_msg)) )
+                    {
+                        mylog(warning=>"master: MAX_PROC ($MAX_PROC) reached");
+                    }
+
+                    $max_proc_msg = 1;
+                    $readable_handles->remove($tcp_socket);
+                    next; 
+                }
+
+                # open a socketpair for control communication with the
+                # soon to be spawned child
+                ($child, $parent) = 
+                    IO::Socket->socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC) or
+                    mylog(warning=>"master: socketpair: $@ $!");
+                 
+                $child->autoflush(1);
+                $parent->autoflush(1);
+
+                # check for configuration changes before we spawn a new child
+                conf_check("master");
+
+                # attempt to fork a new child
+                defined(my $pid = fork) or die "cannot fork: $!";
+
+                # parent stuff
+                if ($pid)
+                {
+                    $pipes{$pid} = $child;
+                    $readable_handles->add($pipes{$pid});
+                    $readable_handles->add($tcp_socket);
+                    $parent->close;
+                    $childs{$pid} = 1;
+                    $avail{$pid}  = 1;
+                    next;
+                }
+
+##############################################################################
+#
+# DAEMON CHILDREN
+#
+##############################################################################
+                $0 = "policyd-weight (child)";
+
+                $SIG{'TERM'} = sub {
+                    eval
+                    {
+                        local $SIG{ALRM} = sub { die "ETIMEOUT" };
+                        alarm $IPC_TIMEOUT;
+                        print $parent ("$$ 0\n");
+                        $parent->recv(my $ans, 1024);
+                        alarm 0;
+                    };
+                    exit;
+                };
+                our $die_r;
+                $SIG{__DIE__} = sub {
+                    die @_ if index($_[0], 'ETIMEOUT') == 0;
+                    die @_ if @_ eq $die_r;
+                    $die_r = @_;
+                    mylog(warning=>"child: err: @_" );
+                    eval
+                    {
+                        local $SIG{ALRM} = sub { die "ETIMEOUT" };
+                        alarm $IPC_TIMEOUT;
+                        print $parent ("$$ 0\n");
+                        $parent->recv(my $ans, 1024);
+                        alarm 0;
+                    };
+                };
+                $SIG{INT} = sub {
+                    eval
+                    {
+                        local $SIG{ALRM} = sub { die "ETIMEOUT" };
+                        alarm $IPC_TIMEOUT;
+                        print $parent ("$$ 0\n");
+                        $parent->recv(my $ans, 1024);
+                        alarm 0;
+                    };
+                    exit;
+                };
+                $SIG{'HUP'} = sub {
+                    conf_check('child');
+                };
+
+
+                $SIG{'PIPE'} = sub {
+                    mylog(warning=>"Got SIG@_. Child $$ terminated.");
+                    die;
+                };
+                $SIG{'SYS'}   = sub {
+                    mylog(warning=>"Got SIG@_. Child $$ terminated.");
+                    die 
+                };
+                $SIG{'USR1'}  = sub {
+                    mylog(warning=>"Got SIG@_. Child $$ terminated.");
+                    die 
+                };
+                $SIG{'USR2'}  = sub {
+                    mylog(warning=>"Got SIG@_. Child $$ terminated.");
+                    die
+                };
+                if($SIG{'POLL'}) {
+                    $SIG{'POLL'}   = sub {
+                      mylog(warning=>"Got SIG@_. Child $$ terminated."); die
+                    };
+                }
+                if($SIG{'UNUSED'}) {
+                    $SIG{'UNUSED'} = sub {
+                      mylog(info=>"Got SIG@_. Child $$ terminated."); die
+                    };
+                }
+                
+
+                # core dumpers
+
+                $SIG{'SEGV'}  = sub {
+                    $SIG{'ABRT'} = '';
+                    delete($SIG{'ABRT'});
+                    mylog(warning=>"Got @_:".longmess().
+                        ". Child $$ terminated");
+                    die
+                };
+                $SIG{'ILL'}   = sub {
+                      $SIG{"ABRT"} = '';
+                      mylog(warning=>"Got @_:".longmess().
+                        ". Child $$ terminated.");
+                      die 
+                };
+                $SIG{'ABRT'}  = sub {
+                      mylog(warning=>"Got @_:".longmess().
+                        ". Child $$ terminated.");
+                      die 
+                };
+                $SIG{'FPE'}   = sub {
+                      $SIG{"ABRT"} = '';
+                      mylog(warning=>"Got @_:".longmess().
+                        ". Child $$ terminated.");
+                      die
+                };
+                $SIG{'BUS'}   = sub {
+                      $SIG{"ABRT"} = '';
+                      mylog(warning=>"Got @_:".longmess().
+                        ". Child $$ terminated.");
+                      die
+                };
+
+
+                mylog(info=>'child: spawned');
+
+                if($res)
+                {
+                    if($s && $s->connected)
+                    {
+                        $s->close; # don't use inherited DNS sockets
+                    }
+                    my $ns = (($res->nameserver)[0]);
+                    if(!($s = IO::Socket::INET->new( 
+                                     PeerAddr => $ns,
+                                     PeerPort => '53',
+                                     Proto    => 'udp'))
+                      )
+                    {
+                        mylog(warning=>
+                            "child: could not open RBL Lookup Socket to $ns: $@ $!");
+
+                        $USE_NET_DNS = 1;
+                    }
+                }
+
+                my $readable_handles = new IO::Select();
+                   $readable_handles->add($parent);
+                   $readable_handles->add($tcp_socket);        
+                close $child;
+
+                my $tout        = $CHILDIDLE;
+                my $maintenance = 0;
+                my $sig_set;
+                my $old_sigset;
+
+                while(1)
+                {
+                    if($maintenance >= $MAINTENANCE_LEVEL)
+                    {
+                        $maintenance = 0;
+                        conf_check("child");
+                    }
+
+                    if($old_sigset)
+                    {
+                        unless (defined sigprocmask(SIG_UNBLOCK, $old_sigset))
+                        {
+                            mylog(warning=>'child: Could not unblock SIGHUP');
+                        }
+                    }
+
+                    my $time_s          = time;
+                    ($new_tcp_readable) = 
+                     IO::Select->select($readable_handles, undef, undef, $tout);
+                    my $time_e          = time;
+
+                    # block SIGHUPs
+                    $sigset     = POSIX::SigSet->new(SIGCHLD);
+                    $old_sigset = POSIX::SigSet->new;
+
+                    unless (defined sigprocmask(SIG_BLOCK, $sigset, $old_sigset))
+                    {
+                        mylog(warning=>'child: Could not block SIGHUP');
+                    }
+
+                    
+                    $select_to = 1;
+                    my $ans;
+                    foreach my $sock (@$new_tcp_readable)
+                    {
+                        $select_to = 0;
+                        my $ans;            # define for the "for"-scope
+                        if($sock == $tcp_socket)
+                        {
+                            my $new_sock = $tcp_socket->accept();
+
+                            if(!($new_sock) || (!($new_sock->connected)))
+                            {
+                                $tout = $CHILDIDLE - ($time_e - $time_s);
+                                
+                                if( ($tout <= 0) || ($tout > $CHILDIDLE))
+                                {
+                                    $tout = $CHILDIDLE;
+                                }
+                                next;
+                            }
+                            else
+                            {
+                                print $parent ("$$ 0\n");
+                                $parent->recv($ans, 1024);
+                                $tout    = $CHILDIDLE;
+                                $new_sock->autoflush(1);
+
+                                # set nonblocking IO, required by linux
+                                # BSD did fine without
+                                fcntl($new_sock, F_SETFD, O_NONBLOCK) || die $!;
+
+                                $readable_handles->add($new_sock);
+                            }
+                            print $parent ("$$ 0\n");
+                            $parent->recv($ans, 1024);
+                        }
+                        else
+                        {
+                            print $parent ("$$ 0\n");
+                            $parent->recv($ans, 1024);
+                            my $action;
+                            my $buf;
+                            my $ans;
+                            my $busy;
+                            $sock->timeout(1);
+
+                            while(<$sock>)
+                            {
+                                $buf = $_;
+                                if($buf)
+                                {
+                                    $busy   = 1;
+                                    $action = parse_input($buf);
+                                }
+
+                                if($action eq 'EPARSE')
+                                {
+                                    $action = '';
+                                    $buf = '';
+                                    last;
+                                }
+                                if($action)
+                                {
+                                    $sock->send($action);
+                                    
+                                    if($TRY_BALANCE)
+                                    {
+                                        $readable_handles->remove($sock);
+                                        $sock->close();
+                                    }
+                                    
+                                    %attr = ();
+                                    print $parent ("$$ 1\n");
+                                    $parent->recv($ans, 1024);
+                                    ++$maintenance;
+                                    last;
+                                }
+                            }
+
+                            next if ($buf && (!($action)));
+
+                            if(!($buf))
+                            {
+                                $readable_handles->remove($sock);
+                                $sock->shutdown(2);
+                                close $sock;
+                                print $parent ("$$ 1\n");
+                                $parent->recv($ans, 1024);
+                            }
+                        }
+                    }
+                    if($select_to)
+                    {
+                        # child was idle too much, exit if no connection
+                        # to a smtp
+                        print $parent ("$$ 0\n");
+                        $parent->recv($ans, 1024);
+                        my $connected;
+
+                        for($readable_handles->handles)
+                        {
+                            next if $_ == $tcp_socket or $_ == $parent;
+                            $connected = 1;
+                        }
+
+                        if((!($connected)))
+                        {
+                            #ask dad if we can die
+                            print $parent ("$$ d\n");
+                            $parent->recv($ans, 1024);
+
+                            if(($ans) && ($ans eq "y\n"))
+                            {
+                                mylog(info=>"child: exiting: idle for $CHILDIDLE sec.");
+                                exit;
+                            }
+                        }
+
+                        $readable_handles->add($tcp_socket);
+                        print $parent ("$$ 1\n");
+                        $parent->recv($ans, 1024);
+                        $tout = $CHILDIDLE;
+                    }
+                 }
+            }
+
+#######################################################################
+#
+# PARENT again
+#
+            else
+            {
+                # piped control-communication with our children
+                my $buf = <$sock>;
+                if(!($buf)) 
+                { 
+                    $readable_handles->remove($sock); 
+                    $sock->close; 
+                    next
+                }
+                my ($cpid, $stat) = split(' ', $buf);
+                # a kid ask to go suicide
+                if($stat eq 'd')
+                {
+                    if(keys (%childs) > $MIN_PROC)
+                    {
+                        # tell kid to commit suicide
+                        print $sock ("y\n");
+                        delete $childs{$cpid};
+                        delete $avail{$cpid};
+                        $readable_handles->add($tcp_socket);
+                    }
+                    else
+                    {
+                        print $sock ("n\n");
+                    }
+                    next;
+                }
+
+                # a kid tells us whether it's busy or free
+                if($stat == 1)
+                {
+                    $avail{$cpid} = 1;
+                }
+                else
+                {
+                    delete $avail{$cpid};
+                }
+                if(keys(%avail) > 0)
+                {
+                    $readable_handles->remove($tcp_socket);
+                }
+                elsif(keys(%childs) < $MAX_PROC)
+                {
+                    $readable_handles->add($tcp_socket);
+                }
+                print $sock ("1\n");
+                next;
+            }
+        }
+    }
+}
+
+sub parse_input
+{
+    $_ = shift; 
+    $_ =~ tr/\r\n//d;
+
+    if (/=/) 
+    {
+        my ($k, $v) = split (/=/, lc($_), 2); 
+        $attr{$k}   = $v; 
+        return;
+    }
+    elsif (length)
+    {
+        mylog(warning=>sprintf("ignoring garbage: %.100s", $_));
+        return;
+
+    }
+    if ($VERBOSE == 1)
+    {
+        for (sort keys %attr)
+        {
+            mylog(debug=> "Attribute: $_=".$attr{$_});
+        }
+    }
+
+    if(!($DAEMONIZE))
+    {
+        fatal_exit ("unrecognized request type: '$attr{request}'")
+        unless 
+        $attr{request} eq 'smtpd_access_policy';
+    }
+    else
+    {
+        if(!($attr{request} eq 'smtpd_access_policy'))
+        {
+            mylog(warning=>"unrecognized request type: '$attr{request}'");
+            return('EPARSE');
+        }
+    }
+
+
+    my $response;
+    my $action;
+       $action   = $DEFAULT_RESPONSE;
+    
+    no strict 'refs';
+
+    my $delay_time = time;
+    $response = weighted_check->(attr=>\%attr);
+    
+    if ($response) 
+    {
+        $action = $response;
+    }
+    else
+    {
+        mylog(warning=>'weighted_check returned a zero value!');
+    }
+
+    # return only a restriction class if the user requested it with
+    # specifying a response message with "rc:foo"
+    if(index($action, 'rc:') != -1)
+    {
+        $action =~ s/^[ \t]*rc:[ \t]*(.*?)[,; .]+.*/$1/i;
+    }
+
+    my $trace_info;
+    if($DEBUG)
+    {
+        $trace_info = '<instance='.$attr{instance}.'> ';
+    }
+    $trace_info  .= '<client=' . $attr{client_address} . '> '  .
+                    '<helo='   . $attr{helo_name}      . '> '  .
+                    '<from='   . $attr{sender}         . '> '  .
+                    '<to='     . $attr{recipient}      . '>'   ;
+
+
+    mylog(info=>"decided action=$action; $trace_info; delay: ".
+                (time - $delay_time).'s');
+    return("action=$action\n\n");
+}
+
+
+sub address_stripped 
+{
+    # my $foo = localpart_lhs('foo+bar@baz.com'); # returns 'foo@baz.com'
+    my $string = shift;
+    
+    for ($string) 
+    {
+        s/[+-].*\@/\@/;
+    }
+    return $string;
+}
+
+
+
+
+###############################################################################
+###############################################################################
+## subroutines ################################################################
+
+
+#------------------------------------------------------------------------------
+#        Plugin: weighted_check
+#------------------------------------------------------------------------------
+sub weighted_check
+{
+    local %_        = @_;
+    my %attr        = %{ $_{attr} };
+    my $ip          = $attr{client_address};
+    $ip = Net::IP::ip_expand_address($ip,6) if Net::IP::ip_is_ipv6($ip);
+    my $cl_hostname = $attr{client_name};
+
+    my $cansw;
+
+    if(index($ip,":") != -1)
+    { 
+        #return ('DUNNO IPv6');               # we have no IPv6 support for now
+    }
+
+    my $client_name = $attr{client_name}              || '';
+    my $helo        = $attr{helo_name}                || '';
+    my $from        = address_stripped($attr{sender}) || '';
+    my $rcpt        = $attr{recipient}                || '';
+
+    my $instance    = $attr{instance} . $ip . $from;
+
+
+    my $trace_info;
+    if($DEBUG)
+    {
+        $trace_info = '<instance='.$instance.'> ';
+    }
+    $trace_info  .= "<client=$ip> <helo=$helo> <from=$from> <to=$rcpt>";
+
+
+
+    my $from_domain;
+    if($attr{sender} =~ /.*@(.*)/)
+    {
+        $from_domain = $1;
+    }
+    if($from eq '')
+    {
+        return('DUNNO NULL (<>) Sender');
+    }
+    my $orig_from   = $from;
+
+    if($attr{recipient} && $attr{recipient} =~ /^(postmaster|abuse)\@/)
+    {
+        return('DUNNO mail for '.$attr{recipient});
+    }
+
+    if(($instance) && ($instance eq $accepted))
+    {
+        return ('DUNNO multirecipient-mail - already accepted by previous query');
+    }
+    elsif(($instance) && ($instance eq $blocked))
+    {
+        return ($my_REJECTMSG.' (multirecipient mail)' );
+    }
+
+## cache check
+    if( ($CACHESIZE > 0) || ($POSCACHESIZE > 0) )
+    {
+        $cansw = cache_query('ask', $ip, '0', $orig_from, $from_domain);
+    }
+
+
+    if($cansw && index($cansw, 'rate') != 0)
+    {
+        $blocked      = $instance;
+        $my_REJECTMSG = $cansw;
+
+        return($my_REJECTMSG);
+    }
+    elsif($cansw && index($cansw, 'rate:hard:') == 0)
+    {
+        $accepted = $instance;
+        return("$RETANSW $POSCACHEMSG; $cansw");
+    }
+
+## startup checks and preparing ###############################################
+
+    my ($revip, $subip16, $subip);
+    if (Net::IP::ip_is_ipv4($ip)) 
+    {
+       my ($ipp1, $ipp2, $ipp3, $ipp4) = split(/\./, $ip);
+       $revip       = $ipp4.'.'.$ipp3.'.'.$ipp2.'.'.$ipp1;
+       $subip16     = $ipp1.'.'.$ipp2.'.';
+       $subip       = $subip16.$ipp3.'.';
+    }
+    else {
+       $ip          = Net::IP::ip_expand_address($ip,6);
+       $revip       = Net::IP::ip_reverse($ip);
+       $revip       =~s/\.ip6.arpa\.$//;
+       $subip16     = substr($ip,0,15);
+       $subip       = substr($ip,0,20);
+    }
+   
+
+    my $rate                    = 0;
+    my $total_dnsbl_score;               # this var holds only positive scores!
+    my $helo_ok                 = 0;
+    my $mx_ok                   = 0;
+    my $helo_untrusted_ok       = 0;
+    my $RET                     = '';
+    my $dont_cache              = 0;
+    my $do_client_from_check    = 0;
+    my $client_seems_dialup     = 0;
+    my $in_dyn_bl               = 0;
+    my $helo_seems_dialup       = 0;
+    my $rhsbl_penalty           = 0;
+    my $bogus_mx_penalty        = 0;
+    my $maxdnserr               = $MAXDNSERR;
+
+    my $RELAYMSG                = '';
+
+    my $found;
+    
+    my $rtime                   = time; # timestamp of policy request
+
+## DNSBL check ################################################################
+    my $i;
+    my $dnsbl_hits = 0;
+    
+    $skip_rel  = $BL_SKIP_RELEASE + $BL_ERROR_SKIP;
+
+    for($i=0;$i < @dnsbl_score; $i += 4)
+    {
+        $found = 0;
+        my $answ = 0;
+        
+        if( (!($bl_err{$dnsbl_score[$i]}))                || 
+            $bl_err{$dnsbl_score[$i]} <= $BL_ERROR_SKIP 
+          )
+        {
+            $answ = rbl_lookup($revip.'.'.$dnsbl_score[$i]);
+        }
+        else
+        {
+            $RET .= ' '.$dnsbl_score[$i+3].'=SKIP('.$dnsbl_score[$i+2].')';
+            $rate += $dnsbl_score[$i+2];
+
+            if(++$bl_err{$dnsbl_score[$i]} >= $skip_rel)
+            {
+                $bl_err{$dnsbl_score[$i]} = 0;
+            }
+            next;
+        }
+
+        if(!($answ))
+        {
+            # increase err counter for that rbl
+            ++$bl_err{$dnsbl_score[$i]};
+
+            if($maxdnserr-- <= 1)
+            {
+                $accepted = $instance;
+                return "$RETANSW $MAXDNSERRMSG in ".$dnsbl_score[$i].' lookups';
+            }
+            $RET .= ' '.$dnsbl_score[$i+3].'=ERR('.$dnsbl_score[$i+2].')';
+            $rate += $dnsbl_score[$i+2];
+            
+            next;
+        }
+
+        $bl_err{$dnsbl_score[$i]} = 0;
+        if($answ > 0)
+        {
+            $RET               .= ' IN_'.$dnsbl_score[$i+3].'=' .
+                                         $dnsbl_score[$i+1];
+            $found              = 1;
+            $rate              += $dnsbl_score[$i+1];
+            $total_dnsbl_score += $dnsbl_score[$i+1];
+
+            if(index(lc($dnsbl_score[$i+3]), 'dyn') != -1)
+            {
+                $client_seems_dialup = 1;
+                $in_dyn_bl = 1;
+            }
+        }
+
+        if($found == 0)
+        {
+            if($LOG_BAD_RBL_ONLY == 1)
+            {
+                if($dnsbl_score[$i+2] != 0) # if an RBL entry manipulates
+                                            # the overall score, log it though.
+                {
+                    $RET .= ' NOT_IN_'.$dnsbl_score[$i+3].'=' .
+                                       $dnsbl_score[$i+2];
+                }
+            }
+            else
+            {
+                $RET .= ' NOT_IN_'.$dnsbl_score[$i+3].'='.$dnsbl_score[$i+2];
+            }
+            $rate += $dnsbl_score[$i+2];
+        }
+        else
+        {
+            # increase DNSBL hitcounter only if the DNSBL is a RBL and no
+            # DNS whitelist
+            if($dnsbl_score[$i+1] > 0)
+            {
+                ++$dnsbl_hits;
+            }
+            else
+            {
+                next;
+            }
+
+
+            # check for DNSBL Hit/Score limit exceeding
+            if( 
+                ($dnsbl_hits      > $MAXDNSBLHITS ) ||
+                ($total_dnsbl_score > $MAXDNSBLSCORE)
+              )
+            {
+                if($CACHESIZE > 0 && $MAXDNSBLMSG !~ /^\s*(4|DEFER|rc\:)/i)
+                {
+                    cache_query('nadd', $ip, $total_dnsbl_score);
+                }
+                $blocked = $instance;
+                mylog(info=>"weighted check: $RET; $trace_info; rate: $rate");
+                return($MAXDNSBLMSG."; check http://www.robtex.com/rbl/$ip.html");
+            }
+        }
+    }
+
+    if($dnsbl_checks_only == 1)
+    {
+        return("$RETANSW $RET (only DNSBL check requested)");
+    }
+    my $re_count;
+    for(@dnsbl_checks_only_regexps)
+    {
+        my $re = $_;
+        $re_count++;
+        next if not $re;
+        if($cl_hostname && $cl_hostname =~ /$re/)
+        {
+            return("$RETANSW $RET (only DNSBL check requested (regex-nr: $re_count))");
+        }
+    }
+
+
+## postive cache check
+    if($cansw && ($POSCACHESIZE > 0) && ($dnsbl_hits < 1))
+    {
+            $accepted = $instance;
+            return("$RETANSW $POSCACHEMSG; $cansw");
+    }
+
+
+## HELO/FROM DNS checks #######################################################
+    $found = 0;
+    my $is_mx            = 0;
+    my $ip_eq_from       = 0;
+    my $addresses        = '';
+    my $mx_names         = '';
+    my $recs_found       = 0;
+    my $MATCH_TYPE;
+    my $from_addresses   = '';
+
+    my $dnserr           = 0;
+    my $bogus_mx         = 0;
+    my $bad_mx           = 0;
+    my $bad_mx_scored    = 0;
+    my $do_reverse_check = 0;
+
+    my $squared_helo = squared_helo(\$helo, \$ip);
+    if($squared_helo == 1) { $helo_ok = 1; }
+
+    my $tmp_domain = $from_domain;
+       $tmp_domain =~ s/[\[\]]//g;
+       $tmp_domain = '['.$from_domain.']';
+    my $tmpip = squared_helo(\$tmp_domain, \$ip);
+    if($tmpip == 1)
+    {
+        $from_addresses .= " $from_domain";
+        $found = 1; $helo_ok = 1;
+    }
+    $addresses .= " $helo";
+
+    my @helo_parts = split(/\./,$helo);
+
+    $from =~ /.*@(.*)/;
+    my $tmp_from = $1;
+
+    my @parts_check = ($tmp_from, $helo);    # don't change order
+
+    for(my $tmpcnt=0; $tmpcnt < @parts_check; $tmpcnt++)
+    {
+        if($tmpcnt == 1)
+        { 
+            $MATCH_TYPE = 'HELO'; 
+        } 
+        else 
+        { 
+            $MATCH_TYPE = 'FROM';
+        }
+
+        my @parts = split(/\./,$parts_check[$tmpcnt]);
+
+        for(;@parts >=2;shift(@parts))
+        {
+            my $testhelo = join('.',@parts);
+            next if $testhelo =~ /\[|\]/;
+            my $query    = $res->send($testhelo, 'MX');
+
+            if(dns_error(\$query, \$res))
+            {
+                if($maxdnserr-- <= 1)
+                {
+                    $accepted = $instance;
+                    return("$RETANSW $MAXDNSERRMSG in $MATCH_TYPE MX lookups for $testhelo");
+                }
+                next;
+
+            }
+
+            # removed "if($query && $query->answer)" (which was introduced in
+            # 0.1.14.4 due to dns_error() implementation) in 0.1.14.5 because
+            # A lookups were not performed if MX returned NXDOMAIN
+            # XXX: this is to be reviewed and sanitized
+            if($query)
+            {
+                $recs_found = 1; # means, we've got some dns response
+
+                foreach my $rr ($query->answer)
+                {
+                    if($rr->type eq 'MX')
+                    {
+                        for my $query_type ('A','AAAA') {
+
+                        my $mxres  = $res->send($rr->exchange , $query_type);
+
+                        if(dns_error(\$mxres, \$res))
+                        {
+                            if($maxdnserr-- <= 1)
+                            {
+                                $accepted = $instance;
+                                return("$RETANSW $MAXDNSERRMSG in $MATCH_TYPE MX -> A lookups");
+                            }
+                            next;
+                        }
+                        foreach my $mxvar ($mxres->answer)
+                        {
+                            next if ($mxvar->type ne 'A' && $mxvar->type ne 'AAAA');
+                           my $ip_address = $mxvar->address;
+                           $ip_address = Net::IP::ip_expand_address($mxvar->address,6) 
+                                       if Net::IP::ip_is_ipv6($mxvar->address);
+                            
+                            # store sender MX hostname entries for comparission 
+                            # with HELO argument
+                            if ($MATCH_TYPE eq 'FROM')
+                            {
+                                $mx_names .= '.'.$rr->exchange . " ";
+                            }
+                            
+                            if($tmpcnt == 0)
+                            {
+                                $from_addresses .= ' '.$ip_address;
+                            }
+
+                            $addresses .= ' '.$ip_address;
+
+                            if ($ip eq $ip_address)
+                            {
+                                $RET    .= ' CL_IP_EQ_'.$MATCH_TYPE.'_MX=' .
+                                           $helo_from_mx_eq_ip_score[1];
+
+                                $found   = 1;
+                                $is_mx   = 1 if $MATCH_TYPE eq 'FROM';
+                                $helo_ok = 1;
+                                $mx_ok   = 1;
+                                $rate   += $helo_from_mx_eq_ip_score[1];
+                                last;
+                            }
+                           undef $ip_address;
+                        }
+
+                       }  #Ipv4/IPv6
+                    }
+                    last if $found;
+                }
+
+                # penalize dnsbl-weighted for empty/bogus MX records
+                # XXX: probably need to separate hostnames from domainnames
+                if( $MATCH_TYPE eq 'FROM'    &&
+                    (!($bad_mx))             && 
+                    (
+                     $from_addresses !~ /\d+/ ||
+                     $from_addresses =~ 
+                 /( 127\.| 192\.168\.| 10\.| 172\.(?:1[6-9]|2\d|3[01])\.)/
+                    )
+                  )
+                {
+                    $bad_mx = 1;
+                }
+
+
+                if(!($found))
+                {
+                    
+                   for my $query_type ('A','AAAA') {
+
+                    my $query = $res->send($testhelo,$query_type);  
+                    if(dns_error(\$query, \$res))
+                    {
+                        if($maxdnserr-- <= 1)
+                        {
+                            $accepted = $instance;
+                            return("$RETANSW $MAXDNSERRMSG in $MATCH_TYPE A lookup for $testhelo");
+                        }
+                        next;
+                    }
+                    foreach my $addr ($query->answer)
+                    {
+                        if($addr->type eq 'PTR')
+                        {
+                            if($helo == $ip)
+                            {
+                                $RET              .= ' CL_IP_EQ_HELO_NUMERIC='.
+                                                     $helo_score[1];
+
+                                $rate             += $helo_score[1];
+                                $found             = 1;
+                                $helo_untrusted_ok = 1;
+                            }
+                        }
+                        if(($addr->type ne 'A' && $addr->type ne 'AAAA')){ next; }
+                       my $ip_address = $addr->address;
+                       $ip_address= Net::IP::ip_expand_address($addr->address,6) if Net::IP::ip_is_ipv6($addr->address);
+                        if($tmpcnt == 0)
+                        {
+                            $from_addresses .= ' '.$ip_address;
+                        }
+
+                        $addresses .= ' '.$ip_address;
+                        if ($ip eq $ip_address)
+                        {
+                            $found    = 1;
+                            $helo_ok  = 1;
+                            $RET     .= ' CL_IP_EQ_'.$MATCH_TYPE.'_IP=' .
+                                        $helo_score[1];
+                            
+                            $rate    += $helo_score[1];
+                            $bad_mx   = 0;
+                            if($tmpcnt == 0)
+                            {
+                                $ip_eq_from = 1;
+                            }
+                            last;
+                        }
+                       undef $ip_address;
+                    }
+                   } #IPv4/IPv6
+                }
+
+                if($bad_mx && (!($bad_mx_scored)))
+                {
+                    my $score = $bogus_mx_score[0] * $total_dnsbl_score;
+                    if($score)
+                    {
+                        $RET             .= ' BAD_MX='.$score;
+                        $rate            += $score;
+                        $bad_mx_scored    = 1;
+                    }
+
+                }
+
+                # check if sender domain has bogus or empty
+                # A/MX records.
+                if( ($MATCH_TYPE eq 'FROM')   &&
+                    (!($bogus_mx))            &&
+                    (
+                     $from_addresses !~ /\d+/ || 
+                     $from_addresses =~
+                 /( 127\.| 192\.168\.| 10\.| 172\.(?:1[6-9]|2\d|3[01])\.)/
+                    )
+                  )
+                {
+                    my $score = $bogus_mx_score[0] + $total_dnsbl_score;
+                    $RET             .= ' BOGUS_MX='.$score;
+                    $rate            += $score;
+                    $bogus_mx         = 1;
+                    $bogus_mx_penalty = $score;
+                }
+
+                
+                last if $found;
+            }
+            last if $found;
+        }
+        last if $found;
+    }
+    if((!($found)) && $recs_found) # helo seems forged
+    {
+        if(index($addresses,' '.$subip) != -1)
+        {
+            $RET     .= ' HELO_IP_IN_CL_SUBNET='.$helo_ip_in_client_subnet[1];
+            $rate    += $helo_ip_in_client_subnet[1];
+            $helo_ok  = 1;
+            $found    = 1;
+        }
+        elsif(index($addresses,' '.$subip16) != -1)
+        {
+            $RET               .= ' HELO_IP_IN_CL16_SUBNET=' .
+                                  $helo_ip_in_cl16_subnet[1];
+
+            $rate              += $helo_ip_in_cl16_subnet[1];
+            $helo_untrusted_ok  = 1;
+            $do_reverse_check   = 1;
+            $found              = 1;
+        }
+        if($found != 1 && $helo_ok != 1 && $squared_helo != 1)
+        {
+         my $score    = $helo_score[0] + $total_dnsbl_score;
+            $RET     .= ' CL_IP_NE_HELO='.$score;
+            $helo_ok  = 2;
+            $rate    += $score;
+        }
+    }
+    elsif($found != 1) # probably DNS error
+    {
+     my $score    = ($helo_score[0]-0.1);
+        $RET     .= ' NO_MX_A_RECS_FOUND='.$score;
+        $rate    += $score;
+        $helo_ok  = 2;
+    }
+
+
+## Reverse IP == dynhost check ###############################################
+
+    my $ip_res = $res->send("$ip");
+    my @reverse_ips;
+
+    if($ip_res && $ip_res->answer)
+    {
+        foreach my $tmprr ($ip_res->answer)
+        {
+            if($tmprr->type eq 'PTR')
+            {
+                my $tmpptr =  $tmprr->ptrdname;
+                   $tmpptr =~ s/\.$//;
+                push(@reverse_ips, lc($tmpptr));
+            }
+        }
+    }
+
+    if((!($client_seems_dialup)) && ($mx_ok != 1))
+    {
+        foreach my $revhost (@reverse_ips)
+        {
+            if( $revhost =~ /(mx|smtp|mail|dedicated|(\b|[^n])stat).*?\..*?\./i )
+            { last }
+
+            if (
+                $revhost =~ 
+          /(\.dip|cable|ppp|dial|dsl|dyn|client|rev.*?(ip|home)).*?\..*?\./i
+               ||
+               $helo    =~ 
+                  /[a-z\.\-\_]+\d{1,3}[-._]\d{1,3}[-._]\d{1,3}[-._]\d{1,3}/i
+               )
+            {
+                $client_seems_dialup = 1;
+                $total_dnsbl_score  += $client_seems_dialup_score[0];
+                $rate               += $client_seems_dialup_score[0];
+                $RET                .= ' CL_SEEMS_DIALUP=' .
+                                       $client_seems_dialup_score[0]; 
+                last;
+            }
+        }
+    }
+
+## Reverse IP == HELO check ###################################################
+    $found = 0;
+    my $rev_processed = 0;
+
+    if(($helo_ok != 1 && $helo_untrusted_ok != 1) || $do_reverse_check)
+    {
+        foreach my $revhost (@reverse_ips)
+        {
+            $rev_processed = 1;
+            $revhost       =~ s/\.*$//;
+
+            if ( $revhost eq $helo )
+            {
+                $found = 1;
+                $RET  .= ' REV_IP_EQ_HELO='.$client_ip_eq_helo_score[1];
+                $rate += $client_ip_eq_helo_score[1];
+                last;
+            }
+
+            my $partsfound = 0;
+            my $tmprevhost = reverse($revhost);
+            my $tmphelo    = reverse($helo);
+               $tmphelo    =~ s/.*?\.([^.]+).*/$1/;
+
+            if( ($tmprevhost =~ /\.\Q$tmphelo\E$/i ) ||
+                ($tmprevhost =~ /\.\Q$tmphelo\E\./i)
+              )
+            {
+                $partsfound  = 1;
+            }
+
+            if( $partsfound != 1 )
+            {
+                my $tmphelo    = reverse($helo);
+                   $tmprevhost =~ s/.*?\.([^.]+).*/$1/;
+
+                if( ($tmphelo  =~ /\.\Q$tmprevhost\E$/i ) ||
+                    ($tmphelo  =~ /\.\Q$tmprevhost\E\./i)
+                  )
+                {
+                    $partsfound = 1;
+                }
+            }
+
+            if($partsfound == 1)
+            {
+                $found = 1;
+                $RET  .= ' REV_IP_EQ_HELO_DOMAIN='.$client_ip_eq_helo_score[1];
+                $rate += $client_ip_eq_helo_score[1];
+                last;
+            }
+        }
+
+        if($rev_processed != 1 && $recs_found != 1)
+        {
+            $RET   .= ' NO_DNS_RECORDS=0.5';
+            $rate  += 0.5;
+            $dnserr = 1;
+        }
+
+        if($found != 1 && $squared_helo != 1)
+        {
+            $RET  .= ' RESOLVED_IP_IS_NOT_HELO='.$client_ip_eq_helo_score[0];
+            $rate += $client_ip_eq_helo_score[0];
+        }
+        else
+        {
+            if( ! ($cl_hostname && $cl_hostname ne "unknown") )
+            {
+                $helo_untrusted_ok = 1;
+            }
+            else
+            {
+                $helo_untrusted_ok = 0;
+            }
+        }
+    }
+
+## HELO numeric check #########################################################
+    my $glob_numeric_score;
+    # check /1.2.3.4/ and /[1.2.3.4]/
+    if($helo =~ /^[\d|\[][\d\.]+[\d|\]]$/)
+    {
+        $glob_numeric_score = myrnd (
+            $helo_numeric_score[0] + 
+            ($helo_numeric_score[0] * $total_dnsbl_score)
+        );
+        $RET  .= ' HELO_NUMERIC='.$glob_numeric_score;
+        $rate += $glob_numeric_score;
+    }
+
+
+## HELO dialup check ##########################################################
+
+    my $DYN_DNS_MSG = '';
+    if(
+        (   ($enforce_dyndns_score[0] != 0)  || 
+            ($client_seems_dialup     != 1)
+        ) 
+        &&
+        (!($mx_ok)) && (!($ip_eq_from))
+        &&
+        $helo !~ /(mx|smtp|mail|dedicated|(\b|[^n])stat).*?\..*?\./i
+        &&
+        (
+            (
+                $helo =~ 
+           /(\.dip|cable|ppp|dial|dsl|dyn|client|rev.*?(ip|home)).*?\..*?\./i
+            ) ||
+            (
+                $helo =~ /[a-z\.\-\_]+\d{1,3}[-._]\d{1,3}[-._]\d{1,3}[-._]\d{1,3}/i
+                     # that's an ugly regex! watch this!
+            )
+        )
+      )
+    {
+        $helo_seems_dialup = 1;
+
+        $DYN_DNS_MSG = "; Please use DynDNS";
+
+        if($helo_ok == 1)
+        {
+         my $score   = $helo_seems_dialup[0] + $enforce_dyndns_score[0];
+            $RET    .= ' HELO_SEEMS_DIALUP='.$score;
+            $rate   += $score;
+
+        }
+        else
+        {
+         my $score   = $failed_helo_seems_dialup[0] + $enforce_dyndns_score[0];
+            $RET    .= ' NOK_HELO_SEEMS_DIALUP='.$score;
+            $rate   += $score;
+        }
+    }
+
+
+## From has nobody/anonymous user #############################################
+    my $anon= 0;
+
+    if($orig_from =~ /(nobody|anonymous)\@/)
+    {
+     my $score              = $from_anon[0] + $total_dnsbl_score + 
+                              $glob_numeric_score;
+        $RET               .= ' FROM_NBDY_ANON='.$score;
+
+        $rate              += $score;
+        $anon               = 1;
+    }
+
+
+## FROM Domain vs HELO regex check ############################################
+    if(!($is_mx))
+    {
+        $from       =~ s/.*@//;                 # delete localpart
+        my $tmphelo = $helo;
+        my $tmp_helo_domain;    
+
+
+        # handle sender "(host.)sub.domain.co.uk"
+        # keep:  "domain"
+        if   ($from =~ s/\.[a-z]{2}\.[a-z]{2}$//i)              
+        { $from =~ s/.*\.// }
+        
+        # handle sender "(host.)sub.domain1.com.br"
+        # keep:  "domain"
+        elsif($from =~ s/\.(com|org|net)\.[a-z]{2}$//i) 
+        { $from =~ s/.*\.// }
+        
+        # handle sender "(host.)sub.domain.com"
+        # handle "(host.)sub.domain.de"
+        # keep:  "domain"
+        elsif($from =~ s/\.[a-z]{2,5}$//i) 
+        { $from =~ s/.*\.// }
+
+        # handle helo "(host.)sub.domain.co.uk"
+        if   ($tmphelo =~ s/\.[a-z]{2}\.[a-z]{2}$//i) 
+        { }
+
+        # handle helo "(host.)sub.domain1.com.br"
+        elsif($tmphelo =~ s/\.(com|org|net)\.[a-z]{2}$//i)
+        { }
+
+        # handle helo "(host.)sub.domain.com"
+        # handle helo "(host.)sub.domain.de"
+        # keep:  "domain"
+        elsif($tmphelo =~ s/\.[a-z]{2,5}$//i)
+        { }
+
+        # get helo domain for checking against sender MX entries
+        $tmp_helo_domain  =  $tmphelo;
+        $tmp_helo_domain  =~ s/.*\.//; 
+
+        # set "." (dot) delimiter for comparisions
+        $from            = '.' . $from            .'.';
+        $tmphelo         = '.' . $tmphelo         .'.'; 
+        $tmp_helo_domain = '.' . $tmp_helo_domain .'.';
+        
+        $RET .= ' (check from: '   . $from 
+             .  ' - helo: '        . $tmphelo 
+             .  ' - helo-domain: ' . $tmp_helo_domain .') ';
+
+        # check trusted helos
+        if($helo_ok == 1)
+        {
+            if(
+                (index($tmphelo,$from)             != -1)  ||
+                (index($from,$tmphelo)             != -1)  ||
+                (index($mx_names,$tmp_helo_domain) != -1)  ||
+                (index($from_addresses,$ip)        != -1)
+              )
+            {
+                $RET  .= ' FROM/MX_MATCHES_HELO(DOMAIN)=' .
+                         $from_match_regex_verified_helo[1];
+
+                $rate += $from_match_regex_verified_helo[1];
+            }
+            else
+            {
+             my $score = myrnd( 
+                            (   $from_match_regex_verified_helo[0] 
+                              + ($total_dnsbl_score/4)
+                              + ($bogus_mx_penalty * $bogus_mx_penalty)
+                              + $glob_numeric_score
+                            )
+                         );
+                $RET  .= ' FROM/MX_MATCHES_NOT_HELO(DOMAIN)='.$score;
+                $rate += $score;
+                $do_client_from_check = 1;
+            }
+        }
+        elsif($helo_untrusted_ok == 1 && $squared_helo != 1)
+        {   
+            # check untrusted helos
+            if( (index($tmphelo,$from)             != -1)  ||
+                (index($from,$tmphelo)             != -1)  ||
+                (index($mx_names,$tmp_helo_domain) != -1)
+              )
+            {
+                $RET  .= ' FROM/MX_MATCHES_UNVR_HELO(DOMAIN)=' .
+                         $from_match_regex_unverified_helo[1];
+
+                $rate += $from_match_regex_unverified_helo[1];
+            }
+            else
+            {
+             my $score = (   $from_match_regex_unverified_helo[0] 
+                           + $total_dnsbl_score
+                           + ($bogus_mx_penalty * $bogus_mx_penalty)
+                         );
+
+                $RET  .= ' FROM/MX_MATCHES_NOT_UNVR_HELO(DOMAIN)='.$score;
+                $rate += $score;
+            
+                $do_client_from_check = 1;
+            }
+        }
+
+
+        # check totaly failed helos
+        elsif(index($tmphelo,$from) != -1 || index($from,$tmphelo) != -1)
+        {
+            $RET  .= ' MAIL_SEEMS_FORGED='.$from_match_regex_failed_helo[0];
+            $rate += $from_match_regex_failed_helo[0];
+        }
+
+        elsif(index($tmphelo,$from) == -1 || index($from,$tmphelo) == -1)
+        {
+         my $score = (  $from_match_regex_failed_helo[0] 
+                      + 0.5 
+                      + $total_dnsbl_score
+                     );
+            $RET  .= ' FROM_NOT_FAILED_HELO(DOMAIN)='.$score;
+            $rate += $score;
+        }
+    }
+
+## client == MX/A FROM domain #################################################
+    
+    if( 
+        ($mx_ok != 1)               &&
+        (
+            ($do_client_from_check) &&
+            ($dnsbl_hits > 0)
+        )                           &&
+        ( $squared_helo != 1)
+      )
+    {
+        if( index($from_addresses, $ip) == -1 )
+        {
+         my $score = $helo_from_mx_eq_ip_score[0] + $total_dnsbl_score;
+
+            $RELAYMSG = '; please relay via your ISP ('.$from_domain.')';
+
+            $RET     .= ' CLIENT_NOT_MX/A_FROM_DOMAIN='.$score;
+
+            $rate    += $score;
+
+            if( index($from_addresses, $subip) == -1 )
+            {
+                $RET  .= ' CLIENT/24_NOT_MX/A_FROM_DOMAIN='.$score;
+
+                $rate += $score;
+            }
+        }
+    }
+
+## From domain multiparted check ##############################################
+    if( 
+        (!($helo_ok || $mx_ok))        &&
+        ($rate < $REJECTLEVEL)         && 
+        ($orig_from =~ /\@.*?\..*?\./)
+      )
+    {
+     my $score = $from_multiparted[0] + $total_dnsbl_score;
+        $RET  .= ' FROM_MULTIPARTED='.$score;
+        $rate += $score;
+    }
+
+## Random sender check ########################################################
+    if( 
+        ($rate < $REJECTLEVEL) &&
+        (
+            ($orig_from =~ /[bcdfgjklmnpqrtvwxz]{5,}.*\@/i) ||
+            ($orig_from =~ /[aeiou]{4,}.*\@/i)
+        )
+      )
+    {
+     my $score = (   $total_dnsbl_score 
+                   + ($total_dnsbl_score * $random_sender_score[0]) 
+                   + $random_sender_score[0]
+                 );
+        $RET .=  ' RANDOM_SENDER=' . $score;
+        $rate += $score;
+        
+        $rhsbl_penalty = $rhsbl_penalty_score[0] * $random_sender_score[0];
+    }
+
+## rhsbl check ################################################################
+    my $in_rhsbl;
+    my $RHSBLMSG = '';
+
+    if($rate < $REJECTLEVEL)
+    {
+        $orig_from =~ /@(.*)/;
+        my $query  =  $1;
+
+        if(  ($do_client_from_check == 1) ||
+             ($helo_untrusted_ok    == 1) ||
+             ($bogus_mx             == 1)
+          )
+        { 
+            $rhsbl_penalty += $rhsbl_penalty_score[0]; 
+        }
+
+        for($i=0;$i < @rhsbl_score; $i += 4)
+        {
+            my $answer = rbl_lookup($query.'.'.$rhsbl_score[$i], 'A');
+
+            if(!($answer))
+            {
+                if($maxdnserr-- <= 1)
+                {
+                    $accepted = $instance;
+                    return ("$RETANSW $MAXDNSERRMSG in " . 
+                             $rhsbl_score[$i].' lookups');
+                }
+                next;
+            }
+            if($answer > 0)
+            {
+             my $score = myrnd( 
+                            ($rhsbl_score[$i+1] + $rhsbl_penalty ) +
+                            ($total_dnsbl_score/2)
+                         );
+                $RET      .= ' IN_'.$rhsbl_score[$i+3].'=' . $score;
+
+                $rate      = myrnd($rate + $score);
+
+                $RHSBLMSG .= '; in '.$rhsbl_score[$i];
+            }
+        }
+    }
+
+
+###############################################################################
+# parse and store results, do some cleanup, return results
+
+    # sanitize rate, perl gives inaccurate results in computings like
+    # -4.6 + 4.3
+    $rate = myrnd($rate);
+
+
+    if(($DEBUG) || ($CMD_DEBUG == 1))
+    {
+        $addresses =~ s/ $//;
+        $RET      .=  ' <helo_ips: '.$addresses.'>';
+    }
+
+    mylog(info=>"weighted check: $RET; $trace_info; rate: $rate");
+
+    if(($dnserr == 1) && ($dnsbl_hits < 2))         # applies if not too
+    {                                               # much dnsbl listed
+        my $my_DNSERRMSG = $DNSERRMSG . ' Your HELO: '.$helo.', IP: '.$ip;
+        return($my_DNSERRMSG);
+    }
+
+    if($rate >= $REJECTLEVEL)
+    {
+        $blocked = $instance;
+
+        $my_REJECTMSG = $REJECTMSG;  
+                                 
+        $dont_cache = 0;
+
+        if($rate < $DEFER_LEVEL)
+        {
+            my @defer_arr = split(' ', $DEFER_STRING);
+
+            foreach(@defer_arr)
+            {
+                if(index($RET, ' '.$_) != -1)
+                {
+                    $dont_cache   = 1;
+                    $my_REJECTMSG =~ s/^.*? /$DEFER_ACTION /;
+                    last;
+                }
+            }
+        }
+        if($my_REJECTMSG =~ /^(4|DEFER|rc\:)/i)
+        {
+            $dont_cache = 1;
+        }
+        if(($CACHESIZE > 0) && ($maxdnserr > 0) && (!($dont_cache)))
+        {
+            # add only the IP to SPAM cache if the client is dnsbl listed,
+            # a dynamic client or has no ok helo
+            # This should help in case of some dictionary attacks
+            if(($dnsbl_hits >= 1 || $client_seems_dialup || $helo_ok != 1))
+            {
+                cache_query('nadd', $ip, $rate);
+            }
+            else
+            { 
+                cache_query('nadd', $ip, $rate, $orig_from, $from_domain);
+            }
+        }
+
+        if(($helo_ok != 1) && ($helo_untrusted_ok != 1))
+        {
+            my $EREJECTMSG = $my_REJECTMSG .
+                             '; MTA helo: '.$helo.', MTA hostname: ' .
+                             $client_name.'['.$ip.'] (helo/hostname mismatch)';
+
+            return($EREJECTMSG.$RHSBLMSG.$RELAYMSG.$DYN_DNS_MSG);
+        }
+        return($my_REJECTMSG.$RHSBLMSG.$RELAYMSG.$DYN_DNS_MSG);
+    }
+    else
+    {
+        if(($POSCACHESIZE > 0) && ($dnsbl_hits < 1))
+        {
+            cache_query('padd', $ip, $rate, $orig_from, $from_domain);
+        }
+        $accepted = $instance;
+        return("$RETANSW $RET; rate: $rate");
+    }
+}
+
+
+
+
+#
+# cache_query (QUERY, IP, SENDER, [RATE], DOMAIN)
+#
+# Function for querying the cache daemon
+#
+# QUERY  : "nadd"  - negative (SPAM) add
+#        : "padd"  - positive (HAM)  add
+#        : "ask"  - is cached as SPAM or HAM?
+#        : "kill"  - terminated cache
+#        : "start" - pre-start cache
+# IP     : Client IP
+# SENDER : Sender Address
+# RATE   : store rate in "Xadd" queries
+# DOMAIN : Sender domain
+#
+# Returns: CACHEREJECTMSG when SPAM listed
+#        : "rate: $rate"  when HAM  listed
+#        : undef in all other cases
+sub cache_query
+{
+
+    my $query  = shift(@_) || '';
+    my $ip     = shift(@_) || '';
+    my $rate   = shift(@_) || '';
+    my $sender = shift(@_) || '';
+    my $domain = shift(@_) || '';
+
+    $! = '';
+    $@ = ();
+    if( (!($csock)) || ($csock && (!($csock->connected))) )
+    {
+        $csock = IO::Socket::UNIX->new($SPATH);
+        if( (!($csock = IO::Socket::UNIX->new($SPATH))) )
+        {
+            mylog(warning=>"cache_query: \$csock couln't be created: $@, calling spawn_cache()");
+            spawn_cache();
+            return(undef);
+        }
+        if( $query eq 'start')
+        {
+            $csock->close(); # dont inherit this socket;
+            return(undef);
+        }
+    }
+
+    if($csock && ($csock->connected))
+    {
+        my $buf;
+
+        my $alrm   = 0;
+        
+        $SIG{'ALRM'} = sub { 
+            # ignore alarms;
+            $alrm = 1; 
+        };
+
+        $csock->autoflush(1);
+        mylog(info=>"cache_query: $query $ip $rate $sender $domain") if $DEBUG;
+        print $csock "$CVERSION $query $ip $rate $sender $domain\n";
+        my $sline;
+        my $match = $query.$ip.$sender.' ';
+        
+        $csock->timeout($IPC_TIMEOUT);
+        
+        while($csock->connected)
+        {
+            eval
+            {
+                local $SIG{'ALRM'} = sub { 
+                    mylog(warning=>'cache_query: timeout');
+                    die "ETIMEOUT"; 
+                };
+                alarm $IPC_TIMEOUT;
+                $csock->recv($buf, 4069);
+                alarm 0;
+            };
+
+            if($@ || (!($buf))) { return(undef) };
+
+            if($STATS)
+            {
+                $buf =~ s/^.*?(blocked|pass)/$1/;
+                print $buf;
+                return(undef) if $buf =~ /\nEOF\n/;
+                next;
+            }
+            
+            if($buf !~ /\n$/)
+            {
+                $sline .= $buf;
+                next;
+            }
+            else
+            {
+                $sline .= $buf;
+            }
+
+            $sline =~ tr/\r\n//d;
+            
+            mylog(info=>"cache_query: \"$sline\" vs \"$match\"") if $DEBUG;
+
+            if(index($sline, 'unknown cache request') >= 0)
+            {
+                print $csock "kill\n";
+                close($csock);
+                $csock = "";
+                return(undef);
+            }
+            
+            # return a proper line in case we had query timeouts
+            # works like "next if not $sline =~ s/.*\Q$match\E//;" but faster
+            my $index = rindex($sline, $match);
+            next if $index < 0;
+            return(substr($sline, $index + length($match)));
+        }
+        return(undef); # just in case ...
+    }
+    else
+    {
+        mylog(info=>'could not connect to cache (maybe just starting up)');
+        return(undef);
+    }
+}
+
+
+
+#############################################################################
+#
+# CACHE PROCESS
+# 
+#############################################################################
+sub spawn_cache
+{
+    my $rname = getpwuid($<);
+
+    if(($rname ne $USER) && (!($CMD_DEBUG)))
+    {
+        mylog(warning=>"cache: running as wrong user: ".$rname."; please edit master.cf, set user=$USER and/or add $USER to your user and group accounts; cache not spawned.");
+        return(undef);
+    }
+
+    if(!( $< = getpwnam($USER)))
+    { 
+        mylog(warning=>"cache: couldn't change UID to user $USER: $!");
+        die $!;
+    }
+
+    if(!( $( = getpwnam($USER) ))
+    {
+        mylog(warning=>"cache: couldn't change GID to user $GROUP: $!");
+    }
+    create_lockpath('cache');
+    # avoid races at startups
+    mkdir $LOCKPATH.'/cache_lock' or return undef;
+
+    # check if a cache-socket file exist, and
+    # whether we can connect to it.
+    if( -S $SPATH)
+    {
+        my $test_sock = IO::Socket::UNIX->new($SPATH);
+        if ($test_sock && $test_sock->connected)
+        {
+            mylog(warning=>"cache-init: error: socket exists and is connectable");
+            return undef;
+        }
+        close($test_sock);
+    }
+
+
+    # no cache seems to exist, go create one
+    unlink $SPATH;
+    use POSIX qw(setsid);
+
+    defined(my $pid = fork) or die "cache: fork: $!";
+    if($pid)
+    {
+        return(undef);
+    }
+
+    setsid                  or die "cache: setsid: $!";
+
+
+    $SIG{__DIE__} = sub {
+        die @_ if index($_[0], 'ETIMEOUT') == 0;
+        mylog(warning=>"cache: err: @_");
+        
+        unlink $SPATH;
+        
+        rmdir $LOCKPATH.'/cache_lock';
+    };
+
+
+    # change directory to $LOCKPATH in order to get some
+    # coredumps just in case.
+    chdir "$LOCKPATH/cores/cache" or die
+        "cache: chdir $LOCKPATH/cores/cache: $!";
+
+
+    mylog(info=>'cache spawned'); 
+    $0 = 'policyd-weight (cache)';
+
+    if($CMD_DEBUG != 1)
+    {
+        close(STDIN);
+        close(STDOUT);
+        close(STDERR);
+        open (STDIN,  '/dev/null');
+        open (STDOUT, '>/dev/null');
+        open (STDERR, '>/dev/null');
+    }
+
+    $s = '' if $s;    # close socks, we don't need them anymore.
+    $res = '' if $res;
+    $sock->close if $sock;
+    $new_sock->close if $new_sock;
+    $tcp_socket->close if $tcp_socket;
+
+    $SIG{'TERM'} = sub {
+        unlink $SPATH;
+        rmdir $LOCKPATH.'/cache_lock';
+        mylog(info=>"cache: SIG@_, terminating");
+        exit 0;
+    };
+    $SIG{'QUIT'} = sub {
+        unlink $SPATH;
+        rmdir $LOCKPATH.'/cache_lock';
+        mylog(info=>"cache: SIG@_, terminating");
+        exit 0;
+    };
+
+
+    $SIG{'INT'}   = sub { unlink $SPATH;
+                      rmdir $LOCKPATH.'/cache_lock';
+                      mylog(warning=>"cache: SIG@_, terminating"); exit};
+
+
+    # commented because an interrupt of 'policyd-weight -s' may
+    # cause a SIGPIPE
+    # changed in: 0.1.14 beta-12
+    #$SIG{'PIPE'}  = sub { unlink $SPATH;
+    #                  rmdir $LOCKPATH.'/cache_lock';
+    #                  mylog(warning=>"cache: SIG@_, terminating"); exit};
+    
+    $SIG{'PIPE'} = 'IGNORE';
+
+
+
+    $SIG{'SYS'}   = sub { unlink $PIDFILE;
+                      rmdir $LOCKPATH.'/cache_lock';
+                      mylog(warning=>"cache: SIG@_, terminating"); exit}; 
+
+    $SIG{'USR1'}  = sub { unlink $PIDFILE;
+                      rmdir $LOCKPATH.'/cache_lock';
+                      mylog(warning=>"cache: SIG@_, terminating"); exit};
+
+    $SIG{'USR2'}  = sub { unlink $PIDFILE;
+                      rmdir $LOCKPATH.'/cache_lock';
+                      mylog(warning=>"cache: SIG@_, terminating"); exit};
+
+    if($SIG{'POLL'})
+    {
+        $SIG{'POLL'}   = sub { unlink $PIDFILE;
+                      rmdir $LOCKPATH.'/cache_lock';
+                      mylog(warning=>"cache: SIG@_, terminating"); exit};
+    }
+
+
+    if($SIG{'UNUSED'})
+    {
+        $SIG{'UNUSED'} = sub { unlink $PIDFILE;
+                      rmdir $LOCKPATH.'/cache_lock';
+                     mylog(warning=>"cache: SIG@_, terminating"); exit};
+    }
+
+
+# core dumpers
+    $SIG{'SEGV'}  = sub { 
+                      $SIG{"ABRT"} = '';
+    #                  cluck;
+                      unlink $SPATH;
+                      rmdir $LOCKPATH.'/cache_lock';
+                      mylog(warning=>"cache: SIG@_:".longmess().
+                        " terminating");
+                      CORE::dump(); exit};
+
+    $SIG{'ILL'}   = sub { 
+                      $SIG{"ABRT"} = '';
+                      unlink $SPATH;
+                      rmdir $LOCKPATH.'/cache_lock';
+                      mylog(warning=>"cache: SIG@_,".longmess().
+                        " terminating"); 
+                      CORE::dump(); exit};
+
+    $SIG{'ABRT'}  = sub { 
+                      unlink $SPATH;
+                      rmdir $LOCKPATH.'/cache_lock';
+                      mylog(warning=>"cache: SIG@_,".longmess().
+                        " terminating"); 
+                      CORE::dump(); exit};
+
+    $SIG{'FPE'}   = sub { 
+                      $SIG{"ABRT"} = '';
+                      unlink $SPATH;
+                      rmdir $LOCKPATH.'/cache_lock';
+                      mylog(warning=>"cache: SIG@_,".longmess().
+                        " terminating"); 
+                      CORE::dump(); exit};
+
+    $SIG{'BUS'}   = sub { 
+                      $SIG{"ABRT"} = '';
+                      unlink $SPATH;
+                      rmdir $LOCKPATH.'/cache_lock';
+                      mylog(warning=>"cache: SIG@_,".longmess().
+                        " terminating"); 
+                      CORE::dump(); exit};
+
+
+    use strict;
+    my $readable_handles = new IO::Select();
+
+    umask(0007); # alow only owner and group to read/write from/to socket
+
+    our $lsock = IO::Socket::UNIX->new( Listen => $SOMAXCONN,
+                                        Local => $SPATH) or 
+                                        die "warning: cache: $@ $!";
+
+    rmdir $LOCKPATH.'/cache_lock';
+    chown($<, $(, $SPATH); # set correct socket owner and group
+    
+    $readable_handles->add($lsock);
+
+    $| = 1;
+    my  $new_readable;
+    my  $i;
+    my  $KILL;
+    our $poscache_cnt = 0;
+    our $cache_cnt    = 0;
+    our $maintenance  = 0;
+    our $FORCE_MAINT;
+    
+    my  $old_mtime;
+    if($conf ne 'default settings')
+    {
+        $old_mtime = (stat($conf))[9];
+    }
+
+    ptime_conv();
+
+    while(1)
+    {
+        autoflush $lsock 1;
+        $FORCE_MAINT = 1;
+        ($new_readable) =
+            IO::Select->select($readable_handles, undef, undef, $MAXIDLECACHE);
+
+        foreach my $sock (@$new_readable)
+        {
+            $FORCE_MAINT = 0;
+
+            if($sock == $lsock)
+            {
+                my $new_sock = $sock->accept();
+                $new_sock->autoflush(1);
+                $readable_handles->add($new_sock);
+            }
+            else
+            {
+                    $sock->autoflush(1);
+                    my $buf = <$sock>;
+                    if(($buf) && ($buf =~ /\n.*?\n/)) 
+                        { mylog(info=>'cache: multiline request. Doh!'); }
+                    $buf =~ tr/\r\n//d if $buf;
+
+                    if($buf)
+                    {
+                        my $time = time;
+                        my $ret  = '0'; # this var will hold the returned
+                                        # result for the client if not told
+                                        # within the routines
+
+                        my($cv, $query, $ip, $rate, $sender, $domain) = 
+                            split(/ /, lc($buf));
+                        
+                        if($CVERSION != $cv && (!($KILL)))
+                        {
+                            mylog(info=>'cache: new cache version, terminating ASAP') if (!($KILL)); 
+                            $KILL = 1;
+                            $query = '';
+                        }
+
+
+
+                        if($query eq 'ask')
+                        {
+
+                            # check whether IP or IP-Sender are in SPAM cache
+                            foreach my $ckey ($ip, $ip.'-'.$sender)
+                            {
+                                if($cache{$ckey})
+                                {
+                                    my $tdiff = $time - $cache{$ckey}[2];
+                                    
+                                    if( ($cache{$ckey}[1] <= 0) &&
+                                        ($tdiff > $NTIME)
+                                      )
+                                    {
+                                        # NTTL reached and client retried it
+                                        # after NTIME seconds
+                                        
+                                        $ret = '0';
+                                        delete($cache{$ckey});
+                                        --$cache_cnt;
+
+                                    }
+                                    else
+                                    {
+                                        if($tdiff > $NTIME)
+                                        {
+                                            $cache{$ckey}[1] -= 1;
+                                        }
+                                        $ret = $CACHEREJECTMSG.
+                                            ' - retrying too fast. penalty: '.
+                                            $NTIME.' seconds x '.
+                                            $cache{$ckey}[1].' retries.';
+                                            $cache{$ckey}[2] = $time;
+                                            last;
+                                    }
+                                }
+                            }
+
+                            if(!($ret))
+                            {
+                                # ask the HAM cache
+
+                                my $ckey = $ip.'-'.$domain;
+                                if($poscache{$ckey})
+                                {
+                                    $ret = "rate: ";
+                                    # check entry time
+                                    if($time - $poscache{$ckey}[3] > 
+                                                             $my_TEMP_PTIME)
+                                    {
+                                        if( ($poscache{$ckey}[1] > 0) &&
+                                            ($time - $poscache{$ckey}[4] < 
+                                                                    $my_PTIME)
+                                          )
+                                        {
+                                            $ret = "rate:hard: ";
+                                            $poscache{$ckey}[1] -= 1;
+                                        }
+                                        else
+                                        {
+                                            $poscache{$ckey}[1] = $PTTL;
+                                            $poscache{$ckey}[4] = $time;
+                                        }
+                                    }
+                                    $ret .= $poscache{$ckey}[0];
+                                    $poscache{$ckey}[2] = $time;
+                                }
+                                
+                            }
+                        }
+
+
+
+                        elsif($query eq 'padd')
+                        {
+                            my $ckey = $ip.'-'.$domain;
+                            ++$poscache_cnt unless $poscache{$ckey};
+                            $poscache{$ckey}[0] = $rate;
+                            $poscache{$ckey}[1] = $PTTL;
+                            $poscache{$ckey}[2] = $time; # last seen
+                            $poscache{$ckey}[3] = $time; # TEMP_PTIME
+                            $poscache{$ckey}[4] = $time; # PTIME
+                            ++$maintenance;
+                        }
+
+                        elsif($query eq 'nadd')
+                        {
+                            my $ckey = $ip;
+                            if($domain)
+                            {
+                                $ckey = $ip.'-'.$sender;
+                            }
+                            ++$cache_cnt unless $cache{$ckey};
+                            $cache{$ckey}[0] = $rate;
+                            $cache{$ckey}[1] = $NTTL;
+                            $cache{$ckey}[2] = $time;
+                            ++$maintenance;
+                        }
+
+                        elsif($query =~ /^stat/)
+                        {
+                            while ( my ($key, $val) = each(%cache) )
+                            {
+                                $ret .= "blocked: $key ".join(" ",@$val)."\n";
+                            }
+                            while ( my ($key, $val) = each(%poscache) )
+                            {
+                                $ret .= "pass: $key ".join(" ",@$val)."\n";
+                            }
+                            $ret .= "EOF";
+                        }
+
+                        elsif($query eq 'reload')
+                        {
+                            $FORCE_MAINT = 1;
+                        }
+
+                        elsif($query eq 'kill')
+                        {
+                            $KILL = 1;
+                        }
+                        else
+                        {
+                            $ret = "unknown cache request: $buf\nEOF";
+                        }
+                        print $sock $query.$ip.$sender.' '.$ret."\n";
+                    }
+                    else
+                    {
+                        $readable_handles->remove($sock);
+                        close ($sock);
+                    }
+            }
+        }
+
+        ## kill the cache
+        if(($KILL) || (($FORCE_MAINT) && ($CMD_DEBUG)))
+        {
+            my $dbmsg = '';
+            $dbmsg = 'debug ' if $CMD_DEBUG;
+            unlink ($SPATH);
+            if($lsock) { close ($lsock) };
+            mylog  (info=>$dbmsg.'cache killed');
+            exit(0);
+        }
+
+        if( ($maintenance >= $MAINTENANCE_LEVEL) || ($FORCE_MAINT == 1) )
+        {
+            $maintenance = 0;
+            conf_check('cache');
+        }
+
+        ## clean up cache
+        if($poscache_cnt > $POSCACHEMAXSIZE) 
+        {
+            my $purgecnt = 0;
+            my $startt = time;
+            for(sort { $poscache{$a}[2] <=> $poscache{$b}[2] } keys %poscache)
+            {
+                if($poscache_cnt > $POSCACHESIZE)
+                {
+                    delete($poscache{$_});
+                    ++$purgecnt;
+                    --$poscache_cnt;
+                }
+                else
+                {
+                    last;
+                }
+            }
+
+            if($purgecnt > 0)
+            {
+                mylog(info=>"cache: purged $purgecnt from HAM cache, time: ".(time - $startt).'s');
+            }
+        }
+
+        if($cache_cnt > $CACHEMAXSIZE)
+        {
+            my $purgecnt = 0;
+            my $startt = time;
+            for(sort { $cache{$a}[2] <=> $cache{$b}[2] } keys %cache)
+            {
+                if($cache_cnt > $CACHESIZE)
+                {
+                    delete($cache{$_});
+                    ++$purgecnt;
+                    --$cache_cnt;
+                }
+                else
+                {
+                    last;
+                }
+            }
+
+            if($purgecnt > 0)
+            {
+                mylog(info=>"cache: purged $purgecnt from SPAM cache, time: ".(time - $startt).'s');
+            }
+        }
+    }
+}
+
+
+
+#
+# mylog(FACILITY, STRING)
+#
+# prints FACILITY, STRING on STDOUT when in command-line debug (-d) mode
+# otherwise passes it to syslog()
+#
+sub mylog
+{
+    my $fac    = shift(@_);
+    my $string = join(' ', @_);
+
+    if($CMD_DEBUG)
+    {
+        my $now =  scalar(localtime);
+           $now =~ /(\d\d:\d\d:\d\d)/;
+        
+        print("$1 $fac: $string");
+        print "\n";
+    }
+    else
+    {
+        my $log_trap;
+        if($fac ne 'info')
+        {
+            $string = $fac.': '.$string;
+        }
+        $@ = '';
+        eval
+        {
+            local $SIG{'__DIE__'};
+            syslog($fac, "%s", $string);
+        };
+        while($@)
+        {
+            if($log_trap++ >= 5)
+            { 
+                emerge_log($fac, $string);
+                last;
+            }
+            select(undef, undef, undef, 0.2); # sleep 0.2 sec
+            $@ = '';
+            eval
+            {
+                local $SIG{'__DIE__'};
+                syslog($fac, "%s", $string);
+            };
+        }
+    }
+}
+
+
+# emerge_log is a routine which shouldn't die()
+# it logs entries in case of syslog absence
+# a die() in emerge_log could mean a log-loop
+# this routine is not resource optimized
+sub emerge_log
+{
+    local $SIG{__DIE__};
+    open(ELOG, ">>$LOCKPATH/polw-emergency.log");
+    print ELOG  localtime().' '.
+                $syslog_ident.
+                '['.$$."]: $syslog_facility: $_[1]\n";
+    close ELOG;
+}
+
+
+
+# rbl_lookup RBL_QUERY [TYPE] 
+# returns: 1: found, -1: not found, 0: error, -2: sock err
+# remember to give IP octets in reversed order.
+# EG: IP: 121.122.123.124, Host: mail.example.com, Rbl: bl.rbl.com
+# RBL_QUERY  : "124.123.122.121.bl.rbl.com"
+# RHSBL_QUERY: "mail.example.com.bl.rbl.com"
+# TYPE       : additonal and usually not needed, default is TXT
+# In case of weird errors it tries to use Net::DNS
+# You may force the permanent usage of Net::DNS by global setting USE_NET_DNS
+sub rbl_lookup
+{
+        my @bu = @_;
+        if($bu[0] =~ /[^.]{64}/) { return (1) }; # see RFC 1035 sect. 2.3.4
+        while(length($bu[0]) > 255)              # see RFC 1035 sect. 3.1
+        {
+            $bu[0] =~ s/.*?\.//;
+        } 
+        if(($USE_NET_DNS == 1) || ($] < 5.008000))
+        {
+            my $answ = $res->send(@bu);
+            if   (!($answ))             { return (0)  } # dns error
+            elsif(($answ->answer) > 0 ) { return (1)  } # found
+            else                        { return (-1) } # not found
+        }
+
+        my $query = shift(@bu);
+        my $rtype = shift(@bu);
+        my $oid   = 1 + int(rand(65535));
+           $rtype = 'A' unless ($rtype && $RTYPES{$rtype});
+
+                          # ID    RD      QDCOUNT
+        my $p = pack ("n*", $oid, 0x100,  1,        0, 0, 0) .
+        
+        # concatenate the query and pack it into length preceded labels
+                pack ('(C/A*)*', split /\./, $query ).
+                pack ('@ (n*)*', $RTYPES{$rtype},      1);
+#                                ^QTYPE                ^QCLASS    see: RFC 1035 
+      
+        $SIG{ALRM} = sub { 
+            mylog(warning=>"rbl_lookup: SIGALRM trapped?! Report."); 
+            return 
+        };
+        
+        my $buf;
+        my $errcnt = 0;
+        my $dropped = 0;
+
+        while($s)
+        {
+            alarm 0; # reset all eventually alarms
+            if($dropped==0)
+            {
+                mylog(info=>"rbl_lookup: sending: $query, $oid") if $DEBUG;
+
+                eval
+                {
+                    local $SIG{ALRM} = sub { die "ETIMEOUT" };
+                    alarm $DNS_RETRY_IVAL;
+                    if($s->send($p) < length($p))
+                    {
+                        mylog(warning=>"rbl_lookup: sent bytes != packet size");
+                        ++$errcnt; # timeout or error on sending
+                    }
+                    alarm 0;
+                };
+
+                if($@)
+                {
+                    ++$errcnt;
+                    mylog(warning=>"rbl_lookup: timeout sending: $query") if $DEBUG;
+                    next;
+                }
+            }            
+            $dropped = 0;
+            my $buf;
+
+            eval
+            {
+                local $SIG{ALRM} = sub { die "ETIMEOUT" };
+                alarm $DNS_RETRY_IVAL;
+                $s->recv($buf, 2048);
+                alarm 0;
+            };
+
+            if((!($buf)) && ($errcnt < $DNS_RETRIES))
+            {
+                ++$errcnt;
+                next;
+            }
+            elsif((!($buf)) && ($errcnt >= $DNS_RETRIES))
+            {
+                return(0);  # too many timeouts or errors
+            }
+
+            my    ($id, $bf, $qc, $anc, $nsc, $arc, $qb) = 
+            unpack('n   n    n    n     n     n     a*', $buf);
+
+            my ($dn, $offset) = dn_expand(\$qb, 0);
+
+            if(($id && $anc) && ($id == $oid) && ($query eq $dn))
+            {
+                mylog(info=>"rbl_lookup: $query vs $dn, $oid vs $id,  anc == $anc") if $DEBUG;
+                return(1);  # found
+            }
+            elsif($id && (!($anc)) && ($id == $oid) && ($query eq $dn))
+            {
+                mylog(info=>"rbl_lookup: $query vs $dn, $oid vs $id, anc == 0") if $DEBUG; 
+                return(-1); # not found
+            }
+            elsif(($id && $dn) && (($query ne $dn) || ($id != $oid)))
+            {
+                mylog(info=>"rbl_lookup: dropped: out:$query vs in:$dn, out:$oid vs in:$id") if $DEBUG;
+                $dropped = 1;
+                return(0) if $errcnt >= $DNS_RETRIES;
+                next;       # wrong packet received, drop
+            }
+            mylog(warning=>"rbl_lookup: unknown error: out:$query, in:$dn, out-id:$oid, in-id:$id");
+            return(0) if $errcnt >= $DNS_RETRIES;
+            ++$errcnt;      # unknown error
+        }
+        mylog(warning=>'RBL Socket died, using Net-DNS now.');
+        $USE_NET_DNS = 1;
+        return(rbl_lookup(@bu)); # return Net::DNS result
+}
+
+sub conf_check
+{
+    my $who = shift;
+    if($conf ne 'default settings')
+    {
+        my @conf_stat = stat($conf);
+        if( $conf_stat[9] != $old_mtime )
+        {
+            if(sprintf("%04o",$conf_stat[2]) !~ /(7|6|3|2)$/)
+            {
+                my $conf_str;
+                if(open(CONF, $conf))
+                {
+                    read(CONF,$conf_str,-s CONF);
+                    close(CONF);
+
+                    #XXX taint $conf_str as $< enables taint mode
+                    ($conf_str) = $conf_str =~ m/(.*)/s;
+
+                    eval $conf_str;
+                    if($@)
+                    {
+                        mylog(warning=>"$who: syntax error in file $conf: ".$@);
+                    }
+                    else
+                    {
+                        $old_mtime = $conf_stat[9];
+                        ptime_conv();
+                        mylog(info=>"$who: $conf reloaded");
+                    }
+                }
+                else
+                {
+                    mylog(warning=>"$who: could not open $conf: $!");
+                }
+            }
+            else
+            {
+                 mylog(warning=>"$who: conf-err: $conf is world-writeable! Config not reloaded!");
+            }
+        }
+    }
+}
+
+
+sub create_lockpath
+{
+    my $who = shift(@_);
+
+    if(!( -d $LOCKPATH))
+    {
+        mkdir $LOCKPATH or die "$who: error while creating $LOCKPATH: $!";
+    }
+
+
+    #
+    # check LOCKPATH, SPATH and cache_lock for being part of symlinks
+    #
+    check_symlnk( $who, $LOCKPATH, $SPATH, "$LOCKPATH/cache_lock" );
+
+
+    my $tuid = $USER;
+
+    if($USER =~ /[^0-9]/)
+    {
+        if( !(defined( $tuid = getpwnam($USER) ) ) )
+        {
+            mylog(warning=>"User $USER doesn't exist, create it, or set \$USER");
+        }
+    }
+    if( !(chown ($tuid, -1, $LOCKPATH)) )
+    {
+        mylog(warning=>
+            "$who: Couldn't chown $LOCKPATH to $USER ($tuid): $! - UID/EUID: $</$>");
+    }
+    if( !(chmod (0700, $LOCKPATH)) )
+    {
+        mylog(warning=>
+            "$who: Couldn't set permissions on $LOCKPATH for $USER ($tuid): $! - UID/EUID: $</$>");
+    }
+}
+
+
+
+
+
+#
+# usage: check_symlnk($caller, @files)
+#
+# caller: context specifier (eg: "cache: function:")
+# files:  list of files to check
+sub check_symlnk
+{
+    my $who = shift;
+    for ( @_ )
+    {
+
+        my $file = File::Spec->canonpath($_);
+
+        my @stat = lstat( $file );
+
+        if(! -e _)
+        {
+            next;
+        }
+
+
+        # first, file must not be a symlink
+        if ( -l _ )
+        {
+            fatal_exit("$who: $file is a symbolic link. Symbolic links are not expected and not allowed within policyd-weight. Exiting!");
+        }
+
+
+        # second, file must be owned by uid root or $USER and
+        # gid root/wheel or $USER
+        if(!(  
+                ( 
+                    $stat[4] == getpwnam($USER) ||
+                    $stat[4] == "0"
+                ) &&
+                (
+                    $stat[5] == getgrnam($GROUP) ||
+                    $stat[5] == "0"
+                )
+            )
+          )
+        {
+            fatal_exit("$who: $file is owned by UID $stat[4], GID $stat[5]. Exiting!");
+        }
+
+
+        # third, the file/dir must not be world writeable
+        if(sprintf("%04o",$stat[2]) =~ /(7|6|3|2)$/)
+        {
+            fatal_exit("$who: $file is world writeable. Exiting!");
+        }       
+
+
+    }
+}
+
+
+
+
+
+
+# function for sanitizing floating point output
+sub myrnd
+{
+    my $n = index($_[0], ".");
+    if($n > 0)
+    {
+        $n-- if index($_[0], "-") >= 0;
+        return(sprintf("%.".($n+3)."g", $_[0]));
+    }
+    return($_[0]);
+}
+
+sub ptime_conv
+{
+# convert PTIME and TEMP_PTIME to seconds
+    my %time_conv;
+    $time_conv{'s'} = 1;
+    $time_conv{'m'} = 60;
+    $time_conv{'h'} = 3600;
+    $time_conv{'d'} = 86400;
+
+    my $time_unit;
+
+    if($PTIME =~ /.*?(\d+)([smhd]{0,1}).*/)
+    {
+        if(!($2)) { $time_unit = 's' }
+        else      { $time_unit = $2  }
+        $my_PTIME = $1 * $time_conv{$time_unit};
+    }
+    else
+    {
+        mylog(warning=>"cache: \$PTIME in wrong format. Using default.");
+        $my_PTIME = 10800; # 3 hours
+    }
+
+    if($TEMP_PTIME =~ /.*?(\d+)([smhd]{0,1}).*/)
+    {
+        if(!($2)) { $time_unit = 's' }
+        else      { $time_unit = $2  }
+        $my_TEMP_PTIME = $1 * $time_conv{$time_unit};   
+    }
+    else
+    {
+        mylog(warning=>"cache: \$TEMP_PTIME in wrong format. Using default.");
+        $my_TEMP_PTIME = 259200;  # 3 days
+    }
+
+    mylog(info=>"cache: PTIME: $my_PTIME, TEMP_PTIME: $my_TEMP_PTIME") if $DEBUG or $VERBOSE;
+}
+
+
+#
+# Usage: dns_error(\$query_object, \$res_object)
+#
+# Returns undef in case of NOERROR or NXDOMAIN
+# Returns 1 in all other cases
+#
+# This function expects references to objects of Net::DNS as arguments
+sub dns_error
+{
+    my ($myquery, $myres) = @_;
+
+    return 1 if not $$myquery;
+    return 1 if not $$myres;
+    return undef if $$myres->errorstring eq 'NOERROR' or 
+                    $$myres->errorstring eq 'NXDOMAIN';
+    mylog(debug=>"dns_error: errorstring: ".$$myres->errorstring) if $CMD_DEBUG;
+    return 1;
+}
+
+#
+# returns 1 if the helo is in [n.n.n.n] notation, 
+# valid, and matches the client ip
+#
+sub squared_helo
+{
+    my $helo = shift;
+    my $ip   = shift;
+
+    if($$helo !~ /^\[(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\]$/ ) { return }
+    my $tmp_helo_ip = $1;
+
+    my $tmpip = inet_aton( $tmp_helo_ip );
+    
+    length($tmpip) or return;
+
+    $tmpip = inet_ntoa($tmpip);
+
+    if($tmpip eq $$ip) { return 1 }
+
+    return 0;
+}
+
+
diff --git a/policyd-weight.conf.sample b/policyd-weight.conf.sample
new file mode 100644 (file)
index 0000000..4aa11bc
--- /dev/null
@@ -0,0 +1,260 @@
+# ----------------------------------------------------------------
+#  policyd-weight configuration (defaults) Version 0.1.14 beta-17
+# ----------------------------------------------------------------
+
+
+   $DEBUG        = 0;               # 1 or 0 - don't comment
+
+   $REJECTMSG    = "550 Mail appeared to be SPAM or forged. Ask your Mail/DNS-Administrator to correct HELO and DNS MX settings or to get removed from DNSBLs";
+
+   $REJECTLEVEL  = 1;               # Mails with scores which exceed this
+                                    # REJECTLEVEL will be rejected
+
+   $DEFER_STRING = 'IN_SPAMCOP= BOGUS_MX='; 
+                                    # A space separated case-sensitive list of
+                                    # strings on which if found in the $RET
+                                    # logging-string policyd-weight changes
+                                    # its action to $DEFER_ACTION in case
+                                    # of rejects.
+                                    # USE WITH CAUTION!
+                                    # DEFAULT: "IN_SPAMCOP= BOGUS_MX="
+
+
+   $DEFER_ACTION = '450';           # Possible values: DEFER_IF_PERMIT,
+                                    # DEFER_IF_REJECT, 
+                                    # 4xx response codes. See also access(5)
+                                    # DEFAULT: 450
+
+   $DEFER_LEVEL  = 5;               # DEFER mail only up to this level
+                                    # scores greater than DEFER_LEVEL will be
+                                    # rejected
+                                    # DEFAULT: 5
+
+   $DNSERRMSG         = '450 No DNS entries for your MTA, HELO and Domain. Contact YOUR administrator';
+
+   $dnsbl_checks_only = 0;          # 1: ON, 0: OFF (default)
+                                    # If ON request that ALL clients are only
+                                    # checked against RBLs
+
+   @dnsbl_checks_only_regexps = (
+    # qr/[^.]*(exch|smtp|mx|mail).*\..*\../,
+    # qr/yahoo.com$/
+);                                  # specify a comma-separated list of regexps
+                                    # for client hostnames which shall only
+                                    # be RBL checked. This does not work for
+                                    # postfix' "unknown" clients.
+                                    # The usage of this should not be the norm
+                                    # and is a tool for people which like to
+                                    # shoot in their own foot.
+                                    # DEFAULT: empty
+                                    
+
+   $LOG_BAD_RBL_ONLY  = 1;          # 1: ON (default), 0: OFF
+                                    # When set to ON it logs only RBLs which
+                                    # affect scoring (positive or negative)
+                                    
+## DNSBL settings
+   @dnsbl_score = (
+#    HOST,                    HIT SCORE,  MISS SCORE,  LOG NAME
+    'pbl.spamhaus.org',       3.25,          0,        'DYN_PBL_SPAMHAUS',
+    'sbl-xbl.spamhaus.org',   4.35,       -1.5,        'SBL_XBL_SPAMHAUS',
+    'bl.spamcop.net',         3.75,       -1.5,        'SPAMCOP',
+    'dnsbl.njabl.org',        4.25,       -1.5,        'BL_NJABL',
+    'list.dsbl.org',          4.35,          0,        'DSBL_ORG',
+    'ix.dnsbl.manitu.net',    4.35,          0,        'IX_MANITU'
+);
+
+   $MAXDNSBLHITS  = 2;  # If Client IP is listed in MORE
+                        # DNSBLS than this var, it gets
+                        # REJECTed immediately
+
+   $MAXDNSBLSCORE = 8;  # alternatively, if the score of
+                        # DNSBLs is ABOVE this
+                        # level, reject immediately
+
+   $MAXDNSBLMSG   = '550 Your MTA is listed in too many DNSBLs';
+
+## RHSBL settings
+   @rhsbl_score = (
+    'multi.surbl.org',             4,        0,        'SURBL',
+    'rhsbl.ahbl.org',              4,        0,        'AHBL',
+    'dsn.rfc-ignorant.org',        3.5,      0,        'DSN_RFCI',
+    'postmaster.rfc-ignorant.org', 0.1,      0,        'PM_RFCI',
+    'abuse.rfc-ignorant.org',      0.1,      0,        'ABUSE_RFCI'
+);
+
+   $BL_ERROR_SKIP     = 2;  # skip a RBL if this RBL had this many continuous
+                            # errors
+
+   $BL_SKIP_RELEASE   = 10; # skip a RBL for that many times
+
+## cache stuff
+   $LOCKPATH          = '/tmp/.policyd-weight/';    # must be a directory (add
+                                                    # trailing slash)
+
+   $SPATH             = $LOCKPATH.'/polw.sock';     # socket path for the cache
+                                                    # daemon. 
+
+   $MAXIDLECACHE      = 60; # how many seconds the cache may be idle
+                            # before starting maintenance routines
+                            # NOTE: standard maintenance jobs happen
+                            # regardless of this setting.
+
+   $MAINTENANCE_LEVEL = 5;  # after this number of requests do following
+                            # maintenance jobs:
+                            # checking for config changes
+
+# negative (i.e. SPAM) result cache settings ##################################
+
+   $CACHESIZE       = 2000; # set to 0 to disable caching for spam results. 
+                            # To this level the cache will be cleaned.
+
+   $CACHEMAXSIZE    = 4000; # at this number of entries cleanup takes place
+
+   $CACHEREJECTMSG  = '550 temporarily blocked because of previous errors';
+
+   $NTTL            = 1;    # after NTTL retries the cache entry is deleted
+
+   $NTIME           = 30;   # client MUST NOT retry within this seconds in order
+                            # to decrease TTL counter
+
+
+# positve (i.,e. HAM) result cache settings ###################################
+
+   $POSCACHESIZE    = 1000; # set to 0 to disable caching of HAM. To this number
+                            # of entries the cache will be cleaned
+
+   $POSCACHEMAXSIZE = 2000; # at this number of entries cleanup takes place
+
+   $POSCACHEMSG     = 'using cached result';
+
+   $PTTL            = 60;   # after PTTL requests the HAM entry must
+                            # succeed one time the RBL checks again
+
+   $PTIME           = '3h'; # after $PTIME in HAM Cache the client
+                            # must pass one time the RBL checks again.
+                            # Values must be nonfractal. Accepted
+                            # time-units: s, m, h, d
+
+   $TEMP_PTIME      = '1d'; # The client must pass this time the RBL
+                            # checks in order to be listed as hard-HAM
+                            # After this time the client will pass
+                            # immediately for PTTL within PTIME
+
+
+## DNS settings
+   $DNS_RETRIES     = 2;    # Retries for ONE DNS-Lookup
+
+   $DNS_RETRY_IVAL  = 2;    # Retry-interval for ONE DNS-Lookup
+
+   $MAXDNSERR       = 3;    # max error count for unresponded queries
+                            # in a complete policy query
+
+   $MAXDNSERRMSG    = 'passed - too many local DNS-errors';
+
+   $PUDP            = 0;    # persistent udp connection for DNS queries.
+                            # broken in Net::DNS version 0.51. Works with
+                            # Net::DNS 0.53; DEFAULT: off
+
+   $USE_NET_DNS     = 0;    # Force the usage of Net::DNS for RBL lookups.
+                            # Normally policyd-weight tries to use a faster
+                            # RBL lookup routine instead of Net::DNS
+
+
+   $NS              = '';   # A list of space separated NS IPs
+                            # This overrides resolv.conf settings
+                            # Example: $NS = '1.2.3.4 1.2.3.5';
+                            # DEFAULT: empty
+
+
+   $IPC_TIMEOUT     = 2;    # timeout for receiving from cache instance
+
+   $TRY_BALANCE     = 0;    # If set to 1 policyd-weight closes connections
+                            # to smtpd clients in order to avoid too many
+                            # established connections to one policyd-weight
+                            # child
+
+# scores for checks, WARNING: they may manipulate eachother
+# or be factors for other scores.
+#                                       HIT score, MISS Score
+   @client_ip_eq_helo_score          = (1.5,       -1.25 );
+   @helo_score                       = (1.5,       -2    );
+   @helo_from_mx_eq_ip_score         = (1.5,       -3.1  );
+   @helo_numeric_score               = (2.5,        0    );
+   @from_match_regex_verified_helo   = (1,         -2    );
+   @from_match_regex_unverified_helo = (1.6,       -1.5  );
+   @from_match_regex_failed_helo     = (2.5,        0    );
+   @helo_seems_dialup                = (1.5,        0    );
+   @failed_helo_seems_dialup         = (2,          0    );
+   @helo_ip_in_client_subnet         = (0,         -1.2  );
+   @helo_ip_in_cl16_subnet           = (0,         -0.41 );
+   @client_seems_dialup_score        = (3.75,       0    );
+   @from_multiparted                 = (1.09,       0    );
+   @from_anon                        = (1.17,       0    );
+   @bogus_mx_score                   = (2.1,        0    );
+   @random_sender_score              = (0.25,       0    );
+   @rhsbl_penalty_score              = (3.1,        0    );
+   @enforce_dyndns_score             = (3,          0    );
+
+
+   $VERBOSE = 0;
+
+   $ADD_X_HEADER        = 1;    # Switch on or off an additional 
+                                # X-policyd-weight: header
+                                # DEFAULT: on
+
+
+   $DEFAULT_RESPONSE    = 'DUNNO default'; # Fallback response in case
+                                           # the weighted check didn't
+                                           # return any response (should never
+                                           # appear).
+
+
+
+#
+# Syslogging options for verbose mode and for fatal errors.
+# NOTE: comment out the $syslog_socktype line if syslogging does not
+# work on your system.
+#
+
+   $syslog_socktype = 'unix';   # inet, unix, stream, console
+
+   $syslog_facility = "mail";
+   $syslog_options  = "pid";
+   $syslog_priority = "info";
+   $syslog_ident    = "postfix/policyd-weight";
+
+
+#
+# Process Options
+#
+   $USER            = "polw";      # User must be a username, no UID
+
+   $GROUP           = "";          # specify GROUP if necessary
+                                   # DEFAULT: empty, will be initialized as 
+                                   # $USER
+
+   $MAX_PROC        = 50;          # Upper limit if child processes
+   $MIN_PROC        = 3;           # keep that minimum processes alive
+
+   $TCP_PORT        = 12525;       # The TCP port on which policyd-weight 
+                                   # listens for policy requests from postfix
+
+   $BIND_ADDRESS    = '127.0.0.1'; # IP-Address on which policyd-weight will
+                                   # listen for requests.
+                                   # You may only list ONE IP here, if you want
+                                   # to listen on all IPs you need to say 'all'
+                                   # here. Default is '127.0.0.1'.
+                                   # You need to restart policyd-weight if you
+                                   # change this.
+
+   $SOMAXCONN       = 1024;        # Maximum of client connections 
+                                   # policyd-weight accepts
+                                   # Default: 1024
+                                   
+
+   $CHILDIDLE       = 240;         # how many seconds a child may be idle before
+                                   # it dies.
+
+   $PIDFILE         = "/var/run/policyd-weight.pid";
+
diff --git a/todo.txt b/todo.txt
new file mode 100644 (file)
index 0000000..a91c8f1
--- /dev/null
+++ b/todo.txt
@@ -0,0 +1,47 @@
+Item                                                Status             Req. for 
+                                                    In Progress:   P   stable
+                                                    Done in devel: D 
+-------------------------------------------------------------------------------
+- Multiplexing policy requests and DNS                                 Y
+  queries via select()
+
+- probably milter capabilities
+
+- probably external hooks at certain stages
+
+- porting for other MTAs (helpers welcome)
+
+- packaging for other systems (helpers welcome)
+
+- IPv6 support
+  Need someone who can do this, since we have 
+  no IPv6 environment for testing here. 
+  For now IPv6 clients are recognized and pass 
+  unchecked.
+
+- proper documentation (helpers welcome)            P                  Y
+
+- man page for config settings                      P                  Y
+
+- probably SPF support (scored)
+  Problem: DUL listed DynDNS MX users might get 
+  rejected if they use a "mail from:" like 
+  user@yahoo.com but deliver direct.
+
+- SNSD (Spammy NS Detection)                                           Y
+  Shall detect NS servers which excessive host 
+  Spam-Domains
+
+- ADD  (Abused Domain Detection)                                       Y
+  Shall detect Domains which are frequently 
+  forged and may not be used by Dynamic Clients 
+  unless they got a DynDNS MX as verified HELO
+
+- postfix restriction classes support               P                  Y
+  (postfix doesn't recognize 
+  "restrictionclass some explanatory text" as 
+  restriction-class-request even though 
+  "restrictionclass" is defined in main.cf)
+
+- review cache efficiency and avoidance of          P                  Y
+  dictionary attacks