# HG changeset patch # User carl # Date 1082516549 25200 # Node ID 96a9758165cd17e8ebd48e283ab37a6527ec2837 Initial revision diff -r 000000000000 -r 96a9758165cd COPYING --- /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. + + + Copyright (C) + + 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. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff -r 000000000000 -r 96a9758165cd Makefile.test --- /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 *~ + diff -r 000000000000 -r 96a9758165cd dnsbl.rc --- /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 diff -r 000000000000 -r 96a9758165cd install.bash --- /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 diff -r 000000000000 -r 96a9758165cd sendmail.st Binary file sendmail.st has changed diff -r 000000000000 -r 96a9758165cd src/dnsbl.cpp --- /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 +#include +#include +#include +#include +#include +#include +#include + +// needed for socket io +#include +#include +#include +#include +#include +#include +#include + +// needed for thread +#include + +// needed for std c++ collections +#include +#include +#include + +// for the dns resolver +#include +#include +#include + +// misc stuff needed here +#include +#include +#include + + +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 DNSBLL; +typedef DNSBLL * DNSBLLP; +typedef map string_map; +typedef map from_map; +typedef map dnsblp_map; +typedef map dnsbllp_map; +typedef set string_set; +typedef list 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 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::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 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= 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 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(); +} diff -r 000000000000 -r 96a9758165cd src/package --- /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 diff -r 000000000000 -r 96a9758165cd src/update --- /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 diff -r 000000000000 -r 96a9758165cd test.bash --- /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"'" + diff -r 000000000000 -r 96a9758165cd test.cf --- /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 -T +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 /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 +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 +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> -> +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 +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 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. > $: $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<$* > <$+> <$+> <$- $-> <$*> $@ <> <$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<$* > <$+> <$+> <$- $-> <$*> $@ <> <$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 $* < @ $* > $: $1 $2 $3 < @ $4> +R $* $@ $1 + + +R $* < @ $=R > $: $1 < @ $2 > +R $* < @ $+ > $: <$(access To:$2 $: NO $)> $1 < @ $2 > +R $* < @ $+ > $: <$(access $2 $: NO $)> $1 < @ $2 > + + + +R $* < @ $* > $@ $>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 <$*> $#error $@ 5.7.1 $: "550 Access denied" +R <$*> $#discard $: discard +R <$*> $#error $@ $1.$2.$3 $: $4 +R <$*> $#error $: $1 +R<$* > <$*> $#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<> $@ 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 $* $: $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 > $: $1 < @ $2 $3 > +R $* < @ $j > $: $1 < @ $j > +R $* < @ $+ > $: $) > $1 < @ $2 > +R> $* < @ $+ > + $: <$2> $3 < @ $4 > + +# check sender address: user@address, user@, address +R<$+> $+ < @ $* > $: @<$1> <$2 < @ $3 >> $| +R<$+> $+ $: @<$1> <$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 $* $| $* $: $3 +R$* $| $* $: $2 +R $* $: < ? $&{client_addr} > $1 +R $* $@ ...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}> $* $@ domain ok: stop +R $* $#error $@ 4.1.8 $: "451 Domain of sender address " $&f " does not resolve" +R $* $#error $@ 5.1.8 $: "553 Domain of sender address " $&f " does not exist" +R<$={Accept}> $* $# $1 accept from access map +R $* $#discard $: discard +R $* $#error $@ 5.7.1 $: "550 Access denied" +R $* $#error $@ $1.$2.$3 $: $4 +R $* $#error $: $1 +R<> $* $#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 +R<$* > $* $#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 $* < @ $+ > $: $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 relayable IP address + +R<> $* $#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 $@ 4.7.1 $: "450 Relaying temporarily denied. Cannot resolve PTR record for " $&{client_addr} +R $#error $@ 5.7.1 $: "550 Relaying denied. IP name possibly forged " $&{client_name} +R $#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 +R<$* > $* $#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 > $| +R $+ < @ $* > $: <> $1 < @ $2 > $| +# lookup the addresses only with Spam tag +R<> $* $| <$+> $: <@> $1 $| $>SearchList $| <$2> <> +R<@> $* $| $* $: $2 $1 reverse result +# is the recipient a spam friend? +R $+ $@ $>"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<$+ > <$*> <$- $-> <$*> $@ <> <$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<$+ > <$*> <$- $-> <$*> $@ <> <$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<$+ > <$*> <$- $-> <$*> $@ <> <$5> +R<$+> <$*> <$- $-> <$*> $@ <$1> <$5> + +###################################################################### +### SearchList: search a list of items in the access map +### Parameters: +### $| ... <> +### where "exact" is either "+" or "!": +### <+ TAG> lookup with and w/o 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: 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}> <> +R$* $: $>A <$&{client_addr}> <> +R$* $: <$(access "Srv_Features": $: ? $)> +R$* $@ OK +R<$* >$* $#temp +R<$+>$* $# $1 + +###################################################################### +### try_tls: try to use STARTTLS? +### (done in client) +###################################################################### +Stry_tls +R$* $: $>D <$&{server_name}> <> +R$* $: $>A <$&{server_addr}> <> +R$* $: <$(access "Try_TLS": $: ? $)> +R$* $@ OK +R<$* >$* $#error $@ 4.3.0 $: "451 Temporary system failure. Please try again later." +R$* $#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 > $| +R $+ $: $1 $| +R$* $| $+ $: $1 $| $>SearchList $| $2 <> +R$* $| $@ OK +R$* $| <$* > $#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}> <> +R$* $| $* $: $1 $| $>A <$&{client_addr}> <> +R$* $| $* $: $1 $| <$(access "TLS_Clt": $: ? $)> +R$* $| <$* > $#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}> <> +R$* $| $* $: $1 $| $>A <$&{server_addr}> <> +R$* $| $* $: $1 $| <$(access "TLS_Srv": $: ? $)> +R$* $| <$* > $#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: RHS from access map, may be ? for none. +###################################################################### +STLS_connection +R$* $| <$*>$* $: $1 $| <$2> +# create the appropriate error codes +R$* $| $: $1 $| <503:5.7.0> <$2 $3> +R$* $| $: $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$* $| <$*> $: <$2> <> $1 +R$* $| <$*> $: <$2> <$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<$*> <> OK $@ OK +R<$*> <$+> OK $: <$1> <$2> +R<$*> <$*> OK $: <$1> <$3> +R<$*> <$*> $* $: <$1> <$3> +R<$-:$+> <$*> $#error $@ $2 $: $1 " authentication required" +R<$-:$+> <$*> FAIL $#error $@ $2 $: $1 " authentication failed" +R<$-:$+> <$*> NO $#error $@ $2 $: $1 " not authenticated" +R<$-:$+> <$*> NOT $#error $@ $2 $: $1 " no authentication requested" +R<$-:$+> <$*> NONE $#error $@ $2 $: $1 " other side does not support STARTTLS" +R<$-:$+> <$*> $+ $#error $@ $2 $: $1 " authentication failure " $4 +R<$*> <$*> $: <$1> <$3> $>max $&{cipher_bits} : $&{auth_ssf} +R<$*> <$*> $- $: <$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: [ ] $| <$-:$+> +### $-: SMTP reply code +### $+: Enhanced Status Code +###################################################################### +STLS_req +R $| $+ $@ OK +R $* $| <$+> $: $1 $| <$2> +R $* $| <$+> $@ $>"TLS_req" $1 $| <$2> +R $* $| <$-:$+> $#error $@ $4 $: $3 " CN " $&{cn_subject} " does not match " $1 +R $* $| <$+> $@ $>"TLS_req" $1 $| <$2> +R $* $| <$-:$+> $#error $@ $4 $: $3 " Cert Subject " $&{cert_subject} " does not match " $1 +R $* $| <$+> $@ $>"TLS_req" $1 $| <$2> +R $* $| <$-:$+> $#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}> <> +R$* $| $* $: $1 $| $>A <$&{server_addr}> <> +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 s through +R< @ $+ > $* $@ < @ $1 > $2 resolve + +# 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 $+ + $* $: $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 + diff -r 000000000000 -r 96a9758165cd test.mc --- /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 /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) diff -r 000000000000 -r 96a9758165cd xml/dnsbl.in --- /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 @@ + + + + +DNSBL Sendmail milter + + +

This milter is released under the GPL license version 2 included in +the LICENSE file in the distribution, and also available at +http://www.gnu.org/licenses/gpl.html + +

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. + +

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. + +

If you are also using the DCC 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. + +

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. + +

Definitions: + +

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. + +

DNSBL-LIST - a named list of DNSBLs that will be used for specific +recipients or recipient domains. + +

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. + +

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, + +

    + +
  1. If the client has authenticated with sendmail, the mail is accepted +and the dns lists are not checked. + +
  2. If either one is BLACK, mail to this recipient is rejected with "no +such user", and the dns lists are not checked. + +
  3. If the envelope-from-map name is WHITE, mail to this recipient is +accepted and the dns lists are not checked. + +
  4. 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. + +
  5. 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). + +
+ + +

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 dnsbl.tar.gz +and + +

+tar xfvz dnsbl.tar.gz
+bash install.bash
+
+ +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 + +
+INPUT_MAIL_FILTER(`dnsbl', `S=local:/var/run/dnsbl/dnsbl.sock, F=T, T=S:30s;R:30s;E:30s')
+
+ +Read the sample var/dnsbl/dnsbl.conf +file and modify it to fit your configuration. + + + + diff -r 000000000000 -r 96a9758165cd xml/sample.conf --- /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