changeset 0:96a9758165cd original

Initial revision
author carl
date Tue, 20 Apr 2004 20:02:29 -0700
parents
children bd0b1a153f67
files COPYING Makefile.test dnsbl.rc install.bash sendmail.st src/dnsbl.cpp src/package src/update test.bash test.cf test.mc xml/dnsbl.in xml/sample.conf
diffstat 13 files changed, 3523 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/COPYING	Tue Apr 20 20:02:29 2004 -0700
@@ -0,0 +1,340 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                       59 Temple Place, Suite 330, Boston, MA  02111-1307  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.
+
+		    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.)
+
+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.
+
+  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.
+
+  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
+
+	    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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Makefile.test	Tue Apr 20 20:02:29 2004 -0700
@@ -0,0 +1,37 @@
+# These could be used by sendmail, but are not part of the default install.
+# To use them you will have to generate your own sendmail.cf with
+#  FEATURE('whatever')
+#
+POSSIBLE += $(shell test -f bitdomain     && echo bitdomain.db)
+POSSIBLE += $(shell test -f uudomain      && echo uudomain.db)
+POSSIBLE += $(shell test -f genericstable && echo genericstable.db)
+POSSIBLE += $(shell test -f userdb	  && echo userdb.db)
+CFFILES   = sendmail.cf submit.cf
+
+
+all: ${CFFILES} ${POSSIBLE} virtusertable.db access.db domaintable.db mailertable.db
+	@chown root:smmsp *.db
+
+userdb.db : userdb
+	@makemap btree $@ < $<
+
+access : access.header access.common access.510sg access.davd
+	cat access.header access.510sg access.common >access
+	cat access.header access.davd  access.common | ssh mail3.davdgrp.com 'cat >/etc/mail/access'
+	ssh mail3.davdgrp.com '(cd /usr/src; sh makesendmailtable.bat)'
+	rm -f /home/httpd/html/510sg/bogus.list
+	cp access /home/httpd/html/510sg/bogus.list
+	scp access ns1:/home/httpd/html/510sg/bogus.list
+
+%.db : %
+	@makemap hash $@ < $<
+
+%.cf : %.mc
+	@if test -f /usr/share/sendmail-cf/m4/cf.m4; then \
+		mv -f $@ $@.bak; \
+		m4 $< > $@; \
+	fi;
+
+clean:
+	rm -f *.db *~
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dnsbl.rc	Tue Apr 20 20:02:29 2004 -0700
@@ -0,0 +1,51 @@
+#!/bin/sh
+#
+# dnsbl   This script controls the dnsbl milter daemon.
+#
+#
+# chkconfig: 2345 79 31
+# description: dnsbl is an interface between MTA and the various dns blocking lists
+# processname: dnsbl
+# pidfile: /var/run/dnsbl.pid
+# Source function library.
+#
+. /etc/rc.d/init.d/functions
+
+# Source networking configuration.
+. /etc/sysconfig/network
+
+RETVAL=0
+# See how we were called.
+case "$1" in
+  start)
+        # Start daemons.
+        echo -n "Starting dnsbl-milter: "
+        cd /var/dnsbl   # conf file is here
+        ./dnsbl -p local:/var/run/dnsbl.sock
+        RETVAL=$?
+        sleep 3
+        echo
+        [ $RETVAL -eq 0 ] && touch /var/lock/subsys/dnsbl
+        ;;
+  stop)
+        # Stop daemons.
+        echo -n "Shutting down dnsbl-milter: "
+        killproc dnsbl
+        RETVAL=$?
+        echo
+        [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/dnsbl
+        ;;
+  restart|reload)
+        $0 stop
+        $0 start
+        RETVAL=$?
+        ;;
+  status)
+        status dnsbl
+        RETVAL=$?
+        ;;
+  *)
+        echo "Usage: dnsbl {start|stop|restart|status}"
+        exit 1
+esac
+exit $RETVAL
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/install.bash	Tue Apr 20 20:02:29 2004 -0700
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+#####################
+# stop the milter if it is already installed
+if [ -f /etc/rc.d/init.d/dnsbl ]; then
+    /etc/rc.d/init.d/dnsbl stop
+    rm -f /etc/rc.d/init.d/dnsbl
+fi
+
+
+#####################
+# build the milter
+g++ -c dnsbl.cpp
+g++ -o dnsbl dnsbl.o /usr/lib/libresolv.a -lmilter -pthread
+if [ "$1" == "comp" ]; then
+    exit
+fi
+
+
+#####################
+# install the milter
+DST=/var/dnsbl
+mkdir -p $DST
+cp    *.conf $DST
+mv -f dnsbl  $DST
+cp dnsbl.rc /etc/rc.d/init.d/dnsbl
+chmod 755 /etc/rc.d/init.d/dnsbl
+/sbin/chkconfig --add dnsbl
+/sbin/chkconfig --level 2345 dnsbl on
+/etc/rc.d/init.d/dnsbl start
Binary file sendmail.st has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/dnsbl.cpp	Tue Apr 20 20:02:29 2004 -0700
@@ -0,0 +1,823 @@
+/*
+
+Copyright (c) 2004 Carl Byington - 510 Software Group, released under
+the GPL version 2 or any later version at your choice available at
+http://www.fsf.org/licenses/gpl.txt
+
+Based on a sample milter Copyright (c) 2000-2003 Sendmail, Inc. and its
+suppliers.  Inspired by the DCC by Rhyolite Software
+
+-p port  The port through which the MTA will connect to this milter.
+-t sec   The timeout value.
+
+TODO:
+
+1) Add "include-dcc NAME fn" to read a dcc whiteclnt file looking
+for many substitute mail-host domain, and add the equivalent "env_from
+domain black" into the NAME mapping. That allows clients to use just the
+DCC for white/blacklisting, but the backup mx machines can use dnsbl
+and get the same effect.
+
+*/
+
+
+// from sendmail sample
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+// needed for socket io
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <netdb.h>
+#include <sys/socket.h>
+
+// needed for thread
+#include <pthread.h>
+
+// needed for std c++ collections
+#include <set>
+#include <map>
+#include <list>
+
+// for the dns resolver
+#include <netinet/in.h>
+#include <arpa/nameser.h>
+#include <resolv.h>
+
+// misc stuff needed here
+#include <ctype.h>
+#include <fstream>
+#include <syslog.h>
+
+
+using namespace std;
+
+
+extern "C" {
+    #include "libmilter/mfapi.h"
+    sfsistat mlfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr);
+    sfsistat mlfi_envfrom(SMFICTX *ctx, char **argv);
+    sfsistat mlfi_envrcpt(SMFICTX *ctx, char **argv);
+    sfsistat mlfi_eom_or_abort(SMFICTX *ctx);
+    sfsistat mlfi_close(SMFICTX *ctx);
+}
+
+#ifndef bool
+# define bool   int
+# define TRUE   1
+# define FALSE  0
+#endif /* ! bool */
+
+struct ltstr {
+    bool operator()(char* s1, char* s2) const {
+        return strcmp(s1, s2) < 0;
+    }
+};
+
+struct DNSBL {
+    char    *suffix;    // blacklist suffix like blackholes.five-ten-sg.com
+    char    *message;   // error message with one or two %s operators for the ip address replacement
+    DNSBL(char *s, char *m);
+};
+DNSBL::DNSBL(char *s, char *m) {
+    suffix  = s;
+    message = m;
+}
+
+typedef DNSBL *                           DNSBLP;
+typedef list<DNSBLP>                      DNSBLL;
+typedef DNSBLL *                          DNSBLLP;
+typedef map<char *, char *, ltstr>        string_map;
+typedef map<char *, string_map *, ltstr>  from_map;
+typedef map<char *, DNSBLP, ltstr>        dnsblp_map;
+typedef map<char *, DNSBLLP, ltstr>       dnsbllp_map;
+typedef set<char *, ltstr>                string_set;
+typedef list<char *>                      string_list;
+
+struct CONFIG {
+    // the only mutable stuff once it has been loaded from the config file
+    int         reference_count;    // protected by the global config_mutex
+    // all the rest is constant after loading from the config file
+    time_t      load_time;
+    string_list config_files;
+    dnsblp_map  dnsbls;
+    dnsbllp_map dnsblls;
+    from_map    env_from;
+    string_map  env_to_dnsbll;      // map recipient to a named dnsbll
+    string_map  env_to_chkfrom;     // map recipient to a named from map
+    CONFIG();
+    ~CONFIG();
+};
+CONFIG::CONFIG() {
+    reference_count = 0;
+    load_time       = 0;
+}
+CONFIG::~CONFIG() {
+    for (dnsblp_map::iterator i=dnsbls.begin(); i!=dnsbls.end(); i++) {
+        DNSBLP d = (*i).second;
+        delete d;
+    }
+    for (dnsbllp_map::iterator i=dnsblls.begin(); i!=dnsblls.end(); i++) {
+        DNSBLLP d = (*i).second;
+        delete d;
+    }
+    for (from_map::iterator i=env_from.begin(); i!=env_from.end(); i++) {
+        string_map *d = (*i).second;
+        delete d;
+    }
+}
+
+static string_set all_strings;      // owns all the strings, only modified by the config loader thread
+static CONFIG * config = NULL;      // protected by the config_mutex
+
+static pthread_mutex_t  config_mutex;
+static pthread_mutex_t  syslog_mutex;
+static pthread_mutex_t  resolve_mutex;
+
+
+
+////////////////////////////////////////////////
+// predefined names
+//
+#define DEFAULT "default"
+#define WHITE   "white"
+#define BLACK   "black"
+
+
+////////////////////////////////////////////////
+// mail filter private data, held for us by sendmail
+//
+enum status {oksofar,   // not rejected yet
+             white,     // whitelisted by envelope from
+             black,     // blacklisted by envelope from or to
+             reject};   // rejected by a dns list
+struct mlfiPriv
+{
+    CONFIG  *pc;            // global context with our maps
+    int     ip;             // ip4 address of the smtp client
+    char    *mailaddr;      // envelope from value
+    bool    authenticated;  // client authenticated? if so, suppress all dnsbl checks
+    map<DNSBLP, status> checked;    // status from those lists
+    mlfiPriv();
+    ~mlfiPriv();
+};
+mlfiPriv::mlfiPriv() {
+    pthread_mutex_lock(&config_mutex);
+        pc = config;
+        pc->reference_count++;
+    pthread_mutex_unlock(&config_mutex);
+    ip       = 0;
+    mailaddr = NULL;
+}
+mlfiPriv::~mlfiPriv() {
+    pthread_mutex_lock(&config_mutex);
+        pc->reference_count--;
+    pthread_mutex_unlock(&config_mutex);
+    if (mailaddr) free(mailaddr);
+}
+
+#define MLFIPRIV    ((struct mlfiPriv *) smfi_getpriv(ctx))
+
+
+////////////////////////////////////////////////
+// syslog a message
+//
+static void my_syslog(char *text);
+static void my_syslog(char *text) {
+    pthread_mutex_lock(&syslog_mutex);
+        openlog("dnsbl", LOG_PID, LOG_MAIL);
+        syslog(LOG_NOTICE, "%s", text);
+        closelog();
+    pthread_mutex_unlock(&syslog_mutex);
+}
+
+
+////////////////////////////////////////////////
+// register a global string
+//
+static char* register_string(char *name);
+static char* register_string(char *name) {
+    string_set::iterator i = all_strings.find(name);
+    if (i != all_strings.end()) return *i;
+    char *x = strdup(name);
+    all_strings.insert(x);
+    return x;
+}
+
+
+static char* next_token(char *delim);
+static char* next_token(char *delim) {
+    char *name = strtok(NULL, delim);
+    if (!name) return name;
+    return register_string(name);
+}
+
+
+////////////////////////////////////////////////
+// lookup an email address in the env_from or env_to maps
+//
+static char* lookup1(char *email, string_map map);
+static char* lookup1(char *email, string_map map) {
+    string_map::iterator i = map.find(email);
+    if (i != map.end()) return (*i).second;
+    char *x = strchr(email, '@');
+    if (!x) return DEFAULT;
+    x++;
+    i = map.find(x);
+    if (i != map.end()) return (*i).second;
+    return DEFAULT;
+}
+
+
+////////////////////////////////////////////////
+// lookup an email address in the env_from or env_to maps
+// this email address is passed in from sendmail, and will
+// always be enclosed in <>. It may have mixed case, just
+// as the mail client sent it.
+//
+static char* lookup(char* email, string_map map);
+static char* lookup(char* email, string_map map) {
+    int n = strlen(email)-2;
+    if (n < 1) return DEFAULT;  // malformed
+    char *key = strdup(email+1);
+    key[n] = '\0';
+    for (int i=0; i<n; i++) key[i] = tolower(key[i]);
+    char *rc = lookup1(key, map);
+    free(key);
+    return rc;
+}
+
+
+////////////////////////////////////////////////
+//  find the dnsbl with a specific name
+//
+static DNSBLP find_dnsbl(CONFIG &dc, char *name);
+static DNSBLP find_dnsbl(CONFIG &dc, char *name) {
+    dnsblp_map::iterator i = dc.dnsbls.find(name);
+    if (i == dc.dnsbls.end()) return NULL;
+    return (*i).second;
+}
+
+
+////////////////////////////////////////////////
+//  find the dnsbll with a specific name
+//
+static DNSBLLP find_dnsbll(CONFIG &dc, char *name);
+static DNSBLLP find_dnsbll(CONFIG &dc, char *name) {
+    dnsbllp_map::iterator i = dc.dnsblls.find(name);
+    if (i == dc.dnsblls.end()) return NULL;
+    return (*i).second;
+}
+
+
+////////////////////////////////////////////////
+//  find the envfrom map with a specific name
+//
+static string_map* find_from_map(CONFIG &dc, char *name);
+static string_map* find_from_map(CONFIG &dc, char *name) {
+    from_map::iterator i = dc.env_from.find(name);
+    if (i == dc.env_from.end()) return NULL;
+    return (*i).second;
+}
+
+
+static string_map& really_find_from_map(CONFIG &dc, char *name);
+static string_map& really_find_from_map(CONFIG &dc, char *name) {
+    string_map *sm = find_from_map(dc, name);
+    if (!sm) {
+        sm = new string_map;
+        dc.env_from[name] = sm;
+    }
+    return *sm;
+}
+
+
+////////////////////////////////////////////////
+//  check a single dnsbl - we don't try very hard, just
+//  using the default resolver retry settings. If we cannot
+//  get an answer, we just accept the mail. The caller
+//  must ensure thread safety.
+//
+static status check_single(int ip, DNSBL &bl);
+static status check_single(int ip, DNSBL &bl) {
+    // make a dns question
+    const u_char *src = (const u_char *)&ip;
+    if (src[0] == 127) return oksofar;  // don't do dns lookups on localhost
+    char question[NS_MAXDNAME];
+    snprintf(question, sizeof(question), "%u.%u.%u.%u.%s.", src[3], src[2], src[1], src[0], bl.suffix);
+    // ask the question
+    u_char answer[NS_PACKETSZ];
+    int length = res_search(question, ns_c_in, ns_t_a, answer, sizeof(answer));
+    if (length < 0) return oksofar;     // error in getting answer
+    // parse the answer
+    ns_msg handle;
+    ns_rr  rr;
+    if (ns_initparse(answer, length, &handle) != 0) return oksofar;
+    int rrnum = 0;
+    while (ns_parserr(&handle, ns_s_an, rrnum++, &rr) == 0) {
+        if (ns_rr_type(rr) == ns_t_a) {
+            // we see an A record, implies blacklisted ip address
+            return reject;
+        }
+    }
+    return oksofar;
+}
+
+
+////////////////////////////////////////////////
+//  check the dnsbls specified for this recipient
+//
+static status check_dnsbl(mlfiPriv &priv, DNSBLLP dnsbllp, DNSBLP &rejectlist);
+static status check_dnsbl(mlfiPriv &priv, DNSBLLP dnsbllp, DNSBLP &rejectlist) {
+    if (priv.authenticated) return oksofar;
+    if (!dnsbllp)           return oksofar;
+    DNSBLL &dnsbll = *dnsbllp;
+    for (DNSBLL::iterator i=dnsbll.begin(); i!=dnsbll.end(); i++) {
+        DNSBLP dp = *i;     // non null by construction
+        status st;
+        map<DNSBLP, status>::iterator f = priv.checked.find(dp);
+        if (f == priv.checked.end()) {
+            // have not checked this list yet
+            pthread_mutex_lock(&resolve_mutex);
+                st = check_single(priv.ip, *dp);
+            pthread_mutex_unlock(&resolve_mutex);
+            rejectlist = dp;
+            priv.checked[dp] = st;
+        }
+        else {
+            st = (*f).second;
+            rejectlist = (*f).first;
+        }
+        if (st == reject) return st;
+    }
+    return oksofar;
+}
+
+
+////////////////////////////////////////////////
+// start of sendmail milter interfaces
+//
+sfsistat mlfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr)
+{
+    // allocate some private memory
+    mlfiPriv *priv = new mlfiPriv;
+    if (hostaddr->sa_family == AF_INET) {
+        priv->ip = ((struct sockaddr_in *)hostaddr)->sin_addr.s_addr;
+    }
+
+    // save the private data
+    smfi_setpriv(ctx, (void*)priv);
+
+    // continue processing
+    return SMFIS_CONTINUE;
+}
+
+sfsistat mlfi_envfrom(SMFICTX *ctx, char **from)
+{
+    mlfiPriv &priv = *MLFIPRIV;
+    priv.mailaddr      = strdup(from[0]);
+    priv.authenticated = (smfi_getsymval(ctx, "{auth_authen}") != NULL);
+    return SMFIS_CONTINUE;
+}
+
+sfsistat mlfi_envrcpt(SMFICTX *ctx, char **rcpt)
+{
+    DNSBLP rejectlist = NULL;   // list that caused the reject
+    status st = oksofar;
+    mlfiPriv &priv = *MLFIPRIV;
+    CONFIG &dc = *priv.pc;
+    char *rcptaddr = rcpt[0];
+    char *dnsname  = lookup(rcptaddr, dc.env_to_dnsbll);
+    char *fromname = lookup(rcptaddr, dc.env_to_chkfrom);
+    if ((strcmp(dnsname,  BLACK) == 0) ||
+        (strcmp(fromname, BLACK) == 0)) {
+        st = black; // two options to blacklist this recipient
+    }
+    else if (strcmp(fromname, WHITE) == 0) {
+        st = white;
+    }
+    else {
+        // check an env_from map
+        string_map *sm = find_from_map(dc, fromname);
+        if (sm != NULL) {
+            fromname = lookup(priv.mailaddr, *sm);  // returns default if name not in map
+            if (strcmp(fromname, BLACK) == 0) {
+                st = black; // blacklist this envelope from value
+            }
+            if (strcmp(fromname, WHITE) == 0) {
+                st = white; // blacklist this envelope from value
+            }
+        }
+    }
+    if ((st == oksofar) && (strcmp(dnsname, WHITE) != 0)) {
+        // check dns lists
+        st = check_dnsbl(priv, find_dnsbll(dc, dnsname), rejectlist);
+    }
+
+    if (st == reject) {
+        // reject the recipient based on some dnsbl
+        char adr[sizeof "255.255.255.255"];
+        adr[0] = '\0';
+        const char *rc = inet_ntop(AF_INET, (const u_char *)&priv.ip, adr, sizeof(adr));
+        char buf[2000];
+        snprintf(buf, sizeof(buf), rejectlist->message, adr, adr);
+        smfi_setreply(ctx, "550", "5.7.1", buf);
+        return SMFIS_REJECT;
+    }
+    else if (st == black) {
+        // reject the recipient based on blacklisting either from or to
+        smfi_setreply(ctx, "550", "5.7.1", "no such user");
+        return SMFIS_REJECT;
+    }
+    else {
+        // accept the recipient
+        return SMFIS_CONTINUE;
+    }
+}
+
+sfsistat mlfi_eom_or_abort(SMFICTX *ctx)
+{
+    mlfiPriv &priv = *MLFIPRIV;
+    if (priv.mailaddr) {
+        free(priv.mailaddr);
+        priv.mailaddr = NULL;
+    }
+    return SMFIS_CONTINUE;
+}
+
+sfsistat mlfi_close(SMFICTX *ctx)
+{
+    mlfiPriv *priv = MLFIPRIV;
+    if (!priv) return SMFIS_CONTINUE;
+    delete priv;
+    smfi_setpriv(ctx, NULL);
+    return SMFIS_CONTINUE;
+}
+
+struct smfiDesc smfilter =
+{
+    "DNSBL",            // filter name
+    SMFI_VERSION,       // version code -- do not change
+    SMFIF_DELRCPT,      // flags
+    mlfi_connect,       // connection info filter
+    NULL,               // SMTP HELO command filter
+    mlfi_envfrom,       // envelope sender filter
+    mlfi_envrcpt,       // envelope recipient filter
+    NULL,               // header filter
+    NULL,               // end of header
+    NULL,               // body block filter
+    mlfi_eom_or_abort,  // end of message
+    mlfi_eom_or_abort,  // message aborted
+    mlfi_close,         // connection cleanup
+};
+
+
+static void dumpit(char *name, string_map map);
+static void dumpit(char *name, string_map map) {
+    fprintf(stderr, "\n");
+    for (string_map::iterator i=map.begin(); i!=map.end(); i++) {
+        fprintf(stderr, "%s %s->%s\n", name, (*i).first, (*i).second);
+    }
+}
+
+
+static void dumpit(from_map map);
+static void dumpit(from_map map) {
+    fprintf(stderr, "\n");
+    for (from_map::iterator i=map.begin(); i!=map.end(); i++) {
+        fprintf(stderr, "envfrom map %s\n", (*i).first);
+        string_map *sm = (*i).second;
+        dumpit("envelope from", *sm);
+    }
+}
+
+
+static void dumpit();
+static void dumpit() {
+    CONFIG &dc = *config;
+    fprintf(stderr, "dnsbls\n");
+    for (dnsblp_map::iterator i=dc.dnsbls.begin(); i!=dc.dnsbls.end(); i++) {
+        fprintf(stderr, "%s %s %s\n", (*i).first, (*i).second->suffix, (*i).second->message);
+    }
+    fprintf(stderr, "dnsbl_lists\n");
+    for (dnsbllp_map::iterator i=dc.dnsblls.begin(); i!=dc.dnsblls.end(); i++) {
+        char *name = (*i).first;
+        DNSBLL &dl = *((*i).second);
+        fprintf(stderr, "%s", name);
+        for (DNSBLL::iterator j=dl.begin(); j!=dl.end(); j++) {
+            DNSBL &d = **j;
+            fprintf(stderr, " %s", d.suffix);
+        }
+        fprintf(stderr, "\n");
+    }
+}
+
+
+////////////////////////////////////////////////
+//  load a single config file
+//
+static void load_conf(CONFIG &dc, char *fn);
+static void load_conf(CONFIG &dc, char *fn) {
+    dc.config_files.push_back(fn);
+    map<char*, int, ltstr> commands;
+    enum {dummy, dnsbl, dnsbll, envfrom, envto, include};
+    commands["dnsbl"     ] = dnsbl;
+    commands["dnsbl_list"] = dnsbll;
+    commands["env_from"  ] = envfrom;
+    commands["env_to"    ] = envto;
+    commands["include"   ] = include;
+    const int LINE_SIZE = 2000;
+    ifstream is(fn);
+    if (is.fail()) return;
+    char line[LINE_SIZE];
+    char orig[LINE_SIZE];
+    char *delim = " \t";
+    int curline = 0;
+    while (!is.eof()) {
+        is.getline(line, LINE_SIZE);
+        snprintf(orig, sizeof(orig), "%s", line);
+        curline++;
+        int n = strlen(line);
+        for (int i=0; i<n; i++) line[i] = tolower(line[i]);
+        char *cmd = strtok(line, delim);
+        if (cmd && (cmd[0] != '#') && (cmd[0] != '\0')) {
+            // have a decent command
+            bool processed = false;
+            switch (commands[cmd]) {
+                case dnsbl: {
+                    // have a new dnsbl to use
+                    char *name = next_token(delim);
+                    if (!name) break;                           // no name name
+                    if (find_dnsbl(dc, name)) break;            // duplicate entry
+                    char *suff = strtok(NULL, delim);
+                    if (!suff) break;                           // no dns suffic
+                    char *msg = suff + strlen(suff);
+                    if ((msg - line) >= strlen(orig)) break;    // line ended with the dns suffix
+                    msg  = strchr(msg+1, '\'');
+                    if (!msg) break;                            // no reply message template
+                    msg++; // move over the leading '
+                    if ((msg - line) >= strlen(orig)) break;    // line ended with the leading quote
+                    char *last = strchr(msg, '\'');
+                    if (!last) break;                           // no trailing quote
+                    *last = '\0';                               // make it a null terminator
+                    dc.dnsbls[name] = new DNSBL(register_string(suff), register_string(msg));
+                    processed = true;
+                    } break;
+
+                case dnsbll: {
+                    // define a new combination of dnsbls
+                    char *name = next_token(delim);
+                    if (!name) break;
+                    if (find_dnsbll(dc, name)) break;               // duplicate entry
+                    char *list = next_token(delim);
+                    if (!list || (*list == '\0') || (*list == '#')) break;
+                    DNSBLLP d = new DNSBLL;
+                    DNSBLP p = find_dnsbl(dc, list);
+                    if (p) d->push_back(p);
+                    while (true) {
+                        list = next_token(delim);
+                        if (!list || (*list == '\0') || (*list == '#')) break;
+                        DNSBLP p = find_dnsbl(dc, list);
+                        if (p) d->push_back(p);
+                    }
+                    dc.dnsblls[name] = d;
+                    processed = true;
+                    } break;
+
+                case envfrom: {
+                    // add an entry into the named string_map
+                    char *name = next_token(delim);
+                    if (!name) break;
+                    char *from = next_token(delim);
+                    if (!from) break;
+                    char *list = next_token(delim);
+                    if (!list) break;
+                    if ((strcmp(list, WHITE) == 0) ||
+                        (strcmp(list, BLACK) == 0)) {
+                        string_map &fm = really_find_from_map(dc, name);
+                        fm[from] = list;
+                        processed = true;
+                    }
+                    else {
+                        // list may be the name of a previously defined from_map
+                        string_map *m = find_from_map(dc, list);
+                        if (m && (strcmp(list,name) != 0)) {
+                            string_map &pm = *m;
+                            string_map &fm = really_find_from_map(dc, name);
+                            fm.insert(pm.begin(), pm.end());
+                            processed = true;
+                        }
+                    }
+                    } break;
+
+                case envto: {
+                    // define the dnsbl_list and env_from maps to use for this recipient
+                    char *to   = next_token(delim);
+                    if (!to) break;
+                    char *list = next_token(delim);
+                    if (!list) break;
+                    char *from = next_token(delim);
+                    if (!from) break;
+                    dc.env_to_dnsbll[to]  = list;
+                    dc.env_to_chkfrom[to] = from;
+                    processed = true;
+                    } break;
+
+                case include: {
+                    char *fn = next_token(delim);
+                    if (fn) {
+                        bool ok = true;
+                        for (string_list::iterator i=dc.config_files.begin(); i!=dc.config_files.end(); i++) {
+                            char *f = *i;
+                            if (strcmp(f, fn) == 0) {
+                                my_syslog("recursive include file detected");
+                                ok = false;
+                                break;
+                            }
+                        }
+                        if (ok) {
+                            load_conf(dc, fn);
+                            processed = true;
+                        }
+                    }
+                    } break;
+
+                default: {
+                    } break;
+            }
+            if (!processed) {
+                pthread_mutex_lock(&syslog_mutex);
+                    openlog("dnsbl", LOG_PID, LOG_MAIL);
+                    syslog(LOG_ERR, "ignoring file %s line %d : %s\n", fn, curline, orig);
+                    closelog();
+                pthread_mutex_unlock(&syslog_mutex);
+            }
+        }
+    }
+    is.close();
+}
+
+
+////////////////////////////////////////////////
+//  reload the config
+//
+static CONFIG* new_conf();
+static CONFIG* new_conf() {
+    my_syslog("loading new configuration");
+    CONFIG *newc = new CONFIG;
+    load_conf(*newc, "dnsbl.conf");
+    newc->load_time = time(NULL);
+    return newc;
+}
+
+
+////////////////////////////////////////////////
+//  thread to watch the old config files for changes
+//  and reload when needed. we also cleanup old
+//  configs whose reference count has gone to zero.
+//
+static void* config_loader(void *arg);
+static void* config_loader(void *arg) {
+    typedef set<CONFIG *> configp_set;
+    configp_set old_configs;
+    while (true) {
+        sleep(180);  // look for modifications every 3 minutes
+        CONFIG &dc = *config;
+        time_t then = dc.load_time;
+        struct stat st;
+        bool reload = false;
+        for (string_list::iterator i=dc.config_files.begin(); i!=dc.config_files.end(); i++) {
+            char *fn = *i;
+            if (stat(fn, &st))           reload = true; // file disappeared
+            else if (st.st_mtime > then) reload = true; // file modified
+            if (reload) break;
+        }
+        if (reload) {
+            CONFIG *newc = new_conf();
+            // replace the global config pointer
+            pthread_mutex_lock(&config_mutex);
+                CONFIG *old = config;
+                config = newc;
+            pthread_mutex_unlock(&config_mutex);
+         // dumpit(env_from);
+         // dumpit("envelope to dnsbl", env_to_dnsbl);
+         // dumpit("envelope to check from", env_to_chkfrom);
+         // dumpit();
+            if (old) old_configs.insert(old);
+        }
+        // now look for old configs with zero ref counts
+        for (configp_set::iterator i=old_configs.begin(); i!=old_configs.end(); ) {
+            CONFIG *old = *i;
+            if (!old->reference_count) {
+                delete old; // destructor does all the work
+                old_configs.erase(i++);
+            }
+            else i++;
+        }
+    }
+}
+
+
+static void usage(char *prog);
+static void usage(char *prog)
+{
+    fprintf(stderr, "Usage: %s  -p socket-addr [-t timeout]\n", prog);
+    fprintf(stderr, "where socket-addr is for the connection to sendmail and should be one of\n");
+    fprintf(stderr, "   inet:port@local-ip-address\n");
+    fprintf(stderr, "   local:local-domain-socket-file-name\n");
+}
+
+
+int main(int argc, char**argv)
+{
+    bool setconn  = FALSE;
+    int c;
+    const char *args = "p:s:h";
+    extern char *optarg;
+
+    // Process command line options
+    while ((c = getopt(argc, argv, args)) != -1) {
+        switch (c) {
+            case 'p':
+                if (optarg == NULL || *optarg == '\0') {
+                    fprintf(stderr, "Illegal conn: %s\n", optarg);
+                    exit(EX_USAGE);
+                }
+                if (smfi_setconn(optarg) == MI_FAILURE) {
+                    fprintf(stderr, "smfi_setconn failed\n");
+                    exit(EX_SOFTWARE);
+                }
+
+                     if (strncasecmp(optarg, "unix:", 5) == 0)  unlink(optarg + 5);
+                else if (strncasecmp(optarg, "local:", 6) == 0) unlink(optarg + 6);
+                setconn = TRUE;
+                break;
+
+            case 't':
+                if (optarg == NULL || *optarg == '\0') {
+                    fprintf(stderr, "Illegal timeout: %s\n", optarg);
+                    exit(EX_USAGE);
+                }
+                if (smfi_settimeout(atoi(optarg)) == MI_FAILURE) {
+                    fprintf(stderr, "smfi_settimeout failed\n");
+                    exit(EX_SOFTWARE);
+                }
+                break;
+
+            case 'h':
+            default:
+                usage(argv[0]);
+                exit(EX_USAGE);
+        }
+    }
+    if (!setconn) {
+        fprintf(stderr, "%s: Missing required -p argument\n", argv[0]);
+        usage(argv[0]);
+        exit(EX_USAGE);
+    }
+    if (smfi_register(smfilter) == MI_FAILURE) {
+        fprintf(stderr, "smfi_register failed\n");
+        exit(EX_UNAVAILABLE);
+    }
+
+    // switch to background mode
+    if (daemon(1,0) < 0) {
+        fprintf(stderr, "daemon() call failed\n");
+        exit(EX_UNAVAILABLE);
+    }
+
+    // initialize the thread sync objects
+    pthread_mutex_init(&config_mutex, 0);
+    pthread_mutex_init(&syslog_mutex, 0);
+    pthread_mutex_init(&resolve_mutex, 0);
+
+    // load the initial config
+    config = new_conf();
+
+    // only create threads after the fork() in daemon
+    pthread_t tid;
+    if (pthread_create(&tid, 0, config_loader, 0))
+        my_syslog("failed to create config loader thread");
+    if (pthread_detach(tid))
+        my_syslog("failed to detach config loader thread");
+
+    // write the pid
+    const char *pidpath = "/var/run/dnsbl.pid";
+    unlink(pidpath);
+    FILE *f = fopen(pidpath, "w");
+    if (f) {
+        fprintf(f, "-%d\n", (u_int)getpgrp());
+        fclose(f);
+    }
+
+    int rc = smfi_main();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/package	Tue Apr 20 20:02:29 2004 -0700
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+target1=/home/httpd/html/510sg/util/dnsbl.tar.gz
+target2=/home/httpd/html/510sg/dnsbl.conf
+
+mv -f dnsbl.conf dnsbl.conf.save
+mv sample.conf dnsbl.conf
+    tar cfvz $target1 dnsbl.cpp dnsbl.conf install.bash LICENSE
+mv dnsbl.conf sample.conf
+mv dnsbl.conf.save dnsbl.conf
+
+cp sample.conf $target2
+
+scp $target1 ns1:$target1
+scp $target2 ns1:$target2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/update	Tue Apr 20 20:02:29 2004 -0700
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+targets="ns1 davd"
+if [ "$1" == "conf" ]; then
+    DST=/var/dnsbl
+    mkdir -p $DST
+    cp *.conf $DST
+    for i in $targets; do
+        scp *.conf $i:$DST
+    done
+else
+    sh install.bash
+    for i in $targets; do
+        DST=/usr/src/rh8/dnsbl
+        scp * $i:$DST
+        ssh $i "cd $DST; sh install.bash"
+    done
+fi
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test.bash	Tue Apr 20 20:02:29 2004 -0700
@@ -0,0 +1,31 @@
+#!/bin/bash
+
+g++ -c dnsbl.cpp
+g++ -o dnsbl dnsbl.o /usr/lib/libresolv.a -lmilter -pthread
+if [ "$1" == "comp" ]; then
+    exit
+fi
+
+# build the test.cf file
+make test.cf
+
+# start the milter
+pid=/var/run/dnsbl.pid
+echo start the milter
+mkdir -p /var/run/dnsbl
+chmod 700 /var/run/dnsbl
+mv -f $pid $pid.save
+rm -f /var/run/dnsbl/dnsbl.sock
+./dnsbl -p local:/var/run/dnsbl/dnsbl.sock &
+sleep 5
+P2=`cat $pid`
+mv -f $pid.save $pid
+echo started dnsbl milter as process $P2
+
+/usr/lib/sendmail -bd -Ctest.cf -Ldnsbl
+sleep 5
+P3=`head -1 /var/run/sm-test.pid`
+echo started sendmail as process $P3
+
+echo eventually "'"kill -KILL $P2 $P3"'"
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test.cf	Tue Apr 20 20:02:29 2004 -0700
@@ -0,0 +1,1901 @@
+#
+# Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
+#	All rights reserved.
+# Copyright (c) 1983, 1995 Eric P. Allman.  All rights reserved.
+# Copyright (c) 1988, 1993
+#	The Regents of the University of California.  All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#
+
+######################################################################
+######################################################################
+#####
+#####		SENDMAIL CONFIGURATION FILE
+#####
+##### built by root@ns.five-ten-sg.com on Wed Apr 14 20:41:48 PDT 2004
+##### in /usr/src/rh8/dnsbl
+##### using /usr/share/sendmail-cf/ as configuration include directory
+#####
+######################################################################
+#####
+#####	DO NOT EDIT THIS FILE!  Only edit the source .mc file.
+#####
+######################################################################
+######################################################################
+
+#####  $Id$  #####
+#####  $Id$  #####
+
+#####  linux setup for Red Hat Linux  #####
+
+#####  $Id$  #####
+
+
+
+#####  $Id$  #####
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#####  $Id$  #####
+
+
+#####  $Id$  #####
+
+#####  $Id$  #####
+
+
+#####  $Id$  #####
+
+
+
+#####  $Id$  #####
+
+
+
+#####  $Id$  #####
+
+
+#####  $Id$  #####
+
+
+#####  $Id$  #####
+
+
+#####  $Id$  #####
+
+
+#####  $Id$  #####
+
+
+#####  $Id$  #####
+
+
+#####  $Id$  #####
+
+
+
+
+
+
+
+
+#####  $Id$  #####
+
+# level 10 config file format
+V10/Berkeley
+
+# override file safeties - setting this option compromises system security,
+# addressing the actual file configuration problem is preferred
+# need to set this before any file actions are encountered in the cf file
+#O DontBlameSendmail=safe
+
+# default LDAP map specification
+# need to set this now before any LDAP maps are defined
+#O LDAPDefaultSpec=-h localhost
+
+##################
+#   local info   #
+##################
+
+# my LDAP cluster
+# need to set this before any LDAP lookups are done (including classes)
+#D{sendmailMTACluster}$m
+
+Cwlocalhost
+# file containing names of hosts for which we receive email
+Fw/etc/mail/sendmail.cw
+
+# my official domain name
+# ... define this only if sendmail cannot automatically determine your domain
+#Dj$w.Foo.COM
+
+CP.
+
+# "Smart" relay host (may be null)
+DS
+
+
+# operators that cannot be in local usernames (i.e., network indicators)
+CO @ % !
+
+# a class with just dot (for identifying canonical names)
+C..
+
+# a class with just a left bracket (for identifying domain literals)
+C[[
+
+# access_db acceptance class
+C{Accept}OK RELAY
+
+
+# Resolve map (to check if a host exists in check_mail)
+Kresolve host -a<OKR> -T<TEMP>
+C{ResOk}OKR
+
+
+# Hosts for which relaying is permitted ($=R)
+FR/etc/mail/relay-domains
+
+# arithmetic map
+Karith arith
+# macro storage map
+Kmacro macro
+# possible values for TLS_connection in access map
+C{tls}VERIFY ENCR
+
+
+
+
+
+# dequoting map
+Kdequote dequote
+
+# class E: names that should be exposed as from this host, even if we masquerade
+# class L: names that should be delivered locally, even if we have a relay
+# class M: domains that should be converted to $M
+# class N: domains that should not be converted to $M
+#CL root
+F{VirtHost}/etc/mail/virtual-host-domains
+
+C{TrustAuthMech}LOGIN PLAIN
+CR$={VirtHost}
+
+
+
+# my name for error messages
+DnMAILER-DAEMON
+
+
+CPREDIRECT
+
+# Access list database (for spam stomping)
+Kaccess hash -T<TMPF> /etc/mail/access.db
+
+# Mailer table (overriding domains)
+Kmailertable hash /etc/mail/mailertable.db
+
+# Virtual user table (maps incoming users)
+Kvirtuser hash /etc/mail/virtusertable.db
+
+# Generics table (mapping outgoing addresses)
+Kgenerics hash /etc/mail/genericstable.db
+
+# Configuration version number
+DZ8.12.8
+
+
+###############
+#   Options   #
+###############
+
+# strip message body to 7 bits on input?
+O SevenBitInput=False
+
+# 8-bit data handling
+#O EightBitMode=pass8
+
+# wait for alias file rebuild (default units: minutes)
+O AliasWait=10
+
+# location of alias file
+O AliasFile=/etc/mail/aliases
+
+# minimum number of free blocks on filesystem
+O MinFreeBlocks=100
+
+# maximum message size
+O MaxMessageSize=30000000
+
+# substitution for space (blank) characters
+O BlankSub=.
+
+# avoid connecting to "expensive" mailers on initial submission?
+O HoldExpensive=False
+
+# checkpoint queue runs after every N successful deliveries
+#O CheckpointInterval=10
+
+# default delivery mode
+O DeliveryMode=background
+
+# error message header/file
+#O ErrorHeader=/etc/mail/error-header
+
+# error mode
+#O ErrorMode=print
+
+# save Unix-style "From_" lines at top of header?
+#O SaveFromLine=False
+
+# queue file mode (qf files)
+#O QueueFileMode=0600
+
+# temporary file mode
+O TempFileMode=0600
+
+# match recipients against GECOS field?
+#O MatchGECOS=False
+
+# maximum hop count
+#O MaxHopCount=25
+
+# location of help file
+O HelpFile=/etc/mail/helpfile
+
+# ignore dots as terminators in incoming messages?
+#O IgnoreDots=False
+
+# name resolver options
+#O ResolverOptions=+AAONLY
+
+# deliver MIME-encapsulated error messages?
+O SendMimeErrors=True
+
+# Forward file search path
+O ForwardPath=$z/.forward.$w:$z/.forward
+
+# open connection cache size
+O ConnectionCacheSize=2
+
+# open connection cache timeout
+O ConnectionCacheTimeout=5m
+
+# persistent host status directory
+#O HostStatusDirectory=.hoststat
+
+# single thread deliveries (requires HostStatusDirectory)?
+#O SingleThreadDelivery=False
+
+# use Errors-To: header?
+O UseErrorsTo=False
+
+# log level
+O LogLevel=20
+
+# send to me too, even in an alias expansion?
+O MeToo=true
+
+# verify RHS in newaliases?
+O CheckAliases=False
+
+# default messages to old style headers if no special punctuation?
+O OldStyleHeaders=True
+
+# SMTP daemon options
+
+O DaemonPortOptions=port=26
+
+# SMTP client options
+#O ClientPortOptions=Family=inet, Address=0.0.0.0
+
+# Modifiers to define {daemon_flags} for direct submissions
+#O DirectSubmissionModifiers
+
+# Use as mail submission program? See sendmail/SECURITY
+#O UseMSP
+
+# privacy flags
+O PrivacyOptions=goaway,nobodyreturn,noreceipts
+
+# who (if anyone) should get extra copies of error messages
+#O PostmasterCopy=Postmaster
+
+# slope of queue-only function
+#O QueueFactor=600000
+
+# limit on number of concurrent queue runners
+#O MaxQueueChildren
+
+# maximum number of queue-runners per queue-grouping with multiple queues
+#O MaxRunnersPerQueue=1
+
+# priority of queue runners (nice(3))
+#O NiceQueueRun
+
+# shall we sort the queue by hostname first?
+#O QueueSortOrder=priority
+
+# minimum time in queue before retry
+#O MinQueueAge=30m
+
+# how many jobs can you process in the queue?
+#O MaxQueueRunSize=10000
+
+# perform initial split of envelope without checking MX records
+#O FastSplit=1
+
+# queue directory
+O QueueDirectory=/var/spool/mqueue
+
+# key for shared memory; 0 to turn off
+#O SharedMemoryKey=0
+
+
+
+# timeouts (many of these)
+#O Timeout.initial=5m
+O Timeout.connect=1m
+#O Timeout.aconnect=0s
+#O Timeout.iconnect=5m
+#O Timeout.helo=5m
+#O Timeout.mail=10m
+#O Timeout.rcpt=1h
+#O Timeout.datainit=5m
+#O Timeout.datablock=1h
+#O Timeout.datafinal=1h
+#O Timeout.rset=5m
+#O Timeout.quit=2m
+#O Timeout.misc=2m
+#O Timeout.command=1h
+O Timeout.ident=0
+#O Timeout.fileopen=60s
+#O Timeout.control=2m
+O Timeout.queuereturn=5d
+#O Timeout.queuereturn.normal=5d
+#O Timeout.queuereturn.urgent=2d
+#O Timeout.queuereturn.non-urgent=7d
+O Timeout.queuewarn=4h
+#O Timeout.queuewarn.normal=4h
+#O Timeout.queuewarn.urgent=1h
+#O Timeout.queuewarn.non-urgent=12h
+#O Timeout.hoststatus=30m
+#O Timeout.resolver.retrans=5s
+#O Timeout.resolver.retrans.first=5s
+#O Timeout.resolver.retrans.normal=5s
+#O Timeout.resolver.retry=4
+#O Timeout.resolver.retry.first=4
+#O Timeout.resolver.retry.normal=4
+#O Timeout.lhlo=2m
+#O Timeout.auth=10m
+#O Timeout.starttls=1h
+
+# time for DeliverBy; extension disabled if less than 0
+#O DeliverByMin=0
+
+# should we not prune routes in route-addr syntax addresses?
+#O DontPruneRoutes=False
+
+# queue up everything before forking?
+O SuperSafe=True
+
+# status file
+O StatusFile=/usr/src/rh8/dnsbl/sendmail.st
+
+# time zone handling:
+#  if undefined, use system default
+#  if defined but null, use TZ envariable passed in
+#  if defined and non-null, use that info
+#O TimeZoneSpec=
+
+# default UID (can be username or userid:groupid)
+O DefaultUser=8:12
+
+# list of locations of user database file (null means no lookup)
+#O UserDatabaseSpec=/etc/mail/userdb
+
+# fallback MX host
+#O FallbackMXhost=fall.back.host.net
+
+# if we are the best MX host for a site, try it directly instead of config err
+#O TryNullMXList=False
+
+# load average at which we just queue messages
+O QueueLA=12
+
+# load average at which we refuse connections
+O RefuseLA=8
+
+# load average at which we delay connections; 0 means no limit
+#O DelayLA=0
+
+# maximum number of children we allow at one time
+O MaxDaemonChildren=20
+
+# maximum number of new connections per second
+O ConnectionRateThrottle=1
+
+# work recipient factor
+#O RecipientFactor=30000
+
+# deliver each queued job in a separate process?
+#O ForkEachJob=False
+
+# work class factor
+#O ClassFactor=1800
+
+# work time factor
+#O RetryFactor=90000
+
+# default character set
+#O DefaultCharSet=iso-8859-1
+
+# service switch file (name hardwired on Solaris, Ultrix, OSF/1, others)
+#O ServiceSwitchFile=/etc/mail/service.switch
+
+# hosts file (normally /etc/hosts)
+#O HostsFile=/etc/hosts
+
+# dialup line delay on connection failure
+#O DialDelay=10s
+
+# action to take if there are no recipients in the message
+#O NoRecipientAction=add-to-undisclosed
+
+# chrooted environment for writing to files
+#O SafeFileEnvironment=/arch
+
+# are colons OK in addresses?
+#O ColonOkInAddr=True
+
+# shall I avoid expanding CNAMEs (violates protocols)?
+#O DontExpandCnames=False
+
+# SMTP initial login message (old $e macro)
+O SmtpGreetingMessage=$j Sendmail $v/$Z; $b
+
+# UNIX initial From header format (old $l macro)
+O UnixFromLine=From $g $d
+
+# From: lines that have embedded newlines are unwrapped onto one line
+#O SingleLineFromHeader=False
+
+# Allow HELO SMTP command that does not include a host name
+#O AllowBogusHELO=False
+
+# Characters to be quoted in a full name phrase (@,;:\()[] are automatic)
+#O MustQuoteChars=.
+
+# delimiter (operator) characters (old $o macro)
+O OperatorChars=.:%@!^/[]+
+
+# shall I avoid calling initgroups(3) because of high NIS costs?
+#O DontInitGroups=False
+
+# are group-writable :include: and .forward files (un)trustworthy?
+# True (the default) means they are not trustworthy.
+#O UnsafeGroupWrites=True
+
+
+# where do errors that occur when sending errors get sent?
+O DoubleBounceAddress
+
+# where to save bounces if all else fails
+#O DeadLetterDrop=/var/tmp/dead.letter
+
+# what user id do we assume for the majority of the processing?
+#O RunAsUser=sendmail
+
+# maximum number of recipients per SMTP envelope
+#O MaxRecipientsPerMessage=100
+
+# limit the rate recipients per SMTP envelope are accepted
+# once the threshold number of recipients have been rejected
+O BadRcptThrottle=2
+
+# shall we get local names from our installed interfaces?
+O DontProbeInterfaces=true
+
+# Return-Receipt-To: header implies DSN request
+#O RrtImpliesDsn=False
+
+# override connection address (for testing)
+#O ConnectOnlyTo=0.0.0.0
+
+# Trusted user for file ownership and starting the daemon
+#O TrustedUser=root
+
+# Control socket for daemon management
+#O ControlSocketName=/var/spool/mqueue/.control
+
+# Maximum MIME header length to protect MUAs
+#O MaxMimeHeaderLength=0/0
+
+# Maximum length of the sum of all headers
+#O MaxHeadersLength=32768
+
+# Maximum depth of alias recursion
+#O MaxAliasRecursion=10
+
+# location of pid file
+O PidFile=/var/run/sm-test.pid
+
+# Prefix string for the process title shown on 'ps' listings
+#O ProcessTitlePrefix=prefix
+
+# Data file (df) memory-buffer file maximum size
+#O DataFileBufferSize=4096
+
+# Transcript file (xf) memory-buffer file maximum size
+#O XscriptFileBufferSize=4096
+
+# lookup type to find information about local mailboxes
+#O MailboxDatabase=pw
+
+# list of authentication mechanisms
+O AuthMechanisms=LOGIN PLAIN
+
+# default authentication information for outgoing connections
+#O DefaultAuthInfo=/etc/mail/default-auth-info
+
+# SMTP AUTH flags
+O AuthOptions=A
+
+# SMTP AUTH maximum encryption strength
+#O AuthMaxBits
+
+# SMTP STARTTLS server options
+#O TLSSrvOptions
+
+# Input mail filters
+O InputMailFilters=dnsbl
+
+# Milter options
+#O Milter.LogLevel
+O Milter.macros.connect=j, _, {daemon_name}, {if_name}, {if_addr}
+O Milter.macros.helo={tls_version}, {cipher}, {cipher_bits}, {cert_subject}, {cert_issuer}
+O Milter.macros.envfrom=i, {auth_type}, {auth_authen}, {auth_ssf}, {auth_author}, {mail_mailer}, {mail_host}, {mail_addr}
+O Milter.macros.envrcpt={rcpt_mailer}, {rcpt_host}, {rcpt_addr}
+
+# CA directory
+#O CACertPath
+# CA file
+#O CACertFile
+# Server Cert
+#O ServerCertFile
+# Server private key
+#O ServerKeyFile
+# Client Cert
+#O ClientCertFile
+# Client private key
+#O ClientKeyFile
+# DHParameters (only required if DSA/DH is used)
+#O DHParameters
+# Random data source (required for systems without /dev/urandom under OpenSSL)
+#O RandFile
+
+############################
+# QUEUE GROUP DEFINITIONS  #
+############################
+
+
+###########################
+#   Message precedences   #
+###########################
+
+Pfirst-class=0
+Pspecial-delivery=100
+Plist=-30
+Pbulk=-60
+Pjunk=-100
+
+#####################
+#   Trusted users   #
+#####################
+
+# this is equivalent to setting class "t"
+Ft/etc/mail/sendmail.ct
+Troot
+Tdaemon
+Tuucp
+
+#########################
+#   Format of headers   #
+#########################
+
+H?P?Return-Path: <$g>
+HReceived: $?sfrom $s $.$?_($?s$|from $.$_)
+	$.$?{auth_type}(authenticated$?{auth_ssf} bits=${auth_ssf}$.)
+	$.by $j ($v/$Z)$?r with $r$. id $i$?{tls_version}
+	(version=${tls_version} cipher=${cipher} bits=${cipher_bits} verify=${verify})$.$?u
+	for $u; $|;
+	$.$b
+H?D?Resent-Date: $a
+H?D?Date: $a
+H?F?Resent-From: $?x$x <$g>$|$g$.
+H?F?From: $?x$x <$g>$|$g$.
+H?x?Full-Name: $x
+# HPosted-Date: $a
+# H?l?Received-Date: $b
+H?M?Resent-Message-Id: <$t.$i@$j>
+H?M?Message-Id: <$t.$i@$j>
+
+#
+######################################################################
+######################################################################
+#####
+#####			REWRITING RULES
+#####
+######################################################################
+######################################################################
+
+############################################
+###  Ruleset 3 -- Name Canonicalization  ###
+############################################
+Scanonify=3
+
+# handle null input (translate to <@> special case)
+R$@			$@ <@>
+
+# strip group: syntax (not inside angle brackets!) and trailing semicolon
+R$*			$: $1 <@>			mark addresses
+R$* < $* > $* <@>	$: $1 < $2 > $3			unmark <addr>
+R@ $* <@>		$: @ $1				unmark @host:...
+R$* [ IPv6 : $+ ] <@>	$: $1 [ IPv6 : $2 ]		unmark IPv6 addr
+R$* :: $* <@>		$: $1 :: $2			unmark node::addr
+R:include: $* <@>	$: :include: $1			unmark :include:...
+R$* : $* [ $* ]		$: $1 : $2 [ $3 ] <@>		remark if leading colon
+R$* : $* <@>		$: $2				strip colon if marked
+R$* <@>			$: $1				unmark
+R$* ;			   $1				strip trailing semi
+R$* < $+ :; > $*	$@ $2 :; <@>			catch <list:;>
+R$* < $* ; >		   $1 < $2 >			bogus bracketed semi
+
+# null input now results from list:; syntax
+R$@			$@ :; <@>
+
+# strip angle brackets -- note RFC733 heuristic to get innermost item
+R$*			$: < $1 >			housekeeping <>
+R$+ < $* >		   < $2 >			strip excess on left
+R< $* > $+		   < $1 >			strip excess on right
+R<>			$@ < @ >			MAIL FROM:<> case
+R< $+ >			$: $1				remove housekeeping <>
+
+# strip route address <@a,@b,@c:user@d> -> <user@d>
+R@ $+ , $+		$2
+R@ [ $* ] : $+		$2
+R@ $+ : $+		$2
+
+# find focus for list syntax
+R $+ : $* ; @ $+	$@ $>Canonify2 $1 : $2 ; < @ $3 >	list syntax
+R $+ : $* ;		$@ $1 : $2;			list syntax
+
+# find focus for @ syntax addresses
+R$+ @ $+		$: $1 < @ $2 >			focus on domain
+R$+ < $+ @ $+ >		$1 $2 < @ $3 >			move gaze right
+R$+ < @ $+ >		$@ $>Canonify2 $1 < @ $2 >	already canonical
+
+
+# convert old-style addresses to a domain-based address
+R$- ! $+		$@ $>Canonify2 $2 < @ $1 .UUCP >	resolve uucp names
+R$+ . $- ! $+		$@ $>Canonify2 $3 < @ $1 . $2 >		domain uucps
+R$+ ! $+		$@ $>Canonify2 $2 < @ $1 .UUCP >	uucp subdomains
+
+# if we have % signs, take the rightmost one
+R$* % $*		$1 @ $2				First make them all @s.
+R$* @ $* @ $*		$1 % $2 @ $3			Undo all but the last.
+R$* @ $*		$@ $>Canonify2 $1 < @ $2 >	Insert < > and finish
+
+# else we must be a local name
+R$*			$@ $>Canonify2 $1
+
+
+################################################
+###  Ruleset 96 -- bottom half of ruleset 3  ###
+################################################
+
+SCanonify2=96
+
+# handle special cases for local names
+R$* < @ localhost > $*		$: $1 < @ $j . > $2		no domain at all
+R$* < @ localhost . $m > $*	$: $1 < @ $j . > $2		local domain
+R$* < @ localhost . UUCP > $*	$: $1 < @ $j . > $2		.UUCP domain
+
+# check for IPv4/IPv6 domain literal
+R$* < @ [ $+ ] > $*		$: $1 < @@ [ $2 ] > $3		mark [addr]
+R$* < @@ $=w > $*		$: $1 < @ $j . > $3		self-literal
+R$* < @@ $+ > $*		$@ $1 < @ $2 > $3		canon IP addr
+
+
+
+
+
+# if really UUCP, handle it immediately
+
+# try UUCP traffic as a local address
+R$* < @ $+ . UUCP > $*		$: $1 < @ $[ $2 $] . UUCP . > $3
+R$* < @ $+ . . UUCP . > $*	$@ $1 < @ $2 . > $3
+
+# hostnames ending in class P are always canonical
+R$* < @ $* $=P > $*		$: $1 < @ $2 $3 . > $4
+R$* < @ $* $~P > $*		$: $&{daemon_flags} $| $1 < @ $2 $3 > $4
+R$* CC $* $| $* < @ $+.$+ > $*	$: $3 < @ $4.$5 . > $6
+R$* CC $* $| $*			$: $3
+# pass to name server to make hostname canonical
+R$* $| $* < @ $* > $*		$: $2 < @ $[ $3 $] > $4
+R$* $| $*			$: $2
+
+# local host aliases and pseudo-domains are always canonical
+R$* < @ $=w > $*		$: $1 < @ $2 . > $3
+R$* < @ $=M > $*		$: $1 < @ $2 . > $3
+R$* < @ $={VirtHost} > $* 	$: $1 < @ $2 . > $3
+R$* < @ $=G > $* 	$: $1 < @ $2 . > $3
+R$* < @ $* . . > $*		$1 < @ $2 . > $3
+
+
+##################################################
+###  Ruleset 4 -- Final Output Post-rewriting  ###
+##################################################
+Sfinal=4
+
+R$+ :; <@>		$@ $1 :				handle <list:;>
+R$* <@>			$@				handle <> and list:;
+
+# strip trailing dot off possibly canonical name
+R$* < @ $+ . > $*	$1 < @ $2 > $3
+
+# eliminate internal code
+R$* < @ *LOCAL* > $*	$1 < @ $j > $2
+
+# externalize local domain info
+R$* < $+ > $*		$1 $2 $3			defocus
+R@ $+ : @ $+ : $+	@ $1 , @ $2 : $3		<route-addr> canonical
+R@ $*			$@ @ $1				... and exit
+
+# UUCP must always be presented in old form
+R$+ @ $- . UUCP		$2!$1				u@h.UUCP => h!u
+
+# delete duplicate local names
+R$+ % $=w @ $=w		$1 @ $2				u%host@host => u@host
+
+
+
+##############################################################
+###   Ruleset 97 -- recanonicalize and call ruleset zero   ###
+###		   (used for recursive calls)		   ###
+##############################################################
+
+SRecurse=97
+R$*			$: $>canonify $1
+R$*			$@ $>parse $1
+
+
+######################################
+###   Ruleset 0 -- Parse Address   ###
+######################################
+
+Sparse=0
+
+R$*			$: $>Parse0 $1		initial parsing
+R<@>			$#local $: <@>		special case error msgs
+R$*			$: $>ParseLocal $1	handle local hacks
+R$*			$: $>Parse1 $1		final parsing
+
+#
+#  Parse0 -- do initial syntax checking and eliminate local addresses.
+#	This should either return with the (possibly modified) input
+#	or return with a #error mailer.  It should not return with a
+#	#mailer other than the #error mailer.
+#
+
+SParse0
+R<@>			$@ <@>			special case error msgs
+R$* : $* ; <@>		$#error $@ 5.1.3 $: "553 List:; syntax illegal for recipient addresses"
+R@ <@ $* >		< @ $1 >		catch "@@host" bogosity
+R<@ $+>			$#error $@ 5.1.3 $: "553 User address required"
+R$+ <@>			$#error $@ 5.1.3 $: "553 Hostname required"
+R$*			$: <> $1
+R<> $* < @ [ $* ] : $+ > $*	$1 < @ [ $2 ] : $3 > $4
+R<> $* < @ [ $* ] , $+ > $*	$1 < @ [ $2 ] , $3 > $4
+R<> $* < @ [ $* ] $+ > $*	$#error $@ 5.1.2 $: "553 Invalid address"
+R<> $* < @ [ $+ ] > $*		$1 < @ [ $2 ] > $3
+R<> $* <$* : $* > $*	$#error $@ 5.1.3 $: "553 Colon illegal in host name part"
+R<> $*			$1
+R$* < @ . $* > $*	$#error $@ 5.1.2 $: "553 Invalid host name"
+R$* < @ $* .. $* > $*	$#error $@ 5.1.2 $: "553 Invalid host name"
+R$* < @ $* @ > $*	$#error $@ 5.1.2 $: "553 Invalid route address"
+R$* @ $* < @ $* > $*	$#error $@ 5.1.3 $: "553 Invalid route address"
+R$* , $~O $*		$#error $@ 5.1.3 $: "553 Invalid route address"
+
+
+# now delete the local info -- note $=O to find characters that cause forwarding
+R$* < @ > $*		$@ $>Parse0 $>canonify $1	user@ => user
+R< @ $=w . > : $*	$@ $>Parse0 $>canonify $2	@here:... -> ...
+R$- < @ $=w . >		$: $(dequote $1 $) < @ $2 . >	dequote "foo"@here
+R< @ $+ >		$#error $@ 5.1.3 $: "553 User address required"
+R$* $=O $* < @ $=w . >	$@ $>Parse0 $>canonify $1 $2 $3	...@here -> ...
+R$- 			$: $(dequote $1 $) < @ *LOCAL* >	dequote "foo"
+R< @ *LOCAL* >		$#error $@ 5.1.3 $: "553 User address required"
+R$* $=O $* < @ *LOCAL* >
+			$@ $>Parse0 $>canonify $1 $2 $3	...@*LOCAL* -> ...
+R$* < @ *LOCAL* >	$: $1
+
+#
+#  Parse1 -- the bottom half of ruleset 0.
+#
+
+SParse1
+
+# handle numeric address spec
+R$* < @ [ $+ ] > $*	$: $>ParseLocal $1 < @ [ $2 ] > $3	numeric internet spec
+R$* < @ [ $+ ] > $*	$1 < @ [ $2 ] : $S > $3		Add smart host to path
+R$* < @ [ $+ ] : > $*		$#esmtp $@ [$2] $: $1 < @ [$2] > $3	no smarthost: send
+R$* < @ [ $+ ] : $- : $*> $*	$#$3 $@ $4 $: $1 < @ [$2] > $5	smarthost with mailer
+R$* < @ [ $+ ] : $+ > $*	$#esmtp $@ $3 $: $1 < @ [$2] > $4	smarthost without mailer
+
+# handle virtual users
+R$+			$: <!> $1		Mark for lookup
+R<!> $+ < @ $={VirtHost} . > 	$: < $(virtuser $1 @ $2 $@ $1 $: @ $) > $1 < @ $2 . >
+R<!> $+ < @ $=w . > 	$: < $(virtuser $1 @ $2 $@ $1 $: @ $) > $1 < @ $2 . >
+R<@> $+ + $+ < @ $* . >
+			$: < $(virtuser $1 + + @ $3 $@ $1 $@ $2 $@ +$2 $: @ $) > $1 + $2 < @ $3 . >
+R<@> $+ + $* < @ $* . >
+			$: < $(virtuser $1 + * @ $3 $@ $1 $@ $2 $@ +$2 $: @ $) > $1 + $2 < @ $3 . >
+R<@> $+ + $* < @ $* . >
+			$: < $(virtuser $1 @ $3 $@ $1 $@ $2 $@ +$2 $: @ $) > $1 + $2 < @ $3 . >
+R<@> $+ + $+ < @ $+ . >	$: < $(virtuser + + @ $3 $@ $1 $@ $2 $@ +$2 $: @ $) > $1 + $2 < @ $3 . >
+R<@> $+ + $* < @ $+ . >	$: < $(virtuser + * @ $3 $@ $1 $@ $2 $@ +$2 $: @ $) > $1 + $2 < @ $3 . >
+R<@> $+ + $* < @ $+ . >	$: < $(virtuser @ $3 $@ $1 $@ $2 $@ +$2 $: ! $) > $1 + $2 < @ $3 . >
+R<@> $+ < @ $+ . >	$: < $(virtuser @ $2 $@ $1 $: @ $) > $1 < @ $2 . >
+R<@> $+			$: $1
+R<!> $+			$: $1
+R< error : $-.$-.$- : $+ > $* 	$#error $@ $1.$2.$3 $: $4
+R< error : $- $+ > $* 	$#error $@ $(dequote $1 $) $: $2
+R< $+ > $+ < @ $+ >	$: $>Recurse $1
+
+# short circuit local delivery so forwarded email works
+
+
+R$=L < @ $=w . >	$#local $: @ $1			special local names
+R$+ < @ $=w . >		$#local $: $1			regular local name
+
+# not local -- try mailer table lookup
+R$* <@ $+ > $*		$: < $2 > $1 < @ $2 > $3	extract host name
+R< $+ . > $*		$: < $1 > $2			strip trailing dot
+R< $+ > $*		$: < $(mailertable $1 $) > $2	lookup
+R< $~[ : $* > $* 	$>MailerToTriple < $1 : $2 > $3		check -- resolved?
+R< $+ > $*		$: $>Mailertable <$1> $2		try domain
+
+# resolve remotely connected UUCP links (if any)
+
+# resolve fake top level domains by forwarding to other hosts
+
+
+
+# pass names that still have a host to a smarthost (if defined)
+R$* < @ $* > $*		$: $>MailerToTriple < $S > $1 < @ $2 > $3	glue on smarthost name
+
+# deal with other remote names
+R$* < @$* > $*		$#esmtp $@ $2 $: $1 < @ $2 > $3	user@host.domain
+
+# handle locally delivered names
+R$=L			$#local $: @ $1		special local names
+R$+			$#local $: $1			regular local names
+
+###########################################################################
+###   Ruleset 5 -- special rewriting after aliases have been expanded   ###
+###########################################################################
+
+SLocal_localaddr
+Slocaladdr=5
+R$+			$: $1 $| $>"Local_localaddr" $1
+R$+ $| $#ok		$@ $1			no change
+R$+ $| $#$*		$#$2
+R$+ $| $*		$: $1
+
+
+
+
+# deal with plussed users so aliases work nicely
+R$+ + *			$#local $@ $&h $: $1
+R$+ + $*		$#local $@ + $2 $: $1 + *
+
+# prepend an empty "forward host" on the front
+R$+			$: <> $1
+
+
+
+R< > $+			$: < > < $1 <> $&h >		nope, restore +detail
+
+R< > < $+ <> + $* >	$: < > < $1 + $2 >		check whether +detail
+R< > < $+ <> $* >	$: < > < $1 >			else discard
+R< > < $+ + $* > $*	   < > < $1 > + $2 $3		find the user part
+R< > < $+ > + $*	$#local $@ $2 $: @ $1		strip the extra +
+R< > < $+ >		$@ $1				no +detail
+R$+			$: $1 <> $&h			add +detail back in
+
+R$+ <> + $*		$: $1 + $2			check whether +detail
+R$+ <> $*		$: $1				else discard
+R< local : $* > $*	$: $>MailerToTriple < local : $1 > $2	no host extension
+R< error : $* > $*	$: $>MailerToTriple < error : $1 > $2	no host extension
+
+R< $~[ : $+ > $+	$: $>MailerToTriple < $1 : $2 > $3 < @ $2 >
+
+R< $+ > $+		$@ $>MailerToTriple < $1 > $2 < @ $1 >
+
+
+###################################################################
+###  Ruleset 90 -- try domain part of mailertable entry 	###
+###################################################################
+
+SMailertable=90
+R$* <$- . $+ > $*	$: $1$2 < $(mailertable .$3 $@ $1$2 $@ $2 $) > $4
+R$* <$~[ : $* > $*	$>MailerToTriple < $2 : $3 > $4		check -- resolved?
+R$* < . $+ > $* 	$@ $>Mailertable $1 . <$2> $3		no -- strip & try again
+R$* < $* > $*		$: < $(mailertable . $@ $1$2 $) > $3	try "."
+R< $~[ : $* > $*	$>MailerToTriple < $1 : $2 > $3		"." found?
+R< $* > $*		$@ $2				no mailertable match
+
+###################################################################
+###  Ruleset 95 -- canonify mailer:[user@]host syntax to triple	###
+###################################################################
+
+SMailerToTriple=95
+R< > $*				$@ $1			strip off null relay
+R< error : $-.$-.$- : $+ > $* 	$#error $@ $1.$2.$3 $: $4
+R< error : $- $+ > $*		$#error $@ $(dequote $1 $) $: $2
+R< local : $* > $*		$>CanonLocal < $1 > $2
+R< $~[ : $+ @ $+ > $*<$*>$*	$# $1 $@ $3 $: $2<@$3>	use literal user
+R< $~[ : $+ > $*		$# $1 $@ $2 $: $3	try qualified mailer
+R< $=w > $*			$@ $2			delete local host
+R< $+ > $*			$#relay $@ $1 $: $2	use unqualified mailer
+
+###################################################################
+###  Ruleset CanonLocal -- canonify local: syntax		###
+###################################################################
+
+SCanonLocal
+# strip local host from routed addresses
+R< $* > < @ $+ > : $+		$@ $>Recurse $3
+R< $* > $+ $=O $+ < @ $+ >	$@ $>Recurse $2 $3 $4
+
+# strip trailing dot from any host name that may appear
+R< $* > $* < @ $* . >		$: < $1 > $2 < @ $3 >
+
+# handle local: syntax -- use old user, either with or without host
+R< > $* < @ $* > $*		$#local $@ $1@$2 $: $1
+R< > $+				$#local $@ $1    $: $1
+
+# handle local:user@host syntax -- ignore host part
+R< $+ @ $+ > $* < @ $* >	$: < $1 > $3 < @ $4 >
+
+# handle local:user syntax
+R< $+ > $* <@ $* > $*		$#local $@ $2@$3 $: $1
+R< $+ > $* 			$#local $@ $2    $: $1
+
+###################################################################
+###  Ruleset 93 -- convert header names to masqueraded form	###
+###################################################################
+
+SMasqHdr=93
+
+# handle generics database
+R$+ < @ $=G . >	$: < $1@$2 > $1 < @ $2 . > @	mark
+R$+ < @ *LOCAL* >	$: < $1@$j > $1 < @ *LOCAL* > @	mark
+R< $+ > $+ < $* > @	$: < $(generics $1 $: @ $1 $) > $2 < $3 >
+R<@$+ + $* @ $+> $+ < @ $+ >
+		$: < $(generics $1+*@$3 $@ $2 $:@$1 + $2@$3 $) >  $4 < @ $5 >
+R<@$+ + $* @ $+> $+ < @ $+ >
+		$: < $(generics $1@$3 $: $) > $4 < @ $5 >
+R<@$+ > $+ < @ $+ >	$: < > $2 < @ $3 >
+R< > $+ < @ $+ . >	$: < $(generics @$2 $@ $1 $: $) > $1 < @ $2 . >
+R< > $+ < @ $+ > 	$: < $(generics $1 $: $) > $1 < @ $2 >
+R< > $+ + $* < @ $+ > 	$: < $(generics $1+* $@ $2 $: $) > $1 + $2 < @ $3 >
+R< > $+ + $* < @ $+ > 	$: < $(generics $1 $: $) > $1 + $2 < @ $3 >
+R< $* @ $* > $* < $* >	$@ $>canonify $1 @ $2		found qualified
+R< $+ > $* < $* >	$: $>canonify $1 @ *LOCAL*	found unqualified
+R< > $*			$: $1				not found
+
+# do not masquerade anything in class N
+R$* < @ $* $=N . >	$@ $1 < @ $2 $3 . >
+
+R$* < @ *LOCAL* >	$@ $1 < @ $j . >
+
+###################################################################
+###  Ruleset 94 -- convert envelope names to masqueraded form	###
+###################################################################
+
+SMasqEnv=94
+R$* < @ *LOCAL* > $*	$: $1 < @ $j . > $2
+
+###################################################################
+###  Ruleset 98 -- local part of ruleset zero (can be null)	###
+###################################################################
+
+SParseLocal=98
+
+# addresses sent to foo@host.REDIRECT will give a 551 error code
+R$* < @ $+ .REDIRECT. >		$: $1 < @ $2 . REDIRECT . > < ${opMode} >
+R$* < @ $+ .REDIRECT. > <i>	$: $1 < @ $2 . REDIRECT. >
+R$* < @ $+ .REDIRECT. > < $- >	$#error $@ 5.1.1 $: "551 User has moved; please try " <$1@$2>
+
+
+
+
+######################################################################
+###  D: LookUpDomain -- search for domain in access database
+###
+###	Parameters:
+###		<$1> -- key (domain name)
+###		<$2> -- default (what to return if not found in db)
+###		<$3> -- mark (must be <(!|+) single-token>)
+###			! does lookup only with tag
+###			+ does lookup with and without tag
+###		<$4> -- passthru (additional data passed unchanged through)
+######################################################################
+
+SD
+R<$*> <$+> <$- $-> <$*>		$: < $(access $4:$1 $: ? $) > <$1> <$2> <$3 $4> <$5>
+R<?> <$+> <$+> <+ $-> <$*>	$: < $(access $1 $: ? $) > <$1> <$2> <+ $3> <$4>
+R<?> <[$+.$-]> <$+> <$- $-> <$*>	$@ $>D <[$1]> <$3> <$4 $5> <$6>
+R<?> <[$+::$-]> <$+> <$- $-> <$*>	$: $>D <[$1]> <$3> <$4 $5> <$6>
+R<?> <[$+:$-]> <$+> <$- $-> <$*>	$: $>D <[$1]> <$3> <$4 $5> <$6>
+R<?> <$+.$+> <$+> <$- $-> <$*>	$@ $>D <$2> <$3> <$4 $5> <$6>
+R<?> <$+> <$+> <$- $-> <$*>	$@ <$2> <$5>
+R<$* <TMPF>> <$+> <$+> <$- $-> <$*>	$@ <<TMPF>> <$6>
+R<$*> <$+> <$+> <$- $-> <$*>	$@ <$1> <$6>
+
+######################################################################
+###  A: LookUpAddress -- search for host address in access database
+###
+###	Parameters:
+###		<$1> -- key (dot quadded host address)
+###		<$2> -- default (what to return if not found in db)
+###		<$3> -- mark (must be <(!|+) single-token>)
+###			! does lookup only with tag
+###			+ does lookup with and without tag
+###		<$4> -- passthru (additional data passed through)
+######################################################################
+
+SA
+R<$+> <$+> <$- $-> <$*>		$: < $(access $4:$1 $: ? $) > <$1> <$2> <$3 $4> <$5>
+R<?> <$+> <$+> <+ $-> <$*>	$: < $(access $1 $: ? $) > <$1> <$2> <+ $3> <$4>
+R<?> <$+::$-> <$+> <$- $-> <$*>		$@ $>A <$1> <$3> <$4 $5> <$6>
+R<?> <$+:$-> <$+> <$- $-> <$*>		$@ $>A <$1> <$3> <$4 $5> <$6>
+R<?> <$+.$-> <$+> <$- $-> <$*>		$@ $>A <$1> <$3> <$4 $5> <$6>
+R<?> <$+> <$+> <$- $-> <$*>	$@ <$2> <$5>
+R<$* <TMPF>> <$+> <$+> <$- $-> <$*>	$@ <<TMPF>> <$6>
+R<$*> <$+> <$+> <$- $-> <$*>	$@ <$1> <$6>
+
+######################################################################
+###  CanonAddr --	Convert an address into a standard form for
+###			relay checking.  Route address syntax is
+###			crudely converted into a %-hack address.
+###
+###	Parameters:
+###		$1 -- full recipient address
+###
+###	Returns:
+###		parsed address, not in source route form
+######################################################################
+
+SCanonAddr
+R$*			$: $>Parse0 $>canonify $1	make domain canonical
+
+
+######################################################################
+###  ParseRecipient --	Strip off hosts in $=R as well as possibly
+###			$* $=m or the access database.
+###			Check user portion for host separators.
+###
+###	Parameters:
+###		$1 -- full recipient address
+###
+###	Returns:
+###		parsed, non-local-relaying address
+######################################################################
+
+SParseRecipient
+R$*				$: <?> $>CanonAddr $1
+R<?> $* < @ $* . >		<?> $1 < @ $2 >			strip trailing dots
+R<?> $- < @ $* >		$: <?> $(dequote $1 $) < @ $2 >	dequote local part
+
+# if no $=O character, no host in the user portion, we are done
+R<?> $* $=O $* < @ $* >		$: <NO> $1 $2 $3 < @ $4>
+R<?> $*				$@ $1
+
+
+R<NO> $* < @ $=R >		$: <RELAY> $1 < @ $2 >
+R<NO> $* < @ $+ >		$: <$(access To:$2 $: NO $)> $1 < @ $2 >
+R<NO> $* < @ $+ >		$: <$(access $2 $: NO $)> $1 < @ $2 >
+
+
+
+R<RELAY> $* < @ $* >		$@ $>ParseRecipient $1
+R<$+> $*			$@ $2
+
+
+######################################################################
+###  check_relay -- check hostname/address on SMTP startup
+######################################################################
+
+SLocal_check_relay
+Scheckrelay
+R$*			$: $1 $| $>"Local_check_relay" $1
+R$* $| $* $| $#$*	$#$3
+R$* $| $* $| $*		$@ $>"Basic_check_relay" $1 $| $2
+
+SBasic_check_relay
+# check for deferred delivery mode
+R$*			$: < $&{deliveryMode} > $1
+R< d > $*		$@ deferred
+R< $* > $*		$: $2
+
+R$+ $| $+		$: $>D < $1 > <?> <+ Connect> < $2 >
+R   $| $+		$: $>A < $1 > <?> <+ Connect> <>	empty client_name
+R<?> <$+>		$: $>A < $1 > <?> <+ Connect> <>	no: another lookup
+R<?> <$*>		$: OK				found nothing
+R<$={Accept}> <$*>	$@ $1				return value of lookup
+R<REJECT> <$*>		$#error $@ 5.7.1 $: "550 Access denied"
+R<DISCARD> <$*>		$#discard $: discard
+R<ERROR:$-.$-.$-:$+> <$*>	$#error $@ $1.$2.$3 $: $4
+R<ERROR:$+> <$*>		$#error $: $1
+R<$* <TMPF>> <$*>		$#error $@ 4.3.0 $: "451 Temporary system failure. Please try again later."
+R<$+> <$*>		$#error $: $1
+
+
+
+######################################################################
+###  check_mail -- check SMTP `MAIL FROM:' command argument
+######################################################################
+
+SLocal_check_mail
+Scheckmail
+R$*			$: $1 $| $>"Local_check_mail" $1
+R$* $| $#$*		$#$2
+R$* $| $*		$@ $>"Basic_check_mail" $1
+
+SBasic_check_mail
+# check for deferred delivery mode
+R$*			$: < $&{deliveryMode} > $1
+R< d > $*		$@ deferred
+R< $* > $*		$: $2
+
+# authenticated?
+R$*			$: $1 $| $>"tls_client" $&{verify} $| MAIL
+R$* $| $#$+		$#$2
+R$* $| $*		$: $1
+
+R<>			$@ <OK>			we MUST accept <> (RFC 1123)
+R$+			$: <?> $1
+R<?><$+>		$: <@> <$1>
+R<?>$+			$: <@> <$1>
+R$*			$: $&{daemon_flags} $| $1
+R$* f $* $| <@> < $* @ $- >	$: < ? $&{client_name} > < $3 @ $4 >
+R$* u $* $| <@> < $* >	$: <?> < $3 >
+R$* $| $*		$: $2
+# handle case of @localhost on address
+R<@> < $* @ localhost >	$: < ? $&{client_name} > < $1 @ localhost >
+R<@> < $* @ [127.0.0.1] >
+			$: < ? $&{client_name} > < $1 @ [127.0.0.1] >
+R<@> < $* @ localhost.$m >
+			$: < ? $&{client_name} > < $1 @ localhost.$m >
+R<@> < $* @ localhost.UUCP >
+			$: < ? $&{client_name} > < $1 @ localhost.UUCP >
+R<@> $*			$: $1			no localhost as domain
+R<? $=w> $*		$: $2			local client: ok
+R<? $+> <$+>		$#error $@ 5.5.4 $: "553 Real domain name required for sender address"
+R<?> $*			$: $1
+R$*			$: <?> $>CanonAddr $1		canonify sender address and mark it
+R<?> $* < @ $+ . >	<?> $1 < @ $2 >			strip trailing dots
+# handle non-DNS hostnames (*.bitnet, *.decnet, *.uucp, etc)
+R<?> $* < @ $* $=P >	$: <OKR> $1 < @ $2 $3 >
+R<?> $* < @ $j >	$: <OKR> $1 < @ $j >
+R<?> $* < @ $+ >	$: <? $(resolve $2 $: $2 <PERM> $) > $1 < @ $2 >
+R<? $* <$->> $* < @ $+ >
+			$: <$2> $3 < @ $4 >
+
+# check sender address: user@address, user@, address
+R<$+> $+ < @ $* >	$: @<$1> <$2 < @ $3 >> $| <F:$2@$3> <U:$2@> <D:$3>
+R<$+> $+		$: @<$1> <$2> $| <U:$2@>
+R@ <$+> <$*> $| <$+>	$: <@> <$1> <$2> $| $>SearchList <+ From> $| <$3> <>
+R<@> <$+> <$*> $| <$*>	$: <$3> <$1> <$2>		reverse result
+# retransform for further use
+R<?> <$+> <$*>		$: <$1> $2	no match
+R<$+> <$+> <$*>		$: <$1> $3	relevant result, keep it
+
+# handle case of no @domain on address
+R<?> $*			$: $&{daemon_flags} $| <?> $1
+R$* u $* $| <?> $*	$: <OKR> $3
+R$* $| $*		$: $2
+R<?> $*			$: < ? $&{client_addr} > $1
+R<?> $*			$@ <OKR>			...local unqualed ok
+R<? $+> $*		$#error $@ 5.5.4 $: "553 Domain name required for sender address " $&f
+							...remote is not
+# check results
+R<?> $*			$: @ $1		mark address: nothing known about it
+R<$={ResOk}> $*		$@ <OKR>	domain ok: stop
+R<TEMP> $*		$#error $@ 4.1.8 $: "451 Domain of sender address " $&f " does not resolve"
+R<PERM> $*		$#error $@ 5.1.8 $: "553 Domain of sender address " $&f " does not exist"
+R<$={Accept}> $*	$# $1		accept from access map
+R<DISCARD> $*		$#discard $: discard
+R<REJECT> $*		$#error $@ 5.7.1 $: "550 Access denied"
+R<ERROR:$-.$-.$-:$+> $*		$#error $@ $1.$2.$3 $: $4
+R<ERROR:$+> $*		$#error $: $1
+R<<TMPF>> $*		$#error $@ 4.3.0 $: "451 Temporary system failure. Please try again later."
+R<$+> $*		$#error $: $1		error from access db
+
+######################################################################
+###  check_rcpt -- check SMTP `RCPT TO:' command argument
+######################################################################
+
+SLocal_check_rcpt
+Scheckrcpt
+R$*			$: $1 $| $>"Local_check_rcpt" $1
+R$* $| $#$*		$#$2
+R$* $| $*		$@ $>"Basic_check_rcpt" $1
+
+SBasic_check_rcpt
+# empty address?
+R<>			$#error $@ nouser $: "553 User address required"
+R$@			$#error $@ nouser $: "553 User address required"
+# check for deferred delivery mode
+R$*			$: < $&{deliveryMode} > $1
+R< d > $*		$@ deferred
+R< $* > $*		$: $2
+
+
+######################################################################
+R$*			$: $1 $| @ $>"Rcpt_ok" $1
+R$* $| @ $#TEMP $+	$: $1 $| T $2
+R$* $| @ $#$*		$#$2
+R$* $| @ RELAY		$@ RELAY
+R$* $| @ $*		$: O $| $>"Relay_ok" $1
+R$* $| T $+		$: T $2 $| $>"Relay_ok" $1
+R$* $| $#TEMP $+	$#error $2
+R$* $| $#$*		$#$2
+R$* $| RELAY		$@ RELAY
+R T $+ $| $*		$#error $1
+# anything else is bogus
+R$*			$#error $@ 5.7.1 $: "550 Relaying denied. Proper authentication required."
+
+
+######################################################################
+### Rcpt_ok: is the recipient ok?
+######################################################################
+SRcpt_ok
+R$*			$: $>ParseRecipient $1		strip relayable hosts
+
+
+
+
+# authenticated via TLS?
+R$*			$: $1 $| $>RelayTLS	client authenticated?
+R$* $| $# $+		$# $2			error/ok?
+R$* $| $*		$: $1			no
+
+R$*			$: $1 $| $>"Local_Relay_Auth" $&{auth_type}
+R$* $| $# $*		$# $2
+R$* $| NO		$: $1
+R$* $| $*		$: $1 $| $&{auth_type}
+R$* $|			$: $1
+R$* $| $={TrustAuthMech}	$# RELAY
+R$* $| $*		$: $1
+# anything terminating locally is ok
+R$+ < @ $=w >		$@ RELAY
+R$+ < @ $=R >		$@ RELAY
+R$+ < @ $+ >		$: <$(access To:$2 $: ? $)> <$1 < @ $2 >>
+R<?> <$+ < @ $+ >>	$: <$(access $2 $: ? $)> <$1 < @ $2 >>
+R<RELAY> $*		$@ RELAY
+R<$* <TMPF>> $*		$#TEMP $@ 4.3.0 $: "451 Temporary system failure. Please try again later."
+R<$*> <$*>		$: $2
+
+
+
+# check for local user (i.e. unqualified address)
+R$*			$: <?> $1
+R<?> $* < @ $+ >	$: <REMOTE> $1 < @ $2 >
+# local user is ok
+R<?> $+			$@ RELAY
+R<$+> $*		$: $2
+
+######################################################################
+### Relay_ok: is the relay/sender ok?
+######################################################################
+SRelay_ok
+# anything originating locally is ok
+# check IP address
+R$*			$: $&{client_addr}
+R$@			$@ RELAY		originated locally
+R0			$@ RELAY		originated locally
+R127.0.0.1		$@ RELAY		originated locally
+RIPv6:::1		$@ RELAY		originated locally
+R$=R $*			$@ RELAY		relayable IP address
+R$*			$: $>A <$1> <?> <+ Connect> <$1>
+R<RELAY> $* 		$@ RELAY		relayable IP address
+
+R<<TMPF>> $*		$#TEMP $@ 4.3.0 $: "451 Temporary system failure. Please try again later."
+R<$*> <$*>		$: $2
+R$*			$: [ $1 ]		put brackets around it...
+R$=w			$@ RELAY		... and see if it is local
+
+
+# check client name: first: did it resolve?
+R$*			$: < $&{client_resolve} >
+R<TEMP>			$#TEMP $@ 4.7.1 $: "450 Relaying temporarily denied. Cannot resolve PTR record for " $&{client_addr}
+R<FORGED>		$#error $@ 5.7.1 $: "550 Relaying denied. IP name possibly forged " $&{client_name}
+R<FAIL>			$#error $@ 5.7.1 $: "550 Relaying denied. IP name lookup failed " $&{client_name}
+R$*			$: <@> $&{client_name}
+# pass to name server to make hostname canonical
+R<@> $* $=P 		$:<?>  $1 $2
+R<@> $+			$:<?>  $[ $1 $]
+R$* .			$1			strip trailing dots
+R<?> $=w		$@ RELAY
+R<?> $=R		$@ RELAY
+R<?> $*			$: <$(access Connect:$1 $: ? $)> <$1>
+R<?> <$*>		$: <$(access $1 $: ? $)> <$1>
+R<RELAY> $*		$@ RELAY
+R<$* <TMPF>> $*		$#TEMP $@ 4.3.0 $: "451 Temporary system failure. Please try again later."
+R<$*> <$*>		$: $2
+
+# turn a canonical address in the form user<@domain>
+# qualify unqual. addresses with $j
+SFullAddr
+R$* <@ $+ . >		$1 <@ $2 >
+R$* <@ $* >		$@ $1 <@ $2 >
+R$+			$@ $1 <@ $j >
+
+SDelay_TLS_Client
+# authenticated?
+R$*			$: $1 $| $>"tls_client" $&{verify} $| MAIL
+R$* $| $#$+		$#$2
+R$*			$# $1
+
+SDelay_TLS_Client2
+# authenticated?
+R$*			$: $1 $| $>"tls_client" $&{verify} $| MAIL
+R$* $| $#$+		$#$2
+R$*			$@ $1
+
+# call all necessary rulesets
+Scheck_rcpt
+# R$@			$#error $@ 5.1.3 $: "553 Recipient address required"
+
+R$+			$: $1 $| $>checkrcpt $1
+R$+ $| $#error $*	$#error $2
+R$+ $| $#discard $*	$#discard $2
+R$+ $| $#$*		$@ $>"Delay_TLS_Client" $2
+R$+ $| $*		$: <?> $>FullAddr $>CanonAddr $1
+R<?> $+ < @ $=w >	$: <> $1 < @ $2 > $| <F: $1@$2 > <D: $2 > <U: $1@>
+R<?> $+ < @ $* >	$: <> $1 < @ $2 > $| <F: $1@$2 > <D: $2 >
+# lookup the addresses only with Spam tag
+R<> $* $| <$+>		$: <@> $1 $| $>SearchList <! Spam> $| <$2> <>
+R<@> $* $| $*		$: $2 $1		reverse result
+# is the recipient a spam friend?
+R<FRIEND> $+		$@ $>"Delay_TLS_Client2" SPAMFRIEND
+R<$*> $+		$: $2
+R$*			$: $1 $| $>checkmail <$&f>
+R$* $| $#$*		$#$2
+R$* $| $*		$: $1 $| $>checkrelay $&{client_name} $| $&{client_addr}
+R$* $| $#$*		$#$2
+R$* $| $*		$: $1
+
+
+######################################################################
+###  F: LookUpFull -- search for an entry in access database
+###
+###	lookup of full key (which should be an address) and
+###	variations if +detail exists: +* and without +detail
+###
+###	Parameters:
+###		<$1> -- key
+###		<$2> -- default (what to return if not found in db)
+###		<$3> -- mark (must be <(!|+) single-token>)
+###			! does lookup only with tag
+###			+ does lookup with and without tag
+###		<$4> -- passthru (additional data passed unchanged through)
+######################################################################
+
+SF
+R<$+> <$*> <$- $-> <$*>		$: <$(access $4:$1 $: ? $)> <$1> <$2> <$3 $4> <$5>
+R<?> <$+> <$*> <+ $-> <$*>	$: <$(access $1 $: ? $)> <$1> <$2> <+ $3> <$4>
+R<?> <$+ + $* @ $+> <$*> <$- $-> <$*>
+			$: <$(access $6:$1+*@$3 $: ? $)> <$1+$2@$3> <$4> <$5 $6> <$7>
+R<?> <$+ + $* @ $+> <$*> <+ $-> <$*>
+			$: <$(access $1+*@$3 $: ? $)> <$1+$2@$3> <$4> <+ $5> <$6>
+R<?> <$+ + $* @ $+> <$*> <$- $-> <$*>
+			$: <$(access $6:$1@$3 $: ? $)> <$1+$2@$3> <$4> <$5 $6> <$7>
+R<?> <$+ + $* @ $+> <$*> <+ $-> <$*>
+			$: <$(access $1@$3 $: ? $)> <$1+$2@$3> <$4> <+ $5> <$6>
+R<?> <$+> <$*> <$- $-> <$*>	$@ <$2> <$5>
+R<$+ <TMPF>> <$*> <$- $-> <$*>	$@ <<TMPF>> <$5>
+R<$+> <$*> <$- $-> <$*>		$@ <$1> <$5>
+
+######################################################################
+###  E: LookUpExact -- search for an entry in access database
+###
+###	Parameters:
+###		<$1> -- key
+###		<$2> -- default (what to return if not found in db)
+###		<$3> -- mark (must be <(!|+) single-token>)
+###			! does lookup only with tag
+###			+ does lookup with and without tag
+###		<$4> -- passthru (additional data passed unchanged through)
+######################################################################
+
+SE
+R<$*> <$*> <$- $-> <$*>		$: <$(access $4:$1 $: ? $)> <$1> <$2> <$3 $4> <$5>
+R<?> <$+> <$*> <+ $-> <$*>	$: <$(access $1 $: ? $)> <$1> <$2> <+ $3> <$4>
+R<?> <$+> <$*> <$- $-> <$*>	$@ <$2> <$5>
+R<$+ <TMPF>> <$*> <$- $-> <$*>	$@ <<TMPF>> <$5>
+R<$+> <$*> <$- $-> <$*>		$@ <$1> <$5>
+
+######################################################################
+###  U: LookUpUser -- search for an entry in access database
+###
+###	lookup of key (which should be a local part) and
+###	variations if +detail exists: +* and without +detail
+###
+###	Parameters:
+###		<$1> -- key (user@)
+###		<$2> -- default (what to return if not found in db)
+###		<$3> -- mark (must be <(!|+) single-token>)
+###			! does lookup only with tag
+###			+ does lookup with and without tag
+###		<$4> -- passthru (additional data passed unchanged through)
+######################################################################
+
+SU
+R<$+> <$*> <$- $-> <$*>		$: <$(access $4:$1 $: ? $)> <$1> <$2> <$3 $4> <$5>
+R<?> <$+> <$*> <+ $-> <$*>	$: <$(access $1 $: ? $)> <$1> <$2> <+ $3> <$4>
+R<?> <$+ + $* @> <$*> <$- $-> <$*>
+			$: <$(access $5:$1+*@ $: ? $)> <$1+$2@> <$3> <$4 $5> <$6>
+R<?> <$+ + $* @> <$*> <+ $-> <$*>
+			$: <$(access $1+*@ $: ? $)> <$1+$2@> <$3> <+ $4> <$5>
+R<?> <$+ + $* @> <$*> <$- $-> <$*>
+			$: <$(access $5:$1@ $: ? $)> <$1+$2@> <$3> <$4 $5> <$6>
+R<?> <$+ + $* @> <$*> <+ $-> <$*>
+			$: <$(access $1@ $: ? $)> <$1+$2@> <$3> <+ $4> <$5>
+R<?> <$+> <$*> <$- $-> <$*>	$@ <$2> <$5>
+R<$+ <TMPF>> <$*> <$- $-> <$*>	$@ <<TMPF>> <$5>
+R<$+> <$*> <$- $-> <$*>		$@ <$1> <$5>
+
+######################################################################
+###  SearchList: search a list of items in the access map
+###	Parameters:
+###		<exact tag> $| <mark:address> <mark:address> ... <>
+###	where "exact" is either "+" or "!":
+###	<+ TAG>	lookup with and w/o tag
+###	<! TAG>	lookup with tag
+###	possible values for "mark" are:
+###		D: recursive host lookup (LookUpDomain)
+###		E: exact lookup, no modifications
+###		F: full lookup, try user+ext@domain and user@domain
+###		U: user lookup, try user+ext and user (input must have trailing @)
+###	return: <RHS of lookup> or <?> (not found)
+######################################################################
+
+# class with valid marks for SearchList
+C{src}E F D U 
+SSearchList
+# just call the ruleset with the name of the tag... nice trick...
+R<$+> $| <$={src}:$*> <$*>	$: <$1> $| <$4> $| $>$2 <$3> <?> <$1> <>
+R<$+> $| <> $| <?> <>		$@ <?>
+R<$+> $| <$+> $| <?> <>		$@ $>SearchList <$1> $| <$2>
+R<$+> $| <$*> $| <$+> <>	$@ <$3>
+R<$+> $| <$+>			$@ <$2>
+
+
+######################################################################
+###  trust_auth: is user trusted to authenticate as someone else?
+###
+###	Parameters:
+###		$1: AUTH= parameter from MAIL command
+######################################################################
+
+SLocal_trust_auth
+Strust_auth
+R$*			$: $&{auth_type} $| $1
+# required by RFC 2554 section 4.
+R$@ $| $*		$#error $@ 5.7.1 $: "550 not authenticated"
+R$* $| $&{auth_authen}		$@ identical
+R$* $| <$&{auth_authen}>	$@ identical
+R$* $| $*		$: $1 $| $>"Local_trust_auth" $1
+R$* $| $#$*		$#$2
+R$*			$#error $@ 5.7.1 $: "550 " $&{auth_authen} " not allowed to act as " $&{auth_author}
+
+######################################################################
+###  Relay_Auth: allow relaying based on authentication?
+###
+###	Parameters:
+###		$1: ${auth_type}
+######################################################################
+SLocal_Relay_Auth
+
+######################################################################
+###  srv_features: which features to offer to a client?
+###	(done in server)
+######################################################################
+Ssrv_features
+R$*		$: $>D <$&{client_name}> <?> <! "Srv_Features"> <>
+R<?>$*		$: $>A <$&{client_addr}> <?> <! "Srv_Features"> <>
+R<?>$*		$: <$(access "Srv_Features": $: ? $)>
+R<?>$*		$@ OK
+R<$* <TMPF>>$*	$#temp
+R<$+>$*		$# $1
+
+######################################################################
+###  try_tls: try to use STARTTLS?
+###	(done in client)
+######################################################################
+Stry_tls
+R$*		$: $>D <$&{server_name}> <?> <! "Try_TLS"> <>
+R<?>$*		$: $>A <$&{server_addr}> <?> <! "Try_TLS"> <>
+R<?>$*		$: <$(access "Try_TLS": $: ? $)>
+R<?>$*		$@ OK
+R<$* <TMPF>>$*	$#error $@ 4.3.0 $: "451 Temporary system failure. Please try again later."
+R<NO>$*		$#error $@ 5.7.1 $: "550 do not try TLS with " $&{server_name} " ["$&{server_addr}"]"
+ 
+######################################################################
+###  tls_rcpt: is connection with server "good" enough?
+###	(done in client, per recipient)
+###
+###	Parameters:
+###		$1: recipient
+######################################################################
+Stls_rcpt
+R$*			$: $(macro {TLS_Name} $@ $&{server_name} $) $1
+R$+			$: <?> $>CanonAddr $1
+R<?> $+ < @ $+ . >	<?> $1 <@ $2 >
+R<?> $+ < @ $+ >	$: $1 <@ $2 > $| <F:$1@$2> <U:$1@> <D:$2> <E:>
+R<?> $+			$: $1 $| <U:$1@> <E:>
+R$* $| $+	$: $1 $| $>SearchList <! "TLS_Rcpt"> $| $2 <>
+R$* $| <?>	$@ OK
+R$* $| <$* <TMPF>>	$#error $@ 4.3.0 $: "451 Temporary system failure. Please try again later."
+R$* $| <$+>	$@ $>"TLS_connection" $&{verify} $| <$2>
+
+######################################################################
+###  tls_client: is connection with client "good" enough?
+###	(done in server)
+###
+###	Parameters:
+###		${verify} $| (MAIL|STARTTLS)
+######################################################################
+Stls_client
+R$*		$: $(macro {TLS_Name} $@ $&{server_name} $) $1
+R$* $| $*	$: $1 $| $>D <$&{client_name}> <?> <! "TLS_Clt"> <>
+R$* $| <?>$*	$: $1 $| $>A <$&{client_addr}> <?> <! "TLS_Clt"> <>
+R$* $| <?>$*	$: $1 $| <$(access "TLS_Clt": $: ? $)>
+R$* $| <$* <TMPF>>	$#error $@ 4.3.0 $: "451 Temporary system failure. Please try again later."
+R$*		$@ $>"TLS_connection" $1
+
+######################################################################
+###  tls_server: is connection with server "good" enough?
+###	(done in client)
+###
+###	Parameter:
+###		${verify}
+######################################################################
+Stls_server
+R$*		$: $(macro {TLS_Name} $@ $&{server_name} $) $1
+R$*		$: $1 $| $>D <$&{server_name}> <?> <! "TLS_Srv"> <>
+R$* $| <?>$*	$: $1 $| $>A <$&{server_addr}> <?> <! "TLS_Srv"> <>
+R$* $| <?>$*	$: $1 $| <$(access "TLS_Srv": $: ? $)>
+R$* $| <$* <TMPF>>	$#error $@ 4.3.0 $: "451 Temporary system failure. Please try again later."
+R$*		$@ $>"TLS_connection" $1
+
+######################################################################
+###  TLS_connection: is TLS connection "good" enough?
+###
+###	Parameters:
+###		${verify} $| <Requirement> [<>]
+###		Requirement: RHS from access map, may be ? for none.
+######################################################################
+STLS_connection
+R$* $| <$*>$*			$: $1 $| <$2>
+# create the appropriate error codes
+R$* $| <PERM + $={tls} $*>	$: $1 $| <503:5.7.0> <$2 $3>
+R$* $| <TEMP + $={tls} $*>	$: $1 $| <403:4.7.0> <$2 $3>
+R$* $| <$={tls} $*>		$: $1 $| <403:4.7.0> <$2 $3>
+# deal with TLS handshake failures: abort
+RSOFTWARE $| <$-:$+> $* 	$#error $@ $2 $: $1 " TLS handshake failed."
+RSOFTWARE $| $* 		$#error $@ 4.7.0 $: "403 TLS handshake failed."
+R$* $| <$*> <VERIFY>		$: <$2> <VERIFY> <> $1
+R$* $| <$*> <VERIFY + $+>	$: <$2> <VERIFY> <$3> $1
+R$* $| <$*> <$={tls}:$->$*	$: <$2> <$3:$4> <> $1
+R$* $| <$*> <$={tls}:$- + $+>$*	$: <$2> <$3:$4> <$5> $1
+R$* $| $*			$@ OK
+# authentication required: give appropriate error
+# other side did authenticate (via STARTTLS)
+R<$*><VERIFY> <> OK		$@ OK
+R<$*><VERIFY> <$+> OK		$: <$1> <REQ:0> <$2>
+R<$*><VERIFY:$-> <$*> OK	$: <$1> <REQ:$2> <$3>
+R<$*><ENCR:$-> <$*> $*		$: <$1> <REQ:$2> <$3>
+R<$-:$+><VERIFY $*> <$*>	$#error $@ $2 $: $1 " authentication required"
+R<$-:$+><VERIFY $*> <$*> FAIL	$#error $@ $2 $: $1 " authentication failed"
+R<$-:$+><VERIFY $*> <$*> NO	$#error $@ $2 $: $1 " not authenticated"
+R<$-:$+><VERIFY $*> <$*> NOT	$#error $@ $2 $: $1 " no authentication requested"
+R<$-:$+><VERIFY $*> <$*> NONE	$#error $@ $2 $: $1 " other side does not support STARTTLS"
+R<$-:$+><VERIFY $*> <$*> $+	$#error $@ $2 $: $1 " authentication failure " $4
+R<$*><REQ:$-> <$*>		$: <$1> <REQ:$2> <$3> $>max $&{cipher_bits} : $&{auth_ssf}
+R<$*><REQ:$-> <$*> $-		$: <$1> <$2:$4> <$3> $(arith l $@ $4 $@ $2 $)
+R<$-:$+><$-:$-> <$*> TRUE	$#error $@ $2 $: $1 " encryption too weak " $4 " less than " $3
+R<$-:$+><$-:$-> <$*> $*		$: <$1:$2 ++ $5>
+R<$-:$+ ++ >			$@ OK
+R<$-:$+ ++ $+ >			$: <$1:$2> <$3>
+R<$-:$+> < $+ ++ $+ >		<$1:$2> <$3> <$4>
+R<$-:$+> $+			$@ $>"TLS_req" $3 $| <$1:$2>
+
+######################################################################
+###  TLS_req: check additional TLS requirements
+###
+###	Parameters: [<list> <of> <req>] $| <$-:$+>
+###		$-: SMTP reply code
+###		$+: Enhanced Status Code
+######################################################################
+STLS_req
+R $| $+		$@ OK
+R<CN> $* $| <$+>		$: <CN:$&{TLS_Name}> $1 $| <$2>
+R<CN:$&{cn_subject}> $* $| <$+>		$@ $>"TLS_req" $1 $| <$2>
+R<CN:$+> $* $| <$-:$+>	$#error $@ $4 $: $3 " CN " $&{cn_subject} " does not match " $1
+R<CS:$&{cert_subject}> $* $| <$+>	$@ $>"TLS_req" $1 $| <$2>
+R<CS:$+> $* $| <$-:$+>	$#error $@ $4 $: $3 " Cert Subject " $&{cert_subject} " does not match " $1
+R<CI:$&{cert_issuer}> $* $| <$+>	$@ $>"TLS_req" $1 $| <$2>
+R<CI:$+> $* $| <$-:$+>	$#error $@ $4 $: $3 " Cert Issuer " $&{cert_issuer} " does not match " $1
+ROK			$@ OK
+
+######################################################################
+###  max: return the maximum of two values separated by :
+###
+###	Parameters: [$-]:[$-]
+######################################################################
+Smax
+R:		$: 0
+R:$-		$: $1
+R$-:		$: $1
+R$-:$-		$: $(arith l $@ $1 $@ $2 $) : $1 : $2
+RTRUE:$-:$-	$: $2
+R$-:$-:$-	$: $2
+
+
+######################################################################
+###  RelayTLS: allow relaying based on TLS authentication
+###
+###	Parameters:
+###		none
+######################################################################
+SRelayTLS
+# authenticated?
+R$*			$: <?> $&{verify}
+R<?> OK			$: OK		authenticated: continue
+R<?> $*			$@ NO		not authenticated
+R$*			$: $&{cert_issuer}
+R$+			$: $(access CERTISSUER:$1 $)
+RRELAY			$# RELAY
+RSUBJECT		$: <@> $&{cert_subject}
+R<@> $+			$: <@> $(access CERTSUBJECT:$1 $)
+R<@> RELAY		$# RELAY
+R$*			$: NO
+
+######################################################################
+###  authinfo: lookup authinfo in the access map
+###
+###	Parameters:
+###		$1: {server_name}
+###		$2: {server_addr}
+######################################################################
+Sauthinfo
+R$*		$: $1 $| $>D <$&{server_name}> <?> <! AuthInfo> <>
+R$* $| <?>$*	$: $1 $| $>A <$&{server_addr}> <?> <! AuthInfo> <>
+R$* $| <?>$*	$: $1 $| <$(access AuthInfo: $: ? $)> <>
+R$* $| <?>$*	$@ no				no authinfo available
+R$* $| <$*> <>	$# $2
+
+#
+######################################################################
+######################################################################
+#####
+#####			MAIL FILTER DEFINITIONS
+#####
+######################################################################
+######################################################################
+
+Xdnsbl, S=local:/var/run/dnsbl/dnsbl.sock, F=T, T=S:30s;R:30s;E:30s
+#
+######################################################################
+######################################################################
+#####
+#####			MAILER DEFINITIONS
+#####
+######################################################################
+######################################################################
+
+#####################################
+###   SMTP Mailer specification   ###
+#####################################
+
+#####  $Id$  #####
+
+#
+#  common sender and masquerading recipient rewriting
+#
+SMasqSMTP
+R$* < @ $* > $*		$@ $1 < @ $2 > $3		already fully qualified
+R$+			$@ $1 < @ *LOCAL* >		add local qualification
+
+#
+#  convert pseudo-domain addresses to real domain addresses
+#
+SPseudoToReal
+
+# pass <route-addr>s through
+R< @ $+ > $*		$@ < @ $1 > $2			resolve <route-addr>
+
+# output fake domains as user%fake@relay
+
+# do UUCP heuristics; note that these are shared with UUCP mailers
+R$+ < @ $+ .UUCP. >	$: < $2 ! > $1			convert to UUCP form
+R$+ < @ $* > $*		$@ $1 < @ $2 > $3		not UUCP form
+
+# leave these in .UUCP form to avoid further tampering
+R< $&h ! > $- ! $+	$@ $2 < @ $1 .UUCP. >
+R< $&h ! > $-.$+ ! $+	$@ $3 < @ $1.$2 >
+R< $&h ! > $+		$@ $1 < @ $&h .UUCP. >
+R< $+ ! > $+		$: $1 ! $2 < @ $Y >		use UUCP_RELAY
+R$+ < @ $~[ $* : $+ >	$@ $1 < @ $4 >			strip mailer: part
+R$+ < @ >		$: $1 < @ *LOCAL* >		if no UUCP_RELAY
+
+
+#
+#  envelope sender rewriting
+#
+SEnvFromSMTP
+R$+			$: $>PseudoToReal $1		sender/recipient common
+R$* :; <@>		$@				list:; special case
+R$*			$: $>MasqSMTP $1		qualify unqual'ed names
+R$+			$: $>MasqEnv $1			do masquerading
+
+
+#
+#  envelope recipient rewriting --
+#  also header recipient if not masquerading recipients
+#
+SEnvToSMTP
+R$+			$: $>PseudoToReal $1		sender/recipient common
+R$+			$: $>MasqSMTP $1		qualify unqual'ed names
+R$* < @ *LOCAL* > $*	$: $1 < @ $j . > $2
+
+#
+#  header sender and masquerading header recipient rewriting
+#
+SHdrFromSMTP
+R$+			$: $>PseudoToReal $1		sender/recipient common
+R:; <@>			$@				list:; special case
+
+# do special header rewriting
+R$* <@> $*		$@ $1 <@> $2			pass null host through
+R< @ $* > $*		$@ < @ $1 > $2			pass route-addr through
+R$*			$: $>MasqSMTP $1		qualify unqual'ed names
+R$+			$: $>MasqHdr $1			do masquerading
+
+
+#
+#  relay mailer header masquerading recipient rewriting
+#
+SMasqRelay
+R$+			$: $>MasqSMTP $1
+R$+			$: $>MasqHdr $1
+
+Msmtp,		P=[IPC], F=mDFMuX, S=EnvFromSMTP/HdrFromSMTP, R=EnvToSMTP, E=\r\n, L=990,
+		T=DNS/RFC822/SMTP,
+		A=TCP $h
+Mesmtp,		P=[IPC], F=mDFMuXa, S=EnvFromSMTP/HdrFromSMTP, R=EnvToSMTP, E=\r\n, L=990,
+		T=DNS/RFC822/SMTP,
+		A=TCP $h
+Msmtp8,		P=[IPC], F=mDFMuX8, S=EnvFromSMTP/HdrFromSMTP, R=EnvToSMTP, E=\r\n, L=990,
+		T=DNS/RFC822/SMTP,
+		A=TCP $h
+Mdsmtp,		P=[IPC], F=mDFMuXa%, S=EnvFromSMTP/HdrFromSMTP, R=EnvToSMTP, E=\r\n, L=990,
+		T=DNS/RFC822/SMTP,
+		A=TCP $h
+Mrelay,		P=[IPC], F=mDFMuXa8, S=EnvFromSMTP/HdrFromSMTP, R=MasqSMTP, E=\r\n, L=2040,
+		T=DNS/RFC822/SMTP,
+		A=TCP $h
+
+
+######################*****##############
+###   PROCMAIL Mailer specification   ###
+##################*****##################
+
+#####  $Id$  #####
+
+Mprocmail,	P=/usr/bin/procmail, F=DFMSPhnu9, S=EnvFromSMTP/HdrFromSMTP, R=EnvToSMTP/HdrFromSMTP,
+		T=DNS/RFC822/X-Unix,
+		A=procmail -Y -m $h $f $u
+
+
+##################################################
+###   Local and Program Mailer specification   ###
+##################################################
+
+#####  $Id$  #####
+
+#
+#  Envelope sender rewriting
+#
+SEnvFromL
+R<@>			$n			errors to mailer-daemon
+R@ <@ $*>		$n			temporarily bypass Sun bogosity
+R$+			$: $>AddDomain $1	add local domain if needed
+R$*			$: $>MasqEnv $1		do masquerading
+
+#
+#  Envelope recipient rewriting
+#
+SEnvToL
+R$+ < @ $* >		$: $1			strip host part
+R$+ + $*		$: < $&{addr_type} > $1 + $2	mark with addr type
+R<e s> $+ + $*		$: $1			remove +detail for sender
+R< $* > $+		$: $2			else remove mark
+
+#
+#  Header sender rewriting
+#
+SHdrFromL
+R<@>			$n			errors to mailer-daemon
+R@ <@ $*>		$n			temporarily bypass Sun bogosity
+R$+			$: $>AddDomain $1	add local domain if needed
+R$*			$: $>MasqHdr $1		do masquerading
+
+#
+#  Header recipient rewriting
+#
+SHdrToL
+R$+			$: $>AddDomain $1	add local domain if needed
+R$* < @ *LOCAL* > $*	$: $1 < @ $j . > $2
+
+#
+#  Common code to add local domain name (only if always-add-domain)
+#
+SAddDomain
+R$* < @ $* > $* 	$@ $1 < @ $2 > $3	already fully qualified
+
+R$+			$@ $1 < @ *LOCAL* >	add local qualification
+
+Mlocal,		P=/usr/bin/procmail, F=lsDFMAw5:/|@qSPfhn9, S=EnvFromL/HdrFromL, R=EnvToL/HdrToL,
+		T=DNS/RFC822/X-Unix,
+		A=procmail -t -Y -a $h -d $u
+Mprog,		P=/bin/sh, F=lsDFMoqeu9, S=EnvFromL/HdrFromL, R=EnvToL/HdrToL, D=$z:/,
+		T=X-Unix/X-Unix/X-Unix,
+		A=sh -c $u
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test.mc	Tue Apr 20 20:02:29 2004 -0700
@@ -0,0 +1,55 @@
+divert(-1)
+dnl This is the sendmail macro config file. If you make changes to this file,
+dnl you need the sendmail-cf rpm installed and then have to generate a
+dnl new /etc/mail/sendmail.cf by running the following command:
+dnl
+dnl        m4 /etc/mail/sendmail.mc > /etc/mail/sendmail.cf
+dnl
+include(`/usr/share/sendmail-cf/m4/cf.m4')
+VERSIONID(`linux setup for Red Hat Linux')
+OSTYPE(`linux')
+undefine(`UUCP_RELAY')
+undefine(`BITNET_RELAY')
+define(`ALIAS_FILE', /etc/mail/aliases)
+define(`confAUTH_MECHANISMS', `LOGIN PLAIN')
+define(`confAUTH_OPTIONS', `A')
+define(`confBAD_RCPT_THROTTLE', 2)
+define(`confCONNECTION_RATE_THROTTLE', 1)
+define(`confCR_FILE', `/etc/mail/relay-domains')
+define(`confCT_FILE', `/etc/mail/sendmail.ct')
+define(`confCW_FILE', `/etc/mail/sendmail.cw')
+define(`confDEF_USER_ID',``8:12'')
+define(`confDONT_PROBE_INTERFACES',true)
+define(`confDOUBLE_BOUNCE_ADDRESS', `')
+define(`confLOG_LEVEL', `20')
+define(`confMAX_DAEMON_CHILDREN', `20')
+define(`confMAX_MESSAGE_SIZE', 30000000)
+define(`confME_TOO', true)
+define(`confPID_FILE', `/var/run/sm-test.pid')
+define(`confPRIVACY_FLAGS', `goaway,nobodyreturn,noreceipts')
+define(`confQUEUE_LA', 12)
+define(`confREFUSE_LA', 8)
+define(`confTO_CONNECT', `1m')
+define(`confTO_IDENT', `0')
+define(`MAIL_SETTINGS_DIR', `/etc/mail/')
+define(`PROCMAIL_MAILER_PATH',`/usr/bin/procmail')
+define(`STATUS_FILE', /usr/src/rh8/dnsbl/sendmail.st)
+define(`MILTER', 1)
+DAEMON_OPTIONS(`port=26')dnl
+FEATURE(always_add_domain)
+FEATURE(local_procmail,`',`procmail -t -Y -a $h -d $u')dnl The '-t' option will retry delivery if e.g. the user runs over his quota.
+FEATURE(redirect)
+FEATURE(use_ct_file)
+FEATURE(use_cw_file)
+FEATURE(`access_db',`hash -T<TMPF> /etc/mail/access.db')
+FEATURE(`delay_checks', `friend')
+FEATURE(`relay_hosts_only')
+FEATURE(`mailertable',`hash /etc/mail/mailertable.db')
+FEATURE(`virtusertable',`hash /etc/mail/virtusertable.db')
+FEATURE(`genericstable',`hash /etc/mail/genericstable.db')
+FEATURE(`no_default_msa',`dnl')
+VIRTUSER_DOMAIN_FILE(`/etc/mail/virtual-host-domains')
+TRUST_AUTH_MECH(`LOGIN PLAIN')
+INPUT_MAIL_FILTER(`dnsbl',         `S=local:/var/run/dnsbl/dnsbl.sock, F=T, T=S:30s;R:30s;E:30s')
+MAILER(smtp)
+MAILER(procmail)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xml/dnsbl.in	Tue Apr 20 20:02:29 2004 -0700
@@ -0,0 +1,114 @@
+<html>
+
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
+<title>DNSBL Sendmail milter</title>
+</head>
+
+<p>This milter is released under the GPL license version 2 included in
+the LICENSE file in the distribution, and also available at
+<a href="http://www.gnu.org/licenses/gpl.html">http://www.gnu.org/licenses/gpl.html</a>
+
+<p>Consider the case of a mail server that is acting as secondary MX
+for a collection of clients, each of which has a collection of mail
+domains.  Each client may use their own collection of DNSBLs on their
+primary mail server.  We present here a mechanism whereby the backup
+mail server can use the correct set of DNSBLs for each message.  As a
+side-effect, it gives us the ability to customize the set of DNSBLs on a
+per-recipient basis, so that fred@example.com could use SPEWS and the
+SBL, where all other users @example.com use only the SBL.
+
+<p>The DNSBL milter reads a text configuration file on startup, and
+whenever the config file (or any of the referenced include files) is
+changed. The entire configuration file is case insensitive.
+
+<p>If you are also using the <a
+href="http://www.rhyolite.com/anti-spam/dcc/">DCC</a> milter, there are
+a few considerations.  You may need to whitelist senders from the DCC
+bulk detector, or from the DNS based lists.  Those are two very
+different reasons for whitelisting.  The former is done thru the DCC
+whiteclnt config file, the later is done thru the DNSBL milter config
+file.
+
+<p>You may want to blacklist some specific senders or sending domains.
+This could be done thru either the DCC (on a global basis, or for a
+specific single recipient).  We prefer to do such blacklisting via the
+DNSBL milter config, since it can be done for an entire recipient mail
+domain.  The DCC approach has the feature that you can capture the
+entire message in the DCC log files.  The DNSBL milter approach has the
+feature that the mail is rejected earlier (at RCPT TO time), and the
+sending machine just gets a generic "550 5.7.1 no such user" message.
+
+<p>Definitions:
+
+<p>DNSBL - a named DNS based blocking list is defined by a dns suffix
+(e.g. sbl-xbl.spamhaus.org) and a message string that is used to
+generate the "550 5.7.1" smtp error return code.  The names of these
+DNSBLs will be used to define the DNSBL-LISTs.
+
+<p>DNSBL-LIST - a named list of DNSBLs that will be used for specific
+recipients or recipient domains.
+
+<p>ENVELOPE-FROM-MAP - a named collection of mappings (key->value pairs)
+from envelope-from values to the WHITE, BLACK, or DEFAULT keywords.  The
+names of these maps will be used for specific recipients or recipient
+domains.
+
+<p>The configuration file maps each recipient (or recipient domain) to
+two names (a named DNSBL-LIST, and a named ENVELOPE-FROM-MAP).  If the
+recipient is not found in the configuration, the named DEFAULT
+dnsbl-list and DEFAULT envelope-from-map will be used.  When mail is
+received for that recipient,
+
+<ol>
+
+<li>If the client has authenticated with sendmail, the mail is accepted
+and the dns lists are not checked.
+
+<li>If either one is BLACK, mail to this recipient is rejected with "no
+such user", and the dns lists are not checked.
+
+<li>If the envelope-from-map name is WHITE, mail to this recipient is
+accepted and the dns lists are not checked.
+
+<li>If the envelope-from-map exists, the map is checked for the presence
+of the sender.  A WHITE or BLACK answer is definitive and the dns lists
+are not checked.
+
+<li>If the dnsbl-list name is WHITE, the dns lists are not checked and
+the mail is accepted.  Otherwise, the dns lists are checked and the mail
+is rejected if any list has an A record for the standard dns based
+lookup scheme (reversed octets of the client followed by the dns
+suffix).
+
+</ol>
+
+
+<p>Usage:  Note that this has ONLY been tested on Linux, specifically
+RedHat Linux.  Your mileage will vary. In particular, this milter makes no
+attempt to understand IPv6.
+
+Fetch <a href="http://www.five-ten-sg.com/util/dnsbl.tar.gz">dnsbl.tar.gz</a>
+and
+
+<pre>
+tar xfvz dnsbl.tar.gz
+bash install.bash
+</pre>
+
+Read and understand the contents of that install.bash script before you
+run it.  It may not be suitable for your system.  Modify your
+sendmail.mc by removing all the "FEATURE(dnsbl" lines, add the following
+line in your sendmail.mc and rebuild the .cf file
+
+<pre>
+INPUT_MAIL_FILTER(`dnsbl', `S=local:/var/run/dnsbl/dnsbl.sock, F=T, T=S:30s;R:30s;E:30s')
+</pre>
+
+Read the sample <a
+href="http://www.five-ten-sg.com/dnsbl.conf">var/dnsbl/dnsbl.conf</a>
+file and modify it to fit your configuration.
+
+
+</body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xml/sample.conf	Tue Apr 20 20:02:29 2004 -0700
@@ -0,0 +1,108 @@
+#
+# lines start with a command token, following by argument tokens
+# tokens are separated by spaces or tabs
+#
+#
+# dnsbl:
+#   second token is the name of this dnsbl
+#   third  token is the dns suffix used for the actual lookups
+#   fourth token? is a string enclosed in single quotes, so it
+#       is not really a token. This is the error message, with
+#       up to two %s parameters for the client ip address.
+#
+# dnsbl_list:
+#   second token is the name of this list of dnsbls
+#   subsequent tokes are the names of the previously defined dnsbls
+#
+# env_from:
+#   second token is the name of this envelope-from-map. There will
+#       generally be multiple lines with the same name.
+#   third token is the envelope from value from the smtp conversation,
+#       or just the domain part that follows the @ symbol.
+#   fourth token is BLACK, WHITE, or the name of a previously defined
+#       envelope-from-map. BLACK causes mail from this sender to be
+#       rejected with "no such user". WHITE causes mail to be accepted
+#       and the dns based lists are ignored. DEFAULT may be used to override
+#       the contents of other maps that are copied into this map, and
+#       set that sender back to the default (not white or black listed,
+#       and subject to dnsbl lookups).
+#
+# env_to:
+#   second token is the envelope recipient value from the smtp conversation,
+#       or just the domain part that follows the @ symbol.
+#   third token is the name of a dnsbl-list, or WHITE or BLACK.
+#   fourth token is the name of an envelope-from-map, or WHITE or BLACK.
+#
+#   If either one is BLACK, mail to this recipient is rejected with
+#   "no such user", and the dns lists are not checked.
+#
+#   If the envelope-from-map name is WHITE, mail to this recipient is accepted
+#   and the dns lists are not checked.
+#
+#   If the envelope-from-map exists, the map is checked for the presence
+#   of the sender. A WHITE or BLACK answer is definitive and the dns lists
+#   are not checked.
+#
+#   If the dnsbl-list name is WHITE, the dns lists are not checked and the
+#   mail is accepted. Otherwise, the dns lists are checked and the mail
+#   is rejected if any list has an A record for the standard dns based
+#   lookup scheme (reversed octets of the client followed by the dns suffix).
+#
+#
+#
+#
+##############################################
+# define the dnsbls to use
+#
+#
+dnsbl   LOCAL   blackholes.five-ten-sg.com  'Mail from %s rejected - local; see http://www.five-ten-sg.com/blackhole.php?%s'
+dnsbl   SPEWS   blackholes.spews.org        'Mail from %s rejected - spews; see http://www.spews.org/ask.cgi?x=%s'
+dnsbl   SBL     sbl-xbl.spamhaus.org        'Mail from %s rejected - sbl; see http://www.spamhaus.org/query/bl?ip=%s'
+
+
+##############################################
+# define the (default and other) lists of dnsbls to use
+#
+dnsbl_list  DEFAULT LOCAL SPEWS SBL
+dnsbl_list  SIMPLE  SBL
+dnsbl_list  CUST1   SBL
+dnsbl_list  CUST2   SPEWS SBL
+
+
+##############################################
+# define the (default and other) env_from maps
+#
+env_from    DEFAULT spammer@example.com     BLACK
+env_from    DEFAULT yahoo.com               BLACK
+
+# special list for the vp
+env_from    TEST    dummy-token             DEFAULT # inherit the currently defined DEFAULT env_from mapping
+env_from    TEST    nai.com                 BLACK   # the vp does not like nai
+env_from    TEST    yahoo.com               DEFAULT #
+env_from    TEST    mother@spammyisp.com    WHITE   # suppresses dnsbl checking
+
+
+##############################################
+# specify dnsbl_lists and env_from maps to use for specific recipients
+#
+env_to      abuse@mydomain.com      WHITE   WHITE       # no dnsbl, no env_from map
+env_to      sales@mydomain.com      SIMPLE  NULL        # sbl only, no env_from map
+env_to      vp@mydomain.com         DEFAULT TEST        # allow mail from mom
+env_to      old-emp@mydomain.com    BLACK   BLACK       # return no such user even from backup mx machines
+
+##############################################
+# specify dnsbl_lists and env_from maps to use for clients domains
+#
+env_to      mydomain.com            DEFAULT DEFAULT
+env_to      customer1.com           CUST1   DEFAULT     # all customer 1 domains use just sbl
+env_to      customer1a.com          CUST1   DEFAULT
+env_to      customer1b.com          CUST1   DEFAULT
+env_to      customer2.com           CUST2   DEFAULT     # all customer 2 domains use spews and sbl
+env_to      customer2a.com          CUST2   DEFAULT
+
+
+##############################################
+# you can also include nested config files
+# file names are single tokens, no embedded blanks
+#
+include dnsbl.conf      # this will generate a recursive include file syslog error message