From 8438d08e8afb938173ebab865d3d35b7cc58aaff Mon Sep 17 00:00:00 2001 From: cvs2svn <stuart@gathman.org> Date: Tue, 14 Jun 2005 22:02:47 +0000 Subject: [PATCH] This commit was manufactured by cvs2svn to create tag 'mitler-0_8_0'. Sprout from master 2005-06-14 22:02:46 UTC Stuart Gathman <stuart@gathman.org> 'Release 0.8.0' Delete: COPYING CREDITS MANIFEST.in Milter.py Milter/__init__.py Milter/dsn.py Milter/dynip.py README TODO bms.py cid2spf.py faq.html milter.cfg milter.html milter.rc milter.rc7 miltermodule.c mime.py rejects.py rhsbl.m4 sample.py setup.cfg setup.py softfail.txt spf.py spfquery.py strike3.txt test.py test/amazon test/big5 test/bounce test/bounce1 test/bound test/honey test/missingboundary test/samp1 test/spam44 test/spam7 test/spam8 test/test1 test/test8 test/virus1 test/virus13 test/virus2 test/virus3 test/virus4 test/virus5 test/virus6 test/virus7 test/zip1 testbms.py testmime.py testsample.py --- COPYING | 340 ------------ CREDITS | 29 - MANIFEST.in | 26 - Milter.py | 203 ------- Milter/__init__.py | 209 ------- Milter/dsn.py | 185 ------- Milter/dynip.py | 93 ---- README | 192 ------- TODO | 81 --- bms.py | 1237 ------------------------------------------ cid2spf.py | 153 ------ faq.html | 159 ------ milter.cfg | 160 ------ milter.html | 502 ----------------- milter.rc | 81 --- milter.rc7 | 81 --- miltermodule.c | 1233 ----------------------------------------- mime.py | 503 ----------------- rejects.py | 38 -- rhsbl.m4 | 44 -- sample.py | 181 ------ setup.cfg | 5 - setup.py | 49 -- softfail.txt | 23 - spf.py | 1039 ----------------------------------- spfquery.py | 99 ---- strike3.txt | 66 --- test.py | 17 - test/amazon | 710 ------------------------ test/big5 | 44 -- test/bounce | 86 --- test/bounce1 | 85 --- test/bound | 84 --- test/honey | 36 -- test/missingboundary | 128 ----- test/samp1 | 46 -- test/spam44 | 497 ----------------- test/spam7 | 30 - test/spam8 | 221 -------- test/test1 | 61 --- test/test8 | 118 ---- test/virus1 | 72 --- test/virus13 | 127 ----- test/virus2 | 90 --- test/virus3 | 50 -- test/virus4 | 60 -- test/virus5 | 38 -- test/virus6 | 27 - test/virus7 | 62 --- test/zip1 | 51 -- testbms.py | 299 ---------- testmime.py | 155 ------ testsample.py | 149 ----- 53 files changed, 10354 deletions(-) delete mode 100644 COPYING delete mode 100644 CREDITS delete mode 100644 MANIFEST.in delete mode 100755 Milter.py delete mode 100755 Milter/__init__.py delete mode 100644 Milter/dsn.py delete mode 100644 Milter/dynip.py delete mode 100644 README delete mode 100644 TODO delete mode 100644 bms.py delete mode 100644 cid2spf.py delete mode 100644 faq.html delete mode 100644 milter.cfg delete mode 100644 milter.html delete mode 100755 milter.rc delete mode 100755 milter.rc7 delete mode 100644 miltermodule.c delete mode 100644 mime.py delete mode 100644 rejects.py delete mode 100644 rhsbl.m4 delete mode 100644 sample.py delete mode 100644 setup.cfg delete mode 100644 setup.py delete mode 100644 softfail.txt delete mode 100755 spf.py delete mode 100755 spfquery.py delete mode 100644 strike3.txt delete mode 100644 test.py delete mode 100644 test/amazon delete mode 100644 test/big5 delete mode 100644 test/bounce delete mode 100644 test/bounce1 delete mode 100644 test/bound delete mode 100644 test/honey delete mode 100644 test/missingboundary delete mode 100644 test/samp1 delete mode 100644 test/spam44 delete mode 100644 test/spam7 delete mode 100644 test/spam8 delete mode 100644 test/test1 delete mode 100644 test/test8 delete mode 100644 test/virus1 delete mode 100644 test/virus13 delete mode 100644 test/virus2 delete mode 100644 test/virus3 delete mode 100644 test/virus4 delete mode 100644 test/virus5 delete mode 100644 test/virus6 delete mode 100644 test/virus7 delete mode 100644 test/zip1 delete mode 100644 testbms.py delete mode 100644 testmime.py delete mode 100644 testsample.py diff --git a/COPYING b/COPYING deleted file mode 100644 index 5b6e7c6..0000000 --- a/COPYING +++ /dev/null @@ -1,340 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc. - 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Library General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - <one line to give the program's name and a brief idea of what it does.> - Copyright (C) <year> <name of author> - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - <signature of Ty Coon>, 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Library General -Public License instead of this License. diff --git a/CREDITS b/CREDITS deleted file mode 100644 index f32fdc3..0000000 --- a/CREDITS +++ /dev/null @@ -1,29 +0,0 @@ -Jim Niemira (urmane@urmane.org) wrote the original C module and some quick -and dirty python to use it. Stuart D. Gathman (stuart@bmsi.com) took that -kludge and added threading and context objects to it, wrote a proper OO -wrapper (Milter.py) that handles attachments, did lots of testing, packaged -it with distutils, and generally transformed it from a quick hack to a -real, usable Python extension. - -Other contributors: - -Terence Way - for providing a Python port of SPF -Alexander Kourakos - for plugging several memory leaks -George Graf at Vienna University of Economics and Business Administration - for handling None passed to setreply and chgheader. -Deron Meranda - for IPv6 patches -Jason Erikson - for handling NULL hostaddr in connect callback. -John Draper - for porting Python milter to OpenBSD, and starting to work on tutorials - then pointing out that it would be easier to just write the MTA in Python. -Eric S. Johansson - for helpful design discussions while working on camram -Business Management Systems - http://www.bmsi.com - for hosting the website, and providing paying clients who need milter service - so I can work on it as part of my day job. - -If I have left anybody out, send me a reminder: stuart@bmsi.com diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index cb56ef0..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,26 +0,0 @@ -include COPYING -include TODO -include NEWS -include CREDITS -include README -include MANIFEST.in -include testsample.py -include testmime.py -include testbms.py -include testdspam.py -include rejects.py -include bms.py -include spf.py -include spfquery.py -include test.py -include sample.py -include test/* -include Milter/*.py -include *.spec -include start.sh -include milter.rc -include milter.rc7 -include milter.cfg -include rhsbl.m4 -include softfail.txt -include strike3.txt diff --git a/Milter.py b/Milter.py deleted file mode 100755 index ac8d3f4..0000000 --- a/Milter.py +++ /dev/null @@ -1,203 +0,0 @@ - -# Author: Stuart D. Gathman <stuart@bmsi.com> -# Copyright 2001 Business Management Systems, Inc. -# This code is under GPL. See COPYING for details. - -import os -import milter -import thread - -from milter import ACCEPT,CONTINUE,REJECT,DISCARD,TEMPFAIL, \ - set_flags, setdbg, setbacklog, settimeout, \ - ADDHDRS, CHGBODY, ADDRCPT, DELRCPT, CHGHDRS, \ - V1_ACTS, V2_ACTS, CURR_ACTS - -try: from milter import QUARANTINE -except: pass - -_seq_lock = thread.allocate_lock() -_seq = 0 - -def uniqueID(): - """Return a sequence number unique to this process. - """ - global _seq - _seq_lock.acquire() - seqno = _seq = _seq + 1 - _seq_lock.release() - return seqno - -class Milter: - """A simple class interface to the milter module. - """ - def _setctx(self,ctx): - self.__ctx = ctx - if ctx: - ctx.setpriv(self) - - # user replaceable callbacks - def log(self,*msg): - print 'Milter:', - for i in msg: print i, - print - - def connect(self,hostname,unused,hostaddr): - "Called for each connection to sendmail." - self.log("connect from %s at %s" % (hostname,hostaddr)) - return CONTINUE - - def hello(self,hostname): - "Called after the HELO command." - self.log("hello from %s" % hostname) - return CONTINUE - - def envfrom(self,f,*str): - """Called to begin each message. - f -> string message sender - str -> tuple additional ESMTP parameters - """ - self.log("mail from",f,str) - return CONTINUE - - def envrcpt(self,to,*str): - "Called for each message recipient." - self.log("rcpt to",to,str) - return CONTINUE - - def header(self,field,value): - "Called for each message header." - self.log("%s: %s" % (field,value)) - return CONTINUE - - def eoh(self): - "Called after all headers are processed." - self.log("eoh") - return CONTINUE - - def body(self,unused): - "Called to transfer the message body." - return CONTINUE - - def eom(self): - "Called at the end of message." - self.log("eom") - return CONTINUE - - def abort(self): - "Called if the connection is terminated abnormally." - self.log("abort") - return CONTINUE - - def close(self): - "Called at the end of connection, even if aborted." - self.log("close") - return CONTINUE - - # Milter methods which can be invoked from callbacks - def getsymval(self,sym): - return self.__ctx.getsymval(sym) - - # If sendmail does not support setmlreply, then only the - # first msg line is used. - def setreply(self,rcode,xcode=None,msg=None,*ml): - return self.__ctx.setreply(rcode,xcode,msg,*ml) - - # Milter methods which can only be called from eom callback. - def addheader(self,field,value): - return self.__ctx.addheader(field,value) - - def chgheader(self,field,idx,value): - return self.__ctx.chgheader(field,idx,value) - - def addrcpt(self,rcpt): - return self.__ctx.addrcpt(rcpt) - - def delrcpt(self,rcpt): - return self.__ctx.delrcpt(rcpt) - - def replacebody(self,body): - return self.__ctx.replacebody(body) - - # When quarantined, a message goes into the mailq as if to be delivered, - # but delivery is deferred until the message is unquarantined. - def quarantine(self,reason): - return self.__ctx.quarantine(reason) - - def progress(self): - return self.__ctx.progress() - -factory = Milter - -def connectcallback(ctx,hostname,family,hostaddr): - m = factory() - m._setctx(ctx) - return m.connect(hostname,family,hostaddr) - -def closecallback(ctx): - m = ctx.getpriv() - if not m: return CONTINUE - rc = m.close() - m._setctx(None) # release milterContext - return rc - -def envcallback(c,args): - """Convert ESMTP parms to keyword parameters. - Can be used in the envfrom and/or envrcpt callbacks to process - ESMTP parameters as python keyword parameters.""" - kw = {} - for s in args[1:]: - pos = s.find('=') - if pos > 0: - kw[s[:pos]] = s[pos+1:] - return apply(c,args,kw) - -def runmilter(name,socketname,timeout = 0): - # This bit is here on the assumption that you will be starting this filter - # before sendmail. If sendmail is not running and the socket already exists, - # libmilter will throw a warning. If sendmail is running, this is still - # safe if there are no messages currently being processed. It's safer to - # shutdown sendmail, kill the filter process, restart the filter, and then - # restart sendmail. - pos = socketname.find(':') - if pos > 1: - s = socketname[:pos] - fname = socketname[pos+1:] - else: - s = "unix" - fname = socketname - if s == "unix" or s == "local": - print "Removing %s" % fname - try: - os.unlink(fname) - except: - pass - - # The default flags set include everything - # milter.set_flags(milter.ADDHDRS) - milter.set_connect_callback(connectcallback) - milter.set_helo_callback(lambda ctx, host: ctx.getpriv().hello(host)) - milter.set_envfrom_callback(lambda ctx,*str: - ctx.getpriv().envfrom(*str)) -# envcallback(ctx.getpriv().envfrom,str)) - milter.set_envrcpt_callback(lambda ctx,*str: - ctx.getpriv().envrcpt(*str)) -# envcallback(ctx.getpriv().envrcpt,str)) - milter.set_header_callback(lambda ctx,fld,val: - ctx.getpriv().header(fld,val)) - milter.set_eoh_callback(lambda ctx: ctx.getpriv().eoh()) - milter.set_body_callback(lambda ctx,chunk: ctx.getpriv().body(chunk)) - milter.set_eom_callback(lambda ctx: ctx.getpriv().eom()) - milter.set_abort_callback(lambda ctx: ctx.getpriv().abort()) - milter.set_close_callback(closecallback) - - milter.setconn(socketname) - if timeout > 0: milter.settimeout(timeout) - # The name *must* match the X line in sendmail.cf (supposedly) - milter.register(name) - start_seq = _seq - try: - milter.main() - except milter.error: - if start_seq == _seq: raise # couldn't start - # milter has been running for a while, but now it can't start new threads - raise milter.error("out of thread resources") diff --git a/Milter/__init__.py b/Milter/__init__.py deleted file mode 100755 index 3f0de8c..0000000 --- a/Milter/__init__.py +++ /dev/null @@ -1,209 +0,0 @@ -# Author: Stuart D. Gathman <stuart@bmsi.com> -# Copyright 2001 Business Management Systems, Inc. -# This code is under the GNU General Public License. See COPYING for details. - -# A thin OO wrapper for the milter module - -import os -import milter -import thread - -from milter import ACCEPT,CONTINUE,REJECT,DISCARD,TEMPFAIL, \ - set_flags, setdbg, setbacklog, settimeout, \ - ADDHDRS, CHGBODY, ADDRCPT, DELRCPT, CHGHDRS, \ - V1_ACTS, V2_ACTS, CURR_ACTS - -try: from milter import QUARANTINE -except: pass - -_seq_lock = thread.allocate_lock() -_seq = 0 - -def uniqueID(): - """Return a sequence number unique to this process. - """ - global _seq - _seq_lock.acquire() - seqno = _seq = _seq + 1 - _seq_lock.release() - return seqno - -class Milter: - """A simple class interface to the milter module. - """ - def _setctx(self,ctx): - self.__ctx = ctx - if ctx: - ctx.setpriv(self) - - # user replaceable callbacks - def log(self,*msg): - print 'Milter:', - for i in msg: print i, - print - - def connect(self,hostname,unused,hostaddr): - "Called for each connection to sendmail." - self.log("connect from %s at %s" % (hostname,hostaddr)) - return CONTINUE - - def hello(self,hostname): - "Called after the HELO command." - self.log("hello from %s" % hostname) - return CONTINUE - - def envfrom(self,f,*str): - """Called to begin each message. - f -> string message sender - str -> tuple additional ESMTP parameters - """ - self.log("mail from",f,str) - return CONTINUE - - def envrcpt(self,to,*str): - "Called for each message recipient." - self.log("rcpt to",to,str) - return CONTINUE - - def header(self,field,value): - "Called for each message header." - self.log("%s: %s" % (field,value)) - return CONTINUE - - def eoh(self): - "Called after all headers are processed." - self.log("eoh") - return CONTINUE - - def body(self,unused): - "Called to transfer the message body." - return CONTINUE - - def eom(self): - "Called at the end of message." - self.log("eom") - return CONTINUE - - def abort(self): - "Called if the connection is terminated abnormally." - self.log("abort") - return CONTINUE - - def close(self): - "Called at the end of connection, even if aborted." - self.log("close") - return CONTINUE - - # Milter methods which can be invoked from callbacks - def getsymval(self,sym): - return self.__ctx.getsymval(sym) - - # If sendmail does not support setmlreply, then only the - # first msg line is used. - def setreply(self,rcode,xcode=None,msg=None,*ml): - return self.__ctx.setreply(rcode,xcode,msg,*ml) - - # Milter methods which can only be called from eom callback. - def addheader(self,field,value): - return self.__ctx.addheader(field,value) - - def chgheader(self,field,idx,value): - return self.__ctx.chgheader(field,idx,value) - - def addrcpt(self,rcpt): - return self.__ctx.addrcpt(rcpt) - - def delrcpt(self,rcpt): - return self.__ctx.delrcpt(rcpt) - - def replacebody(self,body): - return self.__ctx.replacebody(body) - - # When quarantined, a message goes into the mailq as if to be delivered, - # but delivery is deferred until the message is unquarantined. - def quarantine(self,reason): - return self.__ctx.quarantine(reason) - - def progress(self): - return self.__ctx.progress() - -factory = Milter - -def connectcallback(ctx,hostname,family,hostaddr): - m = factory() - m._setctx(ctx) - return m.connect(hostname,family,hostaddr) - -def closecallback(ctx): - m = ctx.getpriv() - if not m: return CONTINUE - rc = m.close() - m._setctx(None) # release milterContext - return rc - -def envcallback(c,args): - """Convert ESMTP parms to keyword parameters. - Can be used in the envfrom and/or envrcpt callbacks to process - ESMTP parameters as python keyword parameters.""" - kw = {} - for s in args[1:]: - pos = s.find('=') - if pos > 0: - kw[s[:pos]] = s[pos+1:] - return apply(c,args,kw) - -def runmilter(name,socketname,timeout = 0): - # This bit is here on the assumption that you will be starting this filter - # before sendmail. If sendmail is not running and the socket already exists, - # libmilter will throw a warning. If sendmail is running, this is still - # safe if there are no messages currently being processed. It's safer to - # shutdown sendmail, kill the filter process, restart the filter, and then - # restart sendmail. - pos = socketname.find(':') - if pos > 1: - s = socketname[:pos] - fname = socketname[pos+1:] - else: - s = "unix" - fname = socketname - if s == "unix" or s == "local": - print "Removing %s" % fname - try: - os.unlink(fname) - except: - pass - - # The default flags set include everything - # milter.set_flags(milter.ADDHDRS) - milter.set_connect_callback(connectcallback) - milter.set_helo_callback(lambda ctx, host: ctx.getpriv().hello(host)) - milter.set_envfrom_callback(lambda ctx,*str: - ctx.getpriv().envfrom(*str)) -# envcallback(ctx.getpriv().envfrom,str)) - milter.set_envrcpt_callback(lambda ctx,*str: - ctx.getpriv().envrcpt(*str)) -# envcallback(ctx.getpriv().envrcpt,str)) - milter.set_header_callback(lambda ctx,fld,val: - ctx.getpriv().header(fld,val)) - milter.set_eoh_callback(lambda ctx: ctx.getpriv().eoh()) - milter.set_body_callback(lambda ctx,chunk: ctx.getpriv().body(chunk)) - milter.set_eom_callback(lambda ctx: ctx.getpriv().eom()) - milter.set_abort_callback(lambda ctx: ctx.getpriv().abort()) - milter.set_close_callback(closecallback) - - milter.setconn(socketname) - if timeout > 0: milter.settimeout(timeout) - # The name *must* match the X line in sendmail.cf (supposedly) - milter.register(name) - start_seq = _seq - try: - milter.main() - except milter.error: - if start_seq == _seq: raise # couldn't start - # milter has been running for a while, but now it can't start new threads - raise milter.error("out of thread resources") - -__all__ = globals().copy() -for priv in ('os','milter','thread','factory','_seq','_seq_lock'): - del __all__[priv] -__all__ = __all__.keys() diff --git a/Milter/dsn.py b/Milter/dsn.py deleted file mode 100644 index fc314c5..0000000 --- a/Milter/dsn.py +++ /dev/null @@ -1,185 +0,0 @@ -# Author: Stuart D. Gathman <stuart@bmsi.com> -# Copyright 2005 Business Management Systems, Inc. -# This code is under the GNU General Public License. See COPYING for details. - -# Send DSNs, do call back verification, -# and generate DSN messages from a template - -import smtplib -import spf -import socket -from email.Message import Message - -nospf_msg = """Subject: Critical mail server configuration error - -This is an automatically generated Delivery Status Notification. - -THIS IS A WARNING MESSAGE ONLY. - -YOU DO *NOT* NEED TO RESEND YOUR MESSAGE. - -Delivery to the following recipients has been delayed. - - %(rcpt)s - -Subject: %(subject)s - -Someone at IP address %(connectip)s sent an email claiming -to be from %(sender)s. - -If that wasn't you, then your domain, %(sender_domain)s, -was forged - i.e. used without your knowlege or authorization by -someone attempting to steal your mail identity. This is a very -serious problem, and you need to provide authentication for your -SMTP (email) servers to prevent criminals from forging your -domain. The simplest step is usually to publish an SPF record -with your Sender Policy. - -For more information, see: http://spfhelp.net - -I hate to annoy you with a DSN (Delivery Status -Notification) from a possibly forged email, but since you -have not published a sender policy, there is no other way -of bringing this to your attention. - -If it *was* you that sent the email, then your email domain -or configuration is in error. If you don't know anything -about mail servers, then pass this on to your SMTP (mail) -server administrator. We have accepted the email anyway, in -case it is important, but we couldn't find anything about -the mail submitter at %(connectip)s to distinguish it from a -zombie (compromised/infected computer - usually a Windows -PC). There was no PTR record for its IP address (PTR names -that contain the IP address don't count). RFC2821 requires -that your hello name be a FQN (Fully Qualified domain Name, -i.e. at least one dot) that resolves to the IP address of -the mail sender. In addition, just like for PTR, we don't -accept a helo name that contains the IP, since this doesn't -help to identify you. The hello name you used, -%(heloname)s, was invalid. - -Furthermore, there was no SPF record for the sending domain -%(sender_domain)s. We even tried to find its IP in any A or -MX records for your domain, but that failed also. We really -should reject mail from anonymous mail clients, but in case -it is important, we are accepting it anyway. - -We are sending you this message to alert you to the fact that - -Either - Someone is forging your domain. -Or - You have problems with your email configuration. -Or - Possibly both. - -If you need further assistance, please do not hesitate to -contact me again. - -Kind regards, - -postmaster@%(receiver)s -""" - -softfail_msg = """Subject: SPF softfail (POSSIBLE FORGERY) - -This is an automatically generated Delivery Status Notification. - -THIS IS A WARNING MESSAGE ONLY. - -YOU DO *NOT* NEED TO RESEND YOUR MESSAGE. - -Delivery to the following recipients has been delayed. - - %(rcpt)s - -Subject: %(subject)s -Received-SPF: %(spf_result)s -""" - -def send_dsn(mailfrom,receiver,msg=None): - "Send DSN. If msg is None, do callback verification." - user,domain = mailfrom.split('@') - q = spf.query(None,None,None) - mxlist = q.dns(domain,'MX') - if not mxlist: - mxlist = (0,domain), - else: - mxlist.sort() - smtp = smtplib.SMTP() - for prior,host in mxlist: - try: - smtp.connect(host) - code,resp = smtp.helo(receiver) - # some wiley spammers have MX records that resolve to 127.0.0.1 - if resp.split()[0] == receiver: - return (553,'Fraudulent MX for %s' % domain) - if not (200 <= code <= 299): - raise SMTPHeloError(code, resp) - if msg: - try: - smtp.sendmail('<>',mailfrom,msg) - except smtplib.SMTPSenderRefused: - # does not accept DSN, try postmaster (at the risk of mail loops) - smtp.sendmail('<postmaster@%s>'%receiver,mailfrom,msg) - else: # CBV - code,resp = smtp.docmd('MAIL FROM: <>') - if code != 250: - raise SMTPSenderRefused(code, resp, '<>') - code,resp = smtp.rcpt(mailfrom) - if code not in (250,251): - return (code,resp) # permanent error - smtp.quit() - return None # success - except smtplib.SMTPRecipientsRefused,x: - return x.recipients[mailfrom] # permanent error - except smtplib.SMTPSenderRefused,x: - return x # does not accept DSN - except smtplib.SMTPDataError,x: - return x # permanent error - except smtplib.SMTPException: - pass # any other error, try next MX - except socket.error: - pass # MX didn't accept connections, try next one - smtp.close() - return (450,'No MX servers available') # temp error - -def create_msg(q,rcptlist,origmsg=None,template=None): - "Create a DSN message from a template. Template must be '\n' separated." - heloname = q.h - sender = q.s - connectip = q.i - receiver = q.r - sender_domain = q.o - rcpt = '\n\t'.join(rcptlist) - try: subject = origmsg['Subject'] - except: subject = '(none)' - try: - spf_result = origmsg['Received-SPF'] - if not spf_result.startswith('softfail'): - spf_result = None - except: spf_result = None - - msg = Message() - - msg.add_header('To',sender) - msg.add_header('From','postmaster@%s'%receiver) - msg.add_header('Auto-Submitted','auto-generated (configuration error)') - msg.set_type('text/plain') - - if not template: - if spf_result: template = softfail_msg - else: template = nospf_msg - hdrs,body = template.split('\n',1) - for ln in hdrs.splitlines(): - name,val = ln.split(':',1) - msg.add_header(name,(val % locals()).strip()) - msg.set_payload(body % locals()) - - return msg - -if __name__ == '__main__': - q = spf.query('192.168.9.50', - 'SRS0=pmeHL=RH=bmsi.com=stuart@bmsi.com', - 'bmsred.bmsi.com',receiver='mail.bmsi.com') - msg = create_msg(q,['charlie@jsconnor.com'],None,None) - print msg.as_string() - # print send_dsn(f,msg.as_string()) - print send_dsn(q.s,'mail.bmsi.com',msg.as_string()) diff --git a/Milter/dynip.py b/Milter/dynip.py deleted file mode 100644 index 8cf9602..0000000 --- a/Milter/dynip.py +++ /dev/null @@ -1,93 +0,0 @@ -# Author: Stuart D. Gathman <stuart@bmsi.com> -# Copyright 2005 Business Management Systems, Inc. -# This code is under the GNU General Public License. See COPYING for details. - -# Heuristically determine whether a domain name is for a dynamic IP. - -# examples we don't yet recognize: -# -# wiley-268-8196.roadrunner.nf.net at ('205.251.174.46', 4810) -# cbl-sd-02-79.aster.com.do at ('200.88.62.79', 4153) - -import re - -ip3 = re.compile('[0-9]{1,3}') -hpats = ( - 'h[0-9a-f]{12}[.]', - 'h\d*n\d*c\d*o\d*\.', - 'pcp\d{6,10}pcs[.]', - 'no-reverse', - 'S[0-9a-f]{16}[.][a-z]{2}[.]', - 'user<3>\.', - '[Cc]ust<3>\.', - '^<3>\.', - 'ppp[^.]*<3>\.', - '-ppp\d*\.', - '\d*-<3>\.', - '[0-9a-f]{1,3}-<3>\.', - 'p<3>\.pool', - 'h<3>\.', - 'xdsl-\d*\.', - '-\d*-\d*\.', - '\.adsl\.', - '\.cable\.' -) -rehmac = re.compile('|'.join(hpats)) - -def is_dynip(host,addr): - """Return True if hostname is for a dynamic ip. - Examples: - - >>> is_dynip('post3.fabulousdealz.com','69.60.99.112') - False - >>> is_dynip('adsl-69-208-201-177.dsl.emhril.ameritech.net','69.208.201.177') - True - >>> is_dynip('[1.2.3.4]','1.2.3.4') - True - """ - if host.startswith('[') and host.endswith(']'): - return True - if addr: - if host.find(addr) >= 0: return True - a = addr.split('.') - ia = map(int,a) - h = host - m = ip3.findall(host) - if m: - g = map(int,m) - ia3 = (ia[1:],ia[:3]) - if g[-3:] in ia3: return True - if g[0] == ia[3] and g[1:3] == ia[:2]: return True - if g[-2:] == ia[2:]: return True - g.reverse() - if g[:3] in ia3: return True - if g[:2] == ia[2:]: return True - if ia[2:] in (g[:2],g[-2:]): return True - for m in ip3.finditer(host): - if int(m.group()) == ia[3]: - h = host[:m.start()] + '<3>' + host[m.end():] - break - if rehmac.search(h): return True - if host.find(''.join(a[:3])) >= 0: return True - if host.find(''.join(a[1:])) >= 0: return True - x = "%02x%02x%02x%02x" % tuple(ia) - if host.lower().find(x) >= 0: return True - return False - -if __name__ == '__main__': - import fileinput - import sets - seen = sets.Set() - for ln in fileinput.input(): - a = ln.split() - if a[3:5] == ['connect','from']: - host = a[5] - if host.startswith('[') and host.endswith(']'): - continue # no PTR - ip = a[7][2:-2] - if ip in seen: continue - seen.add(ip) - if is_dynip(host,ip): - print '%s\t%s DYN' % (ip,host) - else: - print '%s\t%s' % (ip,host) diff --git a/README b/README deleted file mode 100644 index 5df6c93..0000000 --- a/README +++ /dev/null @@ -1,192 +0,0 @@ -Abstract --------- - -This is a python extension module to enable python scripts to attach to -Sendmail's libmilter API, enabling filtering of messages as they arrive. -Since it's a script, you can do anything you want to the message - screen -out viruses, collect statistics, add or modify headers, etc. You can, at -any point, tell Sendmail to reject, discard, or accept the message. - - -Requirements ------------- - -This python milter extension: http://www.bmsi.com/python/milter.html -Python: http://www.python.org -Sendmail: http://www.sendmail.org -NB: From Sendmail's libmilter/README: - -libmilter requires pthread support in the operating system. Moreover, it -requires that the library functions it uses are thread safe; which is true -for the operating systems libmilter has been developed and tested on. On -some operating systems this requires special compile time options (e.g., -not just -pthread). libmilter is currently known to work on (modulo -problems in the pthread support of some specific versions): - -FreeBSD 3.x, 4.x -SunOS 5.x (x >= 5) -AIX 4.3.x -HP UX 11.x -Linux (recent versions/distributions) -OpenBSD -AIX 4.1.5 - -libmilter is currently not supported on: - -IRIX 6.x -Ultrix - -Quick Installation ------------------- - -1. Build and install Sendmail, enabling libmilter (see libmilter/README). -2. Build and install Python, enabling threading. -3. Install this module: python setup.py --help -4. Add these two lines to sendmail.cf: - -O InputMailFilters=pythonfilter -Xpythonfilter, S=local:/home/username/pythonsock - -5. Run the sample.py example milter with: python sample.py -Note that milters should almost certainly not run as root. - -That's it. Incoming mail will cause the milter to print some things, and -some email will be rejected (see the "header" method). Edit and play. See -bms.py for an example milter used in production. - - -Not-so-quick Installation -------------------------- - -First install Sendmail. Make sure you read libmilter/README in the Sendmail -source directory, and make sure you enable libmilter before you build. The -8.11 series had libmilter marked as FFR (For Future Release); 8.12 -officially -supports libmilter, but it's still not built by default. - -Install Python, and enable threading in Modules/Setup. - -Install this miltermodule package; DistUtils Automatic Installation: - -$ python setup.py --help - -For versions of python prior to 2.0, you will need to download distutils -separately or build manually. You will need to download unittest -separately to run the test programs. The bdist_rpm distutils option seems -not to work for python 2.0; upgrade to at least 2.1.1. - -Now that everything is installed, we need to tell sendmail that we're going -to filter incoming email. Add lines similar to the following to -sendmail.cf: - -O InputMailFilters=pythonfilter -Xpythonfilter, S=local:/home/username/pythonsock - -The "O" line tells sendmail which filters to use in what order; here we're -telling sendmail to use the filter named "pythonfilter". - -The next line, the "X" line (for "eXternal"), lists that filter along with -some options associated with it. In this case, we have the "S" option, which -names the socket that sendmail will use to communicate with this particular -milter. This milter's socket is a unix-domain socket in the filesystem. -See libmilter/README for the definitive list of options. -NB: The name is specified in two places: here, in sendmail's cf file, and -in the milter itself. Make sure the two match. -NB: The above lines can be added in your .mc file with this line: - -INPUT_MAIL_FILTER(`pythonfilter', `S=local:/home/username/pythonsock') - -For versions of sendmail prior to 8.12, you will need to enable -_FFR_MILTER for the cf macros. For example, - -m4 -D_FFR_MILTER ../m4/cf.m4 myconfig.mc > myconfig.cf - - -RedHat 6.2 Notes ----------------- - -The Redhat 6.2 sendmail RPM does not enable milter. You can obtain a -modified spec file at - -http://www.bmsi.com/linux/rh62/sendmail-rhmilter.spec - -use it to rebuild the Redhat 7.2 SRPM. The RH6.2 SRPM does not have -recent sendmail security patches. - -RedHat 7.2 Notes ----------------- - -The Redhat 7.2 sendmail RPM enables milter in sendmail - but does not include -the headers needed for compiling a milter. You can obtain a modified spec -file with a sendmail-devel package that includes the needed static libraries -and headers at - -http://www.bmsi.com/linux/sendmail-rh72.spec - -IPv6 Notes ----------- - -IPv6 is still experimental. - -The IPv6 protocol is supported if your operation system supports it -and if sendmail was compiled with IPv6 support. To determine if your -sendmail supports IPv6, run "sendmail -d0" and check for the NETINET6 -compilation option. To compile sendmail with IPv6 support, add this -declaration to your site.config.m4 before building it: - -APPENDDEF(`confENVDEF', `-DNETINET6=1') - -IPv6 support can show up in two places; the communications socket -between the milter and sendmail processes and in the host address -argument to the connect() callback method. - -For sendmail to be able to accept IPv6 SMTP sessions, you must -configure the daemon to listen on an IPv6 port. Furthermore if you -want to allow both IPv4 and IPv6 connections, some operating systems -will require that each listens to different port numbers. For an -IPv6-only setup, your sendmail configuration should contain a line -similar to (first line is for sendmail.mc, second is sendmail.cf): - -DAEMON_OPTIONS(`Name=MTA-v6, Family=inet6, Modify=C, Port=25') -O DaemonPortOptions=Name=MTA-v6, Family=inet6, Modify=C, Port=25 - -To allow sendmail and the milter process to communicate with each -other over IPv6, you may use the "inet6" socket name prefix, as in: - -Xpythonfilter, S=inet6:1234@fec0:0:0:7::5c - -The connect() callback method in the milter class will pass the -IPv6-specific information in the 'hostaddr' argument as a tuple. Note -that the type of this value is dependent upon the protocol family, and -is not compatible with IPv4 connections. Therefore you should always -check the family argument before attempting to use the hostaddr -argument. A quick example showing this follows: - - import socket - ... - class ipv6awareMilter(Milter.Milter): - ... - def connect(self,hostname,family,hostaddr): - if family==socket.AF_INET: - ipaddress, port = hostaddr - elif family==socket.AF_INET6: - ip6address, port, flowinfo, scopeid = hostaddr - elif family==socket.AF_UNIX: - socketpath = hostaddr - -The hostname argument is always safe to use without interpreting the -protocol family. For IPv6 connections for which the hostname can not -be determined the hostname will appear similar to the string -"[IPv6:::1]" with the corresponding hostaddr[0] being "::1". Refer to -RFC 2553 for information on interpreting and using the flowinfo and -scopeid socket attributes, both of which are integers. - -Authors -------- - -Jim Niemira (urmane@urmane.org) wrote the original C module and some quick -and dirty python to use it. Stuart D. Gathman (stuart@bmsi.com) took that -kludge and added threading and context objects to it, wrote a proper OO -wrapper (Milter.py) that handles attachments, did lots of testing, packaged -it with distutils, and generally transformed it from a quick hack to a -real, usable Python extension. diff --git a/TODO b/TODO deleted file mode 100644 index 55d0edc..0000000 --- a/TODO +++ /dev/null @@ -1,81 +0,0 @@ -Defer TEMPERROR in SPF evaluation - give precedence to security -(only defer for PASS mechanisms). - -Option to add Received-SPF header, but never reject on SPF. - -Create null config that does nothing - except maybe add Received-SPF -headers. Many admins would like to turn features on one at a time. - -Checking in mime.py; -/bms/cvs/milter/mime.py,v <-- mime.py -new revision: 1.56; previous revision: 1.55 -done -Checking in spf.py; -/bms/cvs/milter/spf.py,v <-- spf.py -new revision: 1.18; previous revision: 1.17 -done -Checking in testmime.py; -/bms/cvs/milter/testmime.py,v <-- testmime.py -new revision: 1.19; previous revision: 1.18 - -Auto whitelist based on outgoing email - perhaps with magic subject -or recipient prefix. - -Can't output messages with malformed rfc822 attachments. - -Example malformed SPF: -onvunvuvvx.usafisnews.org text "v=spf1 mx ptr ip4:207.44.199.970 -all" - -Move milter,Milter,mime,spf modules to pymilter -milter package will have bms.py application - -Support SMTP AUTH and disable SPF checks when connection is authorized. -Web admin interface -Check valid domains allowed by internal senders to detect PCs infected -with spam trojans. -Do CBV (callback verification) for mail with no published SPF record. -message log for automated stats and blacklisting -Skip dspam when SPF pass? -Report 551 with rcpt on SPF fail? -check spam keywords with character classes, e.g. - {a}=[a@��], {i}=[i1�], {e}=[e�], {o}=[o0�] - -Implement RRS - a backdoor for non-SRS forwarders. User lists non-SRS -forwarder accounts, and a util provides a special local alias for the -user to give to the forwarder. Alias only works for mail from that -forwarder. Milter gets forwarder domain from alias and uses it to -SPF check forwarder. - -Another special dspam user, 'honeypot', can be listed in innoculations. -All email to those addresses is treated as known spam. - -Framework for modular Python milter components within a single VM. -Python milters can be already be composed through sendmail by running each in -a separate process. However, a significant amount of memory is wasted -for each additional Python VM, and communication between milters -is cumbersome (e.g., adding mail headers, writing external files). - -Backup copies for outgoing/incoming mail. - -Allow multiple wiretap groups, each with its own destination. Perhaps -also copy incoming wiretap mail, even though sendmail alias works perfectly -for the purpose, to avoid having to change two configs for a wiretap. - -Provide a way to reload milter.cfg without stopping/restarting milter. - -Allow selected Windows extensions for specific domains via milter.cfg - -Fix setup.py so that _FFR_QUARANTINE is automatically defined when -available in libmilter. - -Keep separate ismodified flag for headers and body. This is important -when rejecting outgoing mail with viruses removed (so as not to -embarrass yourself), and also removing Received headers with hidepath. - -Wrap smfi_setbacklog(int) - but it is only available in sendmail >= 8.12.3, - so how can we detect whether to wrap it? - -Need a test module to feed sample messages to a milter though a live -sendmail and SMTP. The mockup currently used is probably not very accurate, -and doesn't test the threading code. - diff --git a/bms.py b/bms.py deleted file mode 100644 index 150386e..0000000 --- a/bms.py +++ /dev/null @@ -1,1237 +0,0 @@ -#!/usr/bin/env python -# A simple milter that has grown quite a bit. -# $Log$ -# Revision 1.8 2005/06/06 18:24:59 customdesigned -# Properly log exceptions from pydspam -# -# Revision 1.7 2005/06/04 19:41:16 customdesigned -# Fix bugs from testing RPM -# -# Revision 1.6 2005/06/03 04:57:05 customdesigned -# Organize config reader by section. Create defang section. -# -# Revision 1.5 2005/06/02 15:00:17 customdesigned -# Configure banned extensions. Scan zipfile option with test case. -# -# Revision 1.4 2005/06/02 04:18:55 customdesigned -# Update copyright notices after reading article on /. -# -# Revision 1.3 2005/06/02 02:09:00 customdesigned -# Record timestamp in send_dsn.log -# -# Revision 1.2 2005/06/02 01:00:36 customdesigned -# Support configurable templates for DSNs. -# -# -# Revision 1.134 2005/05/25 15:36:43 stuart -# Use dynip module. -# Support smart aliasing of wiretap destination. -# Always send DSN for SOFTFAIL. -# Close forged bounce loophole when there are no headers. -# -# Revision 1.133 2005/03/16 21:58:04 stuart -# Auto DSN feature. -# -# Revision 1.132 2005/02/12 02:11:10 stuart -# Pass unit tests with python2.4. -# -# Revision 1.131 2005/02/11 18:34:13 stuart -# Handle garbage after quote in boundary. -# -# Revision 1.130 2005/02/10 01:10:58 stuart -# Fixed MimeMessage.ismodified() -# -# Revision 1.129 2005/02/10 00:56:48 stuart -# Runs with python2.4. Defang not working correctly - more work needed. -# -# Revision 1.128 2005/02/09 17:53:34 stuart -# Optionally run dspam on internal mail. -# -# Revision 1.127 2004/12/03 14:26:21 stuart -# Mark DYN PTR, REJECT softfail, log Received-SPF from trusted MTA. -# -# Revision 1.126 2004/11/24 14:39:38 stuart -# Also accept softfail if valid PTR or HELO. -# -# Revision 1.125 2004/11/19 16:40:14 stuart -# Block softfail except for listed domains. -# -# Revision 1.124 2004/11/19 06:18:04 stuart -# block softfail for configured domains only -# -# Revision 1.123 2004/11/18 20:36:49 stuart -# Recognize more dynamic hosts. Ignore dynamic PTR for best_guess. -# -# Revision 1.122 2004/11/18 17:16:10 stuart -# Recognize more dynamic ips. -# -# Revision 1.121 2004/11/09 22:37:48 stuart -# Don't accept helo names which are dynamic IP addresses. -# -# Revision 1.120 2004/11/09 20:33:50 stuart -# Recognize more dynamic PTR variations. -# -# Revision 1.118 2004/08/30 21:19:50 stuart -# Try best guess for HELO, expand setreply for common errors -# -# Revision 1.117 2004/08/23 02:27:53 stuart -# Allow multi rcpt CBV. Add some multiline replies. -# -# Revision 1.116 2004/08/20 22:27:52 stuart -# Generate TEMPFAIL for SPF softfail. -# -# Revision 1.115 2004/08/19 20:55:49 stuart -# Always show reversed SRS path. -# Check if encodings are an ASCII superset. Some messages were encoded as -# BIG5 and getting rejected even though chars were all in ascii subset. -# -# Revision 1.114 2004/07/27 00:40:12 stuart -# Make reject on no PTR optional. -# -# Revision 1.113 2004/07/23 23:11:14 stuart -# Log known malformed messages differently than general processing exceptions. -# -# Revision 1.112 2004/07/21 19:18:33 stuart -# Punt on UnicodeDecodeError when decoding headers. -# Accept a pass with default SPF for missing reverse IP. -# -# Revision 1.111 2004/07/18 13:13:31 stuart -# Reject invalid SRS only for SRS domain (which is the only one we -# know the key for). -# Reject senders that have neither reverse IP nor SPF. -# -# Revision 1.110 2004/06/12 03:13:18 stuart -# Block bounces only for SRS domain. Also treat mail from -# postmaster or mailer-daemon as DSN for SRS/SES checking purposes. -# -# Revision 1.109 2004/05/01 02:56:55 stuart -# Let multiple screeners share work. -# -# Revision 1.108 2004/04/29 20:36:23 stuart -# Require HELO name -# -# Revision 1.107 2004/04/24 22:55:29 stuart -# Move some files to make the RPM more standard. -# -# Revision 1.106 2004/04/21 18:29:08 stuart -# Validate hello name with SPF. -# -# Revision 1.105 2004/04/20 15:16:00 stuart -# Release 0.6.9 -# -# Revision 1.104 2004/04/19 21:56:26 stuart -# Support SPF best_guess and get_header -# -# Revision 1.103 2004/04/10 02:31:01 stuart -# Fix timeout config -# -# Revision 1.102 2004/04/08 20:25:11 stuart -# Make libmilter timeout a config option -# -# Revision 1.101 2004/04/08 19:18:16 stuart -# Preserve case of local part in sender -# -# Revision 1.100 2004/04/08 18:41:15 stuart -# Reject numeric hello names -# -# Revision 1.99 2004/04/06 19:46:39 stuart -# Reject invalid SRS immediately for benefit of CallBack Verifiers. -# -# Revision 1.98 2004/04/06 15:28:20 stuart -# Release 0.6.8-2 -# -# Revision 1.97 2004/04/06 13:07:43 stuart -# Pass original header name to check_header -# -# Revision 1.96 2004/04/06 03:27:03 stuart -# bugs from Redhat 9 testing -# -# Revision 1.95 2004/04/05 22:37:08 stuart -# Include Received-SPF headers in dspam. -# -# Revision 1.94 2004/04/05 22:16:50 stuart -# Separate check_header method taking decoded header. -# Reject multiple recipients for a bounce. -# -# Revision 1.93 2004/04/01 20:57:45 stuart -# Report only SRS like addresses as spoofed. -# Return TEMPFAIL on SPF error. -# -# Revision 1.92 2004/03/25 17:45:53 stuart -# Make spf_reject_neutral global in bms.py -# -# Revision 1.91 2004/03/25 03:38:02 stuart -# Reject neutral SPF result for selected domains. -# -# Revision 1.90 2004/03/25 03:27:33 stuart -# Support delegation of SPF records. -# -# Revision 1.89 2004/03/23 22:02:49 stuart -# Header decoding bug. -# -# Revision 1.88 2004/03/23 05:08:45 stuart -# Decode headers, indirect srs config. -# -# Revision 1.87 2004/03/18 02:21:16 stuart -# SRS checking -# -# Revision 1.86 2004/03/11 05:00:37 stuart -# Don't wipe out fail messages from SPF records. -# Hello blacklist -# -# Revision 1.85 2004/03/10 01:49:22 stuart -# Enhanced SPF support. -# -# Revision 1.84 2004/03/09 17:04:49 stuart -# Received-SPF header. -# -# Revision 1.83 2004/03/08 20:23:26 stuart -# SPF support -# -# Revision 1.82 2004/03/01 18:56:50 stuart -# Support progress reporting. -# -# Revision 1.81 2004/03/01 18:36:09 stuart -# Trusted relay. -# -# Revision 1.80 2004/01/12 21:10:58 stuart -# Support wildcard user for smart_alias -# -# Revision 1.79 2003/12/04 23:46:06 stuart -# Release 0.6.4 -# -# Author: Stuart D. Gathman <stuart@bmsi.com> -# Copyright 2001,2002,2003,2004,2005 Business Management Systems, Inc. -# This code is under the GNU General Public License. See COPYING for details. - -import sys -import os -import StringIO -import rfc822 -import mime -import email.Errors -import Milter -import tempfile -import traceback -import ConfigParser -import time -import re -import Milter.dsn as dsn -from Milter.dynip import is_dynip as dynip - -from fnmatch import fnmatchcase -from email.Header import decode_header - -# Import pysrs if available -try: - import SRS - srsre = re.compile(r'^SRS[01][+-=]',re.IGNORECASE) -except: SRS = None - -# Import spf if available -try: import spf -except: spf = None - -ip4re = re.compile(r'^[1-9][0-9]*\.[1-9][0-9]*\.[1-9][0-9]*\.[1-9][0-9]*$') -#import syslog -#syslog.openlog('milter') - -# Thanks to Chris Liechti for config parsing suggestions - -# Global configuration defaults suitable for test framework. -socketname = "/tmp/pythonsock" -reject_virus_from = () -wiretap_users = {} -discard_users = {} -wiretap_dest = None -blind_wiretap = True -check_user = {} -block_forward = {} -hide_path = () -log_headers = False -block_chinese = False -spam_words = () -porn_words = () -banned_exts = mime.extlist.split(',') -scan_zip = False -scan_html = True -scan_rfc822 = True -internal_connect = () -trusted_relay = () -internal_domains = () -hello_blacklist = () -smart_alias = {} -dspam_dict = None -dspam_users = {} -dspam_userdir = None -dspam_exempt = {} -dspam_whitelist = {} -dspam_screener = () -dspam_internal = True # True if internal mail should be dspammed -dspam_reject = () -dspam_sizelimit = 180000 -srs = None -srs_reject_spoofed = False -srs_fwdomain = None -spf_reject_neutral = () -spf_accept_softfail = () -spf_best_guess = False -spf_reject_noptr = False -multiple_bounce_recipients = True -time_format = '%Y%b%d %H:%M:%S %Z' -timeout = 600 -cbv_cache = {} -try: - too_old = time.time() - 30*24*60*60 # 30 days - for ln in open('send_dsn.log'): - try: - rcpt,ts = ln.strip().split(None,1) - l = time.strptime(ts,time_format) - t = time.mktime(l) - if t > too_old: - cbv_cache[rcpt] = None - except: - cbv_cache[ln.strip()] = None -except IOError: pass - -class MilterConfigParser(ConfigParser.ConfigParser): - - def getlist(self,sect,opt): - if self.has_option(sect,opt): - return [q.strip() for q in self.get(sect,opt).split(',')] - return [] - - def getaddrset(self,sect,opt): - if not self.has_option(sect,opt): - return {} - s = self.get(sect,opt) - d = {} - for q in s.split(','): - q = q.strip() - if q.startswith('file:'): - domain = q[5:] - d[domain] = d.setdefault(domain,[]) + open(domain,'r').read().split() - else: - user,domain = q.split('@') - d.setdefault(domain,[]).append(user) - return d - - def getaddrdict(self,sect,opt): - if not self.has_option(sect,opt): - return {} - d = {} - for q in self.get(sect,opt).split(','): - q = q.strip() - if self.has_option(sect,q): - l = self.get(sect,q) - for addr in l.split(','): - addr = addr.strip() - if addr.startswith('file:'): - fname = addr[5:] - for a in open(fname,'r').read().split(): - d[a] = q - else: - d[addr] = q - return d - - def getdefault(self,sect,opt,default=None): - if self.has_option(sect,opt): - return self.get(sect,opt) - return default - -def read_config(list): - cp = MilterConfigParser({ - 'tempdir': "/var/log/milter/save", - 'socket': "/var/run/milter/pythonsock", - 'timeout': '600', - 'scan_html': 'no', - 'scan_rfc822': 'yes', - 'scan_zip': 'no', - 'block_chinese': 'no', - 'log_headers': 'no', - 'blind_wiretap': 'yes', - 'maxage': '8', - 'hashlength': '8', - 'reject_spoofed': 'no', - 'reject_noptr': 'no', - 'best_guess': 'no', - 'dspam_internal': 'yes' - }) - cp.read(list) - - # milter section - tempfile.tempdir = cp.get('milter','tempdir') - global socketname, timeout, check_user, log_headers - global internal_connect, internal_domains, trusted_relay, hello_blacklist - socketname = cp.get('milter','socket') - timeout = cp.getint('milter','timeout') - check_user = cp.getaddrset('milter','check_user') - log_headers = cp.getboolean('milter','log_headers') - internal_connect = cp.getlist('milter','internal_connect') - internal_domains = cp.getlist('milter','internal_domains') - trusted_relay = cp.getlist('milter','trusted_relay') - hello_blacklist = cp.getlist('milter','hello_blacklist') - - # defang section - global scan_rfc822, scan_html, block_chinese, scan_zip, block_forward - global banned_exts, porn_words, spam_words - if cp.has_section('defang'): - section = 'defang' - # for backward compatibility, - # banned extensions defaults to empty only when defang section exists - banned_exts = cp.getlist(section,'banned_exts') - else: # use milter section if no defang section for compatibility - section = 'milter' - scan_rfc822 = cp.getboolean(section,'scan_rfc822') - scan_zip = cp.getboolean(section,'scan_zip') - scan_html = cp.getboolean(section,'scan_html') - block_chinese = cp.getboolean(section,'block_chinese') - block_forward = cp.getaddrset(section,'block_forward') - porn_words = cp.getlist(section,'porn_words') - spam_words = cp.getlist(section,'spam_words') - - # scrub section - global hide_path, reject_virus_from - hide_path = cp.getlist('scrub','hide_path') - reject_virus_from = cp.getlist('scrub','reject_virus_from') - - # wiretap section - global blind_wiretap, wiretap_users, wiretap_dest, discard_users - blind_wiretap = cp.getboolean('wiretap','blind') - wiretap_users = cp.getaddrset('wiretap','users') - discard_users = cp.getaddrset('wiretap','discard') - wiretap_dest = cp.getdefault('wiretap','dest') - if wiretap_dest: wiretap_dest = '<%s>' % wiretap_dest - - global smart_alias - for sa in cp.getlist('wiretap','smart_alias'): - sm = cp.getlist('wiretap',sa) - if len(sm) < 2: - print 'malformed smart alias:',sa - continue - if len(sm) == 2: sm.append(sa) - key = (sm[0],sm[1]) - smart_alias[key] = sm[2:] - - # dspam section - global dspam_dict, dspam_users, dspam_userdir, dspam_exempt, dspam_internal - global dspam_screener,dspam_whitelist,dspam_reject,dspam_sizelimit - dspam_dict = cp.getdefault('dspam','dspam_dict') - dspam_exempt = cp.getaddrset('dspam','dspam_exempt') - dspam_whitelist = cp.getaddrset('dspam','dspam_whitelist') - dspam_users = cp.getaddrdict('dspam','dspam_users') - dspam_userdir = cp.getdefault('dspam','dspam_userdir') - dspam_screener = cp.getlist('dspam','dspam_screener') - dspam_reject = cp.getlist('dspam','dspam_reject') - dspam_internal = cp.getboolean('dspam','dspam_internal') - if cp.has_option('dspam','dspam_sizelimit'): - dspam_sizelimit = cp.getint('dspam','dspam_sizelimit') - - # spf section - global spf_reject_neutral,spf_best_guess,SRS,spf_reject_noptr - global spf_accept_softfail - if spf: - spf.DELEGATE = cp.getdefault('spf','delegate') - spf_reject_neutral = cp.getlist('spf','reject_neutral') - spf_accept_softfail = cp.getlist('spf','accept_softfail') - spf_best_guess = cp.getboolean('spf','best_guess') - spf_reject_noptr = cp.getboolean('spf','reject_noptr') - srs_config = cp.getdefault('srs','config') - if srs_config: cp.read([srs_config]) - srs_secret = cp.getdefault('srs','secret') - if SRS and srs_secret: - global srs,srs_reject_spoofed,srs_fwdomain - database = cp.getdefault('srs','database') - srs_reject_spoofed = cp.getboolean('srs','reject_spoofed') - maxage = cp.getint('srs','maxage') - hashlength = cp.getint('srs','hashlength') - separator = cp.getdefault('srs','separator','=') - if database: - import SRS.DB - srs = SRS.DB.DB(database=database,secret=srs_secret, - maxage=maxage,hashlength=hashlength,separator=separator) - else: - srs = SRS.Guarded.Guarded(secret=srs_secret, - maxage=maxage,hashlength=hashlength,separator=separator) - srs_fwdomain = cp.getdefault('srs','fwdomain') - -def parse_addr(t): - if t.startswith('<') and t.endswith('>'): t = t[1:-1] - return t.split('@') - -def parse_header(val): - h = decode_header(val) - if not len(h) or (not h[0][1] and len(h) == 1): return val - try: - u = [] - for s,enc in h: - if enc: - try: - u.append(unicode(s,enc)) - except LookupError: - u.append(unicode(s)) - else: - u.append(unicode(s)) - u = ''.join(u) - for enc in ('us-ascii','iso-8859-1','utf8'): - try: - return u.encode(enc) - except UnicodeError: continue - except UnicodeDecodeError: pass - except LookupError: pass - return val - -class bmsMilter(Milter.Milter): - """Milter to replace attachments poisonous to Windows with a WARNING message, - check SPF, and other anti-forgery features, and implement wiretapping - and smart alias redirection.""" - - def log(self,*msg): - print "%s [%d]" % (time.strftime('%Y%b%d %H:%M:%S'),self.id), - for i in msg: print i, - print - - def __init__(self): - self.tempname = None - self.mailfrom = None # sender in SMTP form - self.canon_from = None # sender in end user form - self.fp = None - self.bodysize = 0 - self.id = Milter.uniqueID() - - # delrcpt can only be called from eom(). This accumulates recipient - # changes which can then be applied by alter_recipients() - def del_recipient(self,rcpt): - rcpt = rcpt.lower() - if not rcpt in self.discard_list: - self.discard_list.append(rcpt) - - # addrcpt can only be called from eom(). This accumulates recipient - # changes which can then be applied by alter_recipients() - def add_recipient(self,rcpt): - rcpt = rcpt.lower() - if not rcpt in self.redirect_list: - self.redirect_list.append(rcpt) - - # addheader can only be called from eom(). This accumulates added headers - # which can then be applied by alter_headers() - def add_header(self,name,val): - self.new_headers.append((name,val)) - self.log('%s: %s' % (name,val)) - - def connect(self,hostname,unused,hostaddr): - self.internal_connection = False - self.trusted_relay = False - self.receiver = self.getsymval('j') - if hostaddr and len(hostaddr) > 0: - ipaddr = hostaddr[0] - for pat in internal_connect: - if fnmatchcase(ipaddr,pat): - self.internal_connection = True - break - for pat in trusted_relay: - if fnmatchcase(ipaddr,pat): - self.trusted_relay = True - break - else: ipaddr = '' - self.connectip = ipaddr - self.missing_ptr = dynip(hostname,self.connectip) - for pat in internal_connect: - if fnmatchcase(hostname,pat): - self.internal_connection = True - break - if self.internal_connection: - connecttype = 'INTERNAL' - else: - connecttype = 'EXTERNAL' - if self.trusted_relay: - connecttype += ' TRUSTED' - if self.missing_ptr: - connecttype += ' DYN' - self.log("connect from %s at %s %s" % (hostname,hostaddr,connecttype)) - self.hello_name = None - self.connecthost = hostname - if hostname == 'localhost' and not ipaddr.startswith('127.') \ - or hostname == '.': - self.log("REJECT: PTR is",hostname) - self.setreply('550','5.7.1', '"%s" is not a reasonable PTR name'%hostname) - return Milter.REJECT - return Milter.CONTINUE - - def hello(self,hostname): - self.hello_name = hostname - self.log("hello from %s" % hostname) - if ip4re.match(hostname): - self.log("REJECT: numeric hello name:",hostname) - self.setreply('550','5.7.1','hello name cannot be numeric ip') - return Milter.REJECT - if not self.internal_connection and hostname in hello_blacklist: - self.log("REJECT: spam from self:",hostname) - self.setreply('550','5.7.1','I hate talking to myself.') - return Milter.REJECT - return Milter.CONTINUE - - def smart_alias(self,to): - if smart_alias: - t = parse_addr(to.lower()) - if len(t) == 2: - ct = '@'.join(t) - else: - ct = t[0] - cf = self.canon_from - cf0 = cf.split('@',1) - if len(cf0) == 2: - cf0 = '@' + cf0[1] - else: - cf0 = cf - for key in ((cf,ct),(cf0,ct)): - if smart_alias.has_key(key): - self.del_recipient(to) - for t in smart_alias[key]: - self.add_recipient('<%s>'%t) - - # multiple messages can be received on a single connection - # envfrom (MAIL FROM in the SMTP protocol) seems to mark the start - # of each message. - def envfrom(self,f,*str): - self.log("mail from",f,str) - self.fp = StringIO.StringIO() - self.tempname = None - self.mailfrom = f - self.forward = True - self.bodysize = 0 - self.hidepath = False - self.discard = False - self.dspam = True - self.reject_spam = True - self.data_allowed = True - self.trust_received = self.trusted_relay - self.trust_spf = self.trusted_relay - self.redirect_list = [] - self.discard_list = [] - self.new_headers = [] - self.recipients = [] - self.cbv_needed = None - t = parse_addr(f.lower()) - self.canon_from = '@'.join(t) - self.fp.write('From %s %s\n' % (self.canon_from,time.ctime())) - if len(t) == 2: - user,domain = t - if not self.internal_connection: - for pat in internal_domains: - if fnmatchcase(domain,pat): - self.log("REJECT: spam from self",pat) - self.setreply('550','5.7.1','I hate talking to myself.') - return Milter.REJECT - elif internal_domains: - for pat in internal_domains: - if fnmatchcase(domain,pat): break - else: - self.log("REJECT: zombie PC at ",self.connectip," sending MAIL FROM ", - self.canon_from) - self.setreply('550','5.7.1','Get rid of your virus!', - 'Your PC is using an unauthorized MAIL FROM.', - 'It is either badly misconfigured or controlled by organized crime.' - ) - return Milter.REJECT - self.rejectvirus = domain in reject_virus_from - if user in wiretap_users.get(domain,()): - self.add_recipient(wiretap_dest) - self.smart_alias(wiretap_dest) - if user in discard_users.get(domain,()): - self.discard = True - exempt_users = dspam_whitelist.get(domain,()) - if user in exempt_users or '' in exempt_users: - self.dspam = False - else: - self.rejectvirus = False - if not self.hello_name: - self.log("REJECT: missing HELO") - self.setreply('550','5.7.1',"It's polite to say HELO first.") - return Milter.REJECT - if not (self.internal_connection or self.trusted_relay) \ - and self.connectip and spf: - return self.check_spf() - if self.internal_connection: - pass - return Milter.CONTINUE - - def check_spf(self): - t = parse_addr(self.mailfrom) - if len(t) == 2: t[1] = t[1].lower() - receiver = self.receiver - q = spf.query(self.connectip,'@'.join(t),self.hello_name,receiver=receiver) - q.set_default_explanation('SPF fail: see http://spf.pobox.com/why.html') - res,code,txt = q.check() - if res in ('none', 'softfail'): - if self.mailfrom != '<>': - # check hello name via spf - h = spf.query(self.connectip,'',self.hello_name,receiver=receiver) - hres,hcode,htxt = h.check() - if hres in ('deny','fail','neutral','softfail'): - self.log('REJECT: hello SPF: %s 550 %s' % (hres,htxt)) - self.setreply('550','5.7.1',htxt, - "The hostname given in your MTA's HELO response is not listed", - "as a legitimate MTA in the SPF records for your domain. If you", - "get this bounce, the message was not in fact a forgery, and you", - "should IMMEDIATELY notify your email administrator of the problem." - ) - return Milter.REJECT - if hres == 'none' and spf_best_guess \ - and not dynip(self.hello_name,self.connectip): - hres,hcode,htxt = h.best_guess() - else: hres = res - if spf_best_guess and res == 'none': - #self.log('SPF: no record published, guessing') - q.set_default_explanation( - 'SPF guess: see http://spf.pobox.com/why.html') - # best_guess should not result in fail - if self.missing_ptr: - # ignore dynamic PTR for best guess - res,code,txt = q.best_guess('v=spf1 a/24 mx/24') - else: - res,code,txt = q.best_guess() - receiver += ': guessing' - if self.missing_ptr and res in ('neutral', 'none') and hres != 'pass': - if spf_reject_noptr: - self.log('REJECT: no PTR, HELO or SPF') - self.setreply('550','5.7.1', - 'You must have a reverse lookup or publish SPF: http://spf.pobox.com', - 'Contact your mail administrator IMMEDIATELY! Your mail server is', - 'severely misconfigured. It has no PTR record (dynamic PTR records', - "that contain your IP don't count), an invalid HELO, and no SPF record." - ) - return Milter.REJECT - if self.mailfrom != '<>': - q.result = res - self.cbv_needed = q - if res in ('deny', 'fail'): - self.log('REJECT: SPF %s %i %s' % (res,code,txt)) - self.setreply(str(code),'5.7.1',txt) - # A proper SPF fail error message would read: - # forger.biz [1.2.3.4] is not allowed to send mail with the domain - # "forged.org" in the sender address. Contact <postmaster@forged.org>. - return Milter.REJECT - if res == 'softfail' and not q.o in spf_accept_softfail: - if self.missing_ptr and hres != 'pass': - if spf_reject_noptr or q.o in spf_reject_neutral: - self.log('REJECT: SPF %s %i %s' % (res,code,txt)) - self.setreply('550','5.7.1', - 'SPF softfail: If you get this Delivery Status Notice, your email', - 'was probably legitimate. Your administrator has published SPF', - 'records in a testing mode. The SPF record reported your email as', - 'a forgery, which is a mistake if you are reading this. Please', - 'notify your administrator of the problem immediately.' - ) - return Milter.REJECT - if self.mailfrom != '<>': - q.result = res - self.cbv_needed = q - if res == 'neutral' and q.o in spf_reject_neutral: - self.log('REJECT: SPF neutral for',q.s) - self.setreply('550','5.7.1', - 'mail from %s must pass SPF: http://spf.pobox.com/why.html' % q.o, - 'The %s domain is one that spammers love to forge. Due to' % q.o, - 'the volume of forged mail, we can only accept mail that', - 'the SPF record for %s explicitly designates as legitimate.' % q.o, - 'Sending your email through the recommended outgoing SMTP', - 'servers for %s should accomplish this.' % q.o - ) - return Milter.REJECT - if res == 'error': - if code >= 500: - self.log('REJECT: SPF %s %i %s' % (res,code,txt)) - # latest SPF draft recommends 5.5.2 instead of 5.7.1 - self.setreply(str(code),'5.5.2',txt) - return Milter.REJECT - self.log('TEMPFAIL: SPF %s %i %s' % (res,code,txt)) - self.setreply(str(code),'4.3.0',txt) - return Milter.TEMPFAIL - self.add_header('Received-SPF',q.get_header(res,receiver)) - return Milter.CONTINUE - - # hide_path causes a copy of the message to be saved - until we - # track header mods separately from body mods - so use only - # in emergencies. - def envrcpt(self,to,*str): - # mail to MAILER-DAEMON is generally spam that bounced - if to.startswith('<MAILER-DAEMON@'): - self.log('DISCARD: RCPT TO:',to,str) - return Milter.DISCARD - self.log("rcpt to",to,str) - t = parse_addr(to.lower()) - if len(t) == 2: - user,domain = t - if self.mailfrom == '<>' or self.canon_from.startswith('postmaster@') \ - or self.canon_from.startswith('mailer-daemon@'): - if self.recipients and not multiple_bounce_recipients: - self.data_allowed = False - if srs and domain == srs_fwdomain: - oldaddr = '@'.join(parse_addr(to)) - try: - newaddr = srs.reverse(oldaddr) - # Currently, a sendmail map reverses SRS. We just log it here. - self.log("srs rcpt:",newaddr) - except: - if not (self.internal_connection or self.trusted_relay): - if srsre.match(oldaddr): - self.log("REJECT: srs spoofed:",oldaddr) - self.setreply('550','5.7.1','Invalid SRS signature') - return Milter.REJECT - self.data_allowed = not srs_reject_spoofed - # non DSN mail to SRS address will bounce due to invalid local part - self.recipients.append('@'.join(t)) - users = check_user.get(domain) - if self.discard: - self.del_recipient(to) - if users and not user in users: - self.log('REJECT: RCPT TO:',to) - return Milter.REJECT - if user in block_forward.get(domain,()): - self.forward = False - exempt_users = dspam_exempt.get(domain,()) - if user in exempt_users or '' in exempt_users: - self.dspam = False - if domain in hide_path: - self.hidepath = True - if not domain in dspam_reject: - self.reject_spam = False - self.smart_alias(to) - #rcpt = self.getsymval("{rcpt_addr}") - #self.log("rcpt-addr",rcpt); - return Milter.CONTINUE - - # Heuristic checks for spam headers - def check_header(self,name,val): - lname = name.lower() - # val is decoded header value - if lname == 'subject': - - # check for common spam keywords - for wrd in spam_words: - if val.find(wrd) >= 0: - self.log('REJECT: %s: %s' % (name,val)) - self.setreply('550','5.7.1','That subject is not allowed') - return Milter.REJECT - - # check for spam that claims to be legal - lval = val.lower().strip() - for adv in ("adv:","adv.","adv ","[adv]","(adv)","advt:","advert:"): - if lval.startswith(adv): - self.log('REJECT: %s: %s' % (name,val)) - self.setreply('550','5.7.1','Advertising not accepted here') - return Milter.REJECT - for adv in ("adv","(adv)","[adv]"): - if lval.endswith(adv): - self.log('REJECT: %s: %s' % (name,val)) - self.setreply('550','5.7.1','Advertising not accepted here') - return Milter.REJECT - - # check for porn keywords - for w in porn_words: - if lval.find(w) >= 0: - self.log('REJECT: %s: %s' % (name,val)) - self.setreply('550','5.7.1','That subject is not allowed') - return Milter.REJECT - - # check for annoying forwarders - if not self.forward: - if lval.startswith("fwd:") or lval.startswith("[fw"): - self.log('REJECT: %s: %s' % (name,val)) - self.setreply('550','5.7.1','I find unedited forwards annoying') - return Milter.REJECT - - # check for invalid message id - if lname == 'message-id' and len(val) < 4: - self.log('REJECT: %s: %s' % (name,val)) - return Milter.REJECT - - # check for common bulk mailers - if lname == 'x-mailer': - mailer = val.lower() - if mailer in ('direct email','calypso','mail bomber') \ - or mailer.find('optin') >= 0: - self.log('REJECT: %s: %s' % (name,val)) - return Milter.REJECT - elif self.trust_received and lname == 'received': - self.trust_received = False - self.log('%s: %s' % (name,val.splitlines()[0])) - elif self.trust_spf and lname == 'received-spf': - self.trust_spf = False - self.log('%s: %s' % (name,val.splitlines()[0])) - return Milter.CONTINUE - - def forged_bounce(self): - if len(self.recipients) > 1: - self.log('REJECT: Multiple bounce recipients') - self.setreply('550','5.7.1','Multiple bounce recipients') - else: - self.log('REJECT: bounce with no SRS encoding') - self.setreply('550','5.7.1', - "I did not send you that message. Please consider implementing SPF", - "(http://spf.pobox.com) to avoid bouncing mail to spoofed senders.", - "Thank you." - ) - return Milter.REJECT - - def header(self,name,hval): - if not self.data_allowed: - return self.forged_bounce() - - lname = name.lower() - # decode near ascii text to unobfuscate - val = parse_header(hval) - if not self.internal_connection: - # even if we wanted the Taiwanese spam, we can't read Chinese - if block_chinese and lname == 'subject': - if val.startswith('=?big5') or val.startswith('=?ISO-2022-JP'): - self.log('REJECT: %s: %s' % (name,val)) - self.setreply('550','5.7.1',"We don't understand chinese") - return Milter.REJECT - rc = self.check_header(name,val) - if rc != Milter.CONTINUE: return rc - # log selected headers - if log_headers or lname in ('subject','x-mailer'): - self.log('%s: %s' % (name,val)) - if self.fp: - try: - val = val.encode('us-ascii') - except: - val = hval - self.fp.write("%s: %s\n" % (name,val)) # add header to buffer - return Milter.CONTINUE - - def eoh(self): - if not self.fp: return Milter.TEMPFAIL # not seen by envfrom - if not self.data_allowed: - return self.forged_bounce() - for name,val in self.new_headers: - self.fp.write("%s: %s\n" % (name,val)) # add new headers to buffer - self.fp.write("\n") # terminate headers - self.fp.seek(0) - # copy headers to a temp file for scanning the body - headers = self.fp.getvalue() - self.fp.close() - fd,fname = tempfile.mkstemp(".defang") - self.tempname = fname - self.fp = os.fdopen(fd,"w+b") - self.fp.write(headers) # IOError (e.g. disk full) causes TEMPFAIL - # check if headers are really spammy - if dspam_dict and not self.internal_connection: - ds = dspam.dspam(dspam_dict,dspam.DSM_PROCESS, - dspam.DSF_CHAINED|dspam.DSF_CLASSIFY) - try: - ds.process(headers) - if ds.probability > 0.93 and self.dspam: - self.log('REJECT: X-DSpam-HeaderScore: %f' % ds.probability) - self.setreply('550','5.7.1','Your Message looks spammy') - return Milter.REJECT - self.add_header('X-DSpam-HeaderScore','%f'%ds.probability) - finally: - ds.destroy() - return Milter.CONTINUE - - def body(self,chunk): # copy body to temp file - if self.fp: - self.fp.write(chunk) # IOError causes TEMPFAIL in milter - self.bodysize += len(chunk) - return Milter.CONTINUE - - def _headerChange(self,msg,name,value): - if value: # add header - self.addheader(name,value) - else: # delete all headers with name - h = msg.getheaders(name) - if h: - for i in range(len(h),0,-1): - self.chgheader(name,i-1,'') - - def _chk_ext(self,name): - "Check a name for dangerous Winblows extensions." - if not name: return name - lname = name.lower() - for ext in self.bad_extensions: - if lname.endswith(ext): return name - return None - - - def _chk_attach(self,msg): - "Filter attachments by content." - # check for bad extensions - mime.check_name(msg,self.tempname,ckname=self._chk_ext,scan_zip=scan_zip) - # remove scripts from HTML - if scan_html: - mime.check_html(msg,self.tempname) - # don't let a tricky virus slip one past us - if scan_rfc822: - msg = msg.get_submsg() - if isinstance(msg,email.Message.Message): - return mime.check_attachments(msg,self._chk_attach) - return Milter.CONTINUE - - def alter_recipients(self,discard_list,redirect_list): - for rcpt in discard_list: - if rcpt in redirect_list: continue - self.log("DISCARD RCPT: %s" % rcpt) # log discarded rcpt - self.delrcpt(rcpt) - for rcpt in redirect_list: - if rcpt in discard_list: continue - self.log("APPEND RCPT: %s" % rcpt) # log appended rcpt - self.addrcpt(rcpt) - if not blind_wiretap: - self.addheader('Cc',rcpt) - - # check spaminess for recipients in dictionary groups - # if there are multiple users getting dspammed, then - # a signature tag for each is added to the message. - - # FIXME: quarantine messages rejected via fixed patterns above - # this will give a fast start to stats - - def check_spam(self): - if not dspam_userdir: return False - ds = Dspam.DSpamDirectory(dspam_userdir) - ds.log = self.log - ds.headerchange = self._headerChange - modified = False - for rcpt in self.recipients: - if dspam_users.has_key(rcpt): - user = dspam_users.get(rcpt) - if user: - try: - self.fp.seek(0) - txt = self.fp.read() - if user == 'spam' and self.internal_connection: - sender = dspam_users.get(self.canon_from) - if sender: - self.log("SPAM: %s" % sender) # log user for FP - ds.add_spam(sender,txt) - txt = None - self.fp = None - return False - elif user == 'falsepositive' and self.internal_connection: - sender = dspam_users.get(self.canon_from) - if sender: - self.log("FP: %s" % sender) # log user for FP - txt = ds.false_positive(sender,txt) - self.fp = StringIO.StringIO(txt) - self.delrcpt('<%s>' % rcpt) - self.recipients = None - self.rejectvirus = False - return True - elif not self.internal_connection or dspam_internal: - if len(txt) > dspam_sizelimit: - self.log("Large message:",len(txt)) - return False - txt = ds.check_spam(user,txt,self.recipients) - if not txt: - # DISCARD if quarrantined for any recipient. It - # will be resent to all recipients if they submit - # as a false positive. - self.log("DSPAM:",user,rcpt) - self.fp = None - return False - self.fp = StringIO.StringIO(txt) - modified = True - except Exception,x: - self.log("check_spam:",x) - traceback.print_exc() - # screen if no recipients are dspam_users - if not modified and dspam_screener and not self.internal_connection \ - and self.dspam: - self.fp.seek(0) - txt = self.fp.read() - if len(txt) > dspam_sizelimit: - self.log("Large message:",len(txt)) - return False - screener = dspam_screener[self.id % len(dspam_screener)] - if not ds.check_spam(screener,txt,self.recipients, - classify=True,quarantine=not self.reject_spam): - self.fp = None - if self.reject_spam: - self.log("DSPAM:",screener, - 'REJECT: X-DSpam-Score: %f' % ds.probability) - self.setreply('550','5.7.1','Your Message looks spammy') - return True - self.log("DSPAM:",screener,"SCREENED") - return modified - - def eom(self): - if not self.fp: - return Milter.ACCEPT # no message collected - so no eom processing - - try: - # analyze external mail for spam - spam_checked = self.check_spam() # tag or quarantine for spam - if not self.fp: - if spam_checked: return Milter.REJECT - return Milter.DISCARD # message quarantined for all recipients - - # analyze all mail for dangerous attachments and scripts - self.fp.seek(0) - msg = mime.message_from_file(self.fp) - # pass header changes in top level message to sendmail - msg.headerchange = self._headerChange - - # filter leaf attachments through _chk_attach - assert not msg.ismodified() - self.bad_extensions = ['.' + x for x in banned_exts] - rc = mime.check_attachments(msg,self._chk_attach) - except: # milter crashed trying to analyze mail - exc_type,exc_value = sys.exc_info()[0:2] - if dspam_userdir and exc_type == dspam.error: - if not exc_value.strerror: - exc_value.strerror = exc_value.args[0] - if exc_value.strerror == 'Lock failed': - self.log("LOCK: BUSY") # log filename - self.setreply('450','4.2.0', - 'Too busy discarding spam. Please try again later.') - return Milter.TEMPFAIL - fname = tempfile.mktemp(".fail") # save message that caused crash - os.rename(self.tempname,fname) - self.tempname = None - if exc_type == email.Errors.BoundaryError: - self.log("MALFORMED: %s" % fname) # log filename - if self.internal_connection: - # accept anyway for now - return Milter.ACCEPT - self.setreply('554','5.7.7', - 'Boundary error in your message, are you a spammer?') - return Milter.REJECT - if exc_type == email.Errors.HeaderParseError: - self.log("MALFORMED: %s" % fname) # log filename - self.setreply('554','5.7.7', - 'Header parse error in your message, are you a spammer?') - return Milter.REJECT - # let default exception handler print traceback and return 451 code - self.log("FAIL: %s" % fname) # log filename - raise - if rc == Milter.REJECT: return rc; - if rc == Milter.DISCARD: return rc; - - if rc == Milter.CONTINUE: rc = Milter.ACCEPT # for testbms.py compat - - defanged = msg.ismodified() - - if self.hidepath: del msg['Received'] - - if self.recipients == None: - # false positive being recirculated - self.recipients = msg.get_all('x-dspam-recipients',[]) - if self.recipients: - for rcptlist in self.recipients: - for rcpt in rcptlist.split(','): - self.addrcpt('<%s>' % rcpt.strip()) - del msg['x-dspam-recipients'] - else: - self.addrcpt(self.mailfrom) - else: - self.alter_recipients(self.discard_list,self.redirect_list) - for name,val in self.new_headers: - self.addheader(name,val) - - if self.cbv_needed: - q = self.cbv_needed - sender = q.s - cached = cbv_cache.has_key(sender) - if cached: - self.log('CBV:',sender,'(cached)') - res = cbv_cache[sender] - else: - self.log('CBV:',sender) - try: - if q.result == 'softfail': - template = file('softfail.txt').read() - else: - template = file('strike3.txt').read() - except IOError: template = None - m = dsn.create_msg(q,self.recipients,msg,template) - m = m.as_string() - print >>open('last_dsn','w'),m - res = dsn.send_dsn(sender,self.receiver,m) - if res: - desc = "CBV: %d %s" % res[:2] - if 400 <= res[0] < 500: - self.log('TEMPFAIL:',desc) - self.setreply('450','4.2.0',*desc.splitlines()) - return Milter.TEMPFAIL - if len(res) < 3: res += time.time(), - cbv_cache[sender] = res - self.log('REJECT:',desc) - self.setreply('550','5.7.1',*desc.splitlines()) - return Milter.REJECT - cbv_cache[sender] = res - if not cached: - s = time.strftime(time_format,time.localtime()) - print >>open('send_dsn.log','a'),sender,s # log who we sent DSNs to - self.cbv_needed = None - - if not defanged and not spam_checked: - os.remove(self.tempname) - self.tempname = None # prevent re-removal - self.log("eom") - return rc # no modified attachments - - # Body modified, copy modified message to a temp file - if defanged: - if self.rejectvirus and not self.hidepath: - self.log("REJECT virus from",self.mailfrom) - self.tempname = None - return Milter.REJECT - self.log("Temp file:",self.tempname) - self.tempname = None # prevent removal of original message copy - out = tempfile.TemporaryFile() - try: - msg.dump(out) - out.seek(0) - msg = rfc822.Message(out) - msg.rewindbody() - while True: - buf = out.read(8192) - if len(buf) == 0: break - self.replacebody(buf) # feed modified message to sendmail - if spam_checked: self.log("dspam") - return rc - finally: - out.close() - return Milter.TEMPFAIL - - def close(self): - sys.stdout.flush() # make log messages visible - if self.tempname: - os.remove(self.tempname) # remove in case session aborted - if self.fp: - self.fp.close() - sys.stdout.flush() - return Milter.CONTINUE - - def abort(self): - self.log("abort after %d body chars" % self.bodysize) - return Milter.CONTINUE - -def main(): - Milter.factory = bmsMilter - flags = Milter.CHGBODY + Milter.CHGHDRS + Milter.ADDHDRS - if wiretap_dest or smart_alias or dspam_userdir: - flags = flags + Milter.ADDRCPT - if srs or len(discard_users) > 0 or smart_alias or dspam_userdir: - flags = flags + Milter.DELRCPT - Milter.set_flags(flags) - print "%s bms milter startup" % time.strftime('%Y%b%d %H:%M:%S') - sys.stdout.flush() - Milter.runmilter("pythonfilter",socketname,timeout) - print "%s bms milter shutdown" % time.strftime('%Y%b%d %H:%M:%S') - -if __name__ == "__main__": - read_config(["/etc/mail/pymilter.cfg","milter.cfg"]) - if dspam_dict: - import dspam # low level spam check - if dspam_userdir: - import dspam - import Dspam # high level spam check - try: - dspam_version = Dspam.VERSION - except: - dspam_version = '1.1.4' - assert dspam_version >= '1.1.5' - main() diff --git a/cid2spf.py b/cid2spf.py deleted file mode 100644 index 2140aa5..0000000 --- a/cid2spf.py +++ /dev/null @@ -1,153 +0,0 @@ -#!/usr/bin/python2.3 - -# Convert a MS Caller-ID entry (XML) to a SPF entry -# -# (c) 2004 by Ernesto Baschny -# (c) 2004 Python version by Stuart Gathman -# -# Date: 2004-02-25 -# Version: 1.0 -# -# Usage: -# ./cid2spf.pl "<ep xmlns='http://ms.net/1'>...</ep>" -# -# Note that the 'include' directives will also have to be checked and -# "translated". Future versions of this script might be able to get a -# domain name as an argument and "crawl" the DNS for the necessary -# information. -# -# A complete reverse translation (SPF -> CID) might be impossible, since -# there are no way to handle: -# - PTR and EXISTS mechanism -# - MX mechanism with an different domain as argument -# - macros -# -# References: -# http://www.microsoft.com/mscorp/twc/privacy/spam_callerid.mspx -# http://spf.pobox.com/ -# -# Known bugs: -# - Currently it won't handle the exclusions provided in the A and R -# tags (prefix '!'). They will show up "as-is" in the SPF record -# - I really haven't read the MS-CID specs in-depth, so there are probably -# other bugs too :) -# -# Ernesto Baschny <ernst@baschny.de> -# - -import xml.sax -import spf - -# ------------------------------------------------------------------------- -class CIDParser(xml.sax.ContentHandler): - "Convert a MS Caller-ID entry (XML) to a SPF entry" - - def __init__(self,q=None): - self.spf = [] - self.action = '-all' - self.has_servers = None - self.spf_entry = None - if q: - self.spf_query = q - else: - self.spf_query = spf.query(i='127.0.0.1', s='localhost', h='unknown') - - def startElement(self,tag,attr): - if tag == 'm': - if self.has_servers != None and not self.has_servers: - raise ValueError( - "Declared <noMailServers\> and later <m>, this CID entry is not valid." - ) - self.has_servers = True - elif tag == 'noMailServers': - if self.has_servers: - raise ValueError( - "Declared <m> and later <noMailServers\>, this CID entry is not valid." - ) - self.has_servers = False - elif tag == 'ep': - if attr.has_key('testing') and attr.getValue('testing') == 'true': - # A CID with 'testing' found: - # From the MS-specs: - # "Documents in which such attribute is present with a true - # value SHOULD be entirely ignored (one should act as if the - # document were absent)" - # From the SPF-specs: - # "Neutral (?): The SPF client MUST proceed as if a domain did - # not publish SPF data." - # So we set SPF action to "neutral": - self.action = '?all' - elif tag == 'mx': - # The empty MX-tag, same as SPF's MX-mechanism - self.spf.append('mx') - self.tag = tag - - def characters(self,text): - tag = self.tag - # Remove starting and trailing spaces from text: - text = text.strip() - - if tag == 'a' or tag == 'r': - # The A and R tags from MS-CID are both handled by the - # ipv4/6-mechanisms from SPF: - if text.find(':') < 0: - mechanism = 'ip4' - else: - mechanism = 'ip6' - self.spf.append(mechanism + ':' + text) - elif tag == 'indirect': - # MS-CID's indirect is "sort of" the include from SPF: - # Not really true, because the <indirect> tag from MS-CID also - # provides a fallback in case the included domain doesn't provide - # _ep-records: The inbound MX-servers of the included domains - # are added to the list of allowed outgoing mailservers for the - # domain that declared the _ep-record with the <indirect> tag. - # In SPF you would use the 'mx:domain' to handle this, but this - # wouldn't depend on referred domain having or not SPF-records. - cid_xml = self.cid_txt(text) - if cid_xml: - p = CIDParser() - xml.sax.parseString(cid_xml,p) - if p.has_servers != False: - self.spf += p.spf - else: - self.spf.append('mx:' + text) - - def cid_txt(self,domain): - q = self.spf_query - domain='_ep.' + domain - a = q.dns_txt(domain) - if not a: return None - if a[0].lower().startswith('<ep ') and a[-1].lower().endswith('</ep>'): - return ''.join(a) - return None - - def endElement(self,tag): - if tag == 'ep': - # This is the end... assemble what we've got - spf_entry = ['v=spf1'] - if self.has_servers != False: - spf_entry += self.spf - spf_entry.append(self.action) - self.spf_entry = ' '.join(spf_entry) - - def spf_txt(self,cid_xml): - if not cid_xml.startswith('<'): - cid_xml = self.cid_txt(cid_xml) - if not cid_xml: return None - # Parse the beast. Any XML-problem will be reported by xlm.sax - self.spf_entry = None - xml.sax.parseString(cid_xml,self) - return self.spf_entry - -if __name__ == '__main__': - import sys - if len(sys.argv) < 2: - print >>sys.stderr, \ - """Usage: %s "<ep xmlns='http://ms.net/1'>...</ep>" """ % sys.argv[0] - sys.exit(1) - - cid_xml = sys.argv[1] - - p = CIDParser() - print p.spf_txt(cid_xml) diff --git a/faq.html b/faq.html deleted file mode 100644 index 3f51084..0000000 --- a/faq.html +++ /dev/null @@ -1,159 +0,0 @@ -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> -<html> -<head> -<title>Python Milter FAQ</title> -</head><body> - -<h1> Python Milter <a name=faq>FAQ</a> </h1> - -<ol> -<h3> Compiling Python Milter </h3> -<li> Q. I have installed sendmail from source, but Python milter won't -compile. -<p> A. Even though libmilter is officially supported in sendmail-8.12, -you need to build and install it in separate steps. Take a look -at the <a href="/aix/sendmail12.spec">RPM spec file</a> for sendmail-8.12. -The %prep section shows you how to create -a site.config.m4 that enables MILTER. The %build section shows you how -to build libmilter in a separate invocation of make. The %install section -shows you how to install libmilter with a separate invocation of make. -<p> - -<li> Q. Why is mfapi.h not found when I try to compile Python milter on -RedHat 7.2? -<p> A. RedHat forgot to include the header in the RPM. See the -<a href="milter.html#rh72">RedHat 7.2 requirements</a>. -<p> - -<h3> Running Python Milter </h3> - -<li> Q. The sample.py milter prints a message, then just sits there. -<pre> -To use this with sendmail, add the following to sendmail.cf: - -O InputMailFilters=pythonfilter -Xpythonfilter, S=local:inet:1030@localhost - -See the sendmail README for libmilter. -sample milter startup -</pre> -<p> A. You need to tell sendmail to connect to your milter. The -sample milter tells you what to add to your sendmail.cf to tell -sendmail to use the milter. You can also add an INPUT_MAIL_FILTER -macro to your sendmail.mc file and rebuild sendmail.cf - see the sendmail -README for milters. -<p> - -<li> Q. I've configured sendmail properly, but still nothing happens -when I send myself mail! -<p> A. Sendmail only milters SMTP mail. Local mail is not miltered. -You can pipe a raw message through sendmail to test your milter: -<pre> -$ cat rawtextmsg | sendmail myname@my.full.domain -</pre> -Now check your milter log. -<p> - -<li> Q. Why do I get this ImportError exception? -<pre> -File "mime.py", line 370, in ? - from sgmllib import declstringlit, declname - ImportError: cannot import name declstringlit -</pre> -<p> A. <code>declstringlit</code> is not provided by sgmllib in all versions -of python. For instance, python-2.2 does not have it. Upgrade to -milter-0.4.5 or later to remove this dependency. -<p> - -<li> Q. Why do I get <code>milter.error: cannot add recipient</code>? -<pre> -</pre> -<p> A. You must tell libmilter how you might mutate the message with -<code>set_flags()</code> before calling <code>runmilter()</code>. For -instance, <code>Milter.set_flags(Milter.ADDRCPT)</code>. You must add together -all of <code>ADDHDRS, CHGBODY, ADDRCPT, DELRCPT, CHGHDRS</code> that apply. -<p> - -<li> Q. Why does sendmail sometimes print something like: -"...write(D) returned -1, expected 5: Broken pipe" -in the sendmail log? -<p> A. Libmilter expects "rcpt to" shortly after getting "mail from". -"Shortly" is defined by the timeout parameter you passed to -<code>Milter.runmilter() -</code> or <code>milter.settimeout()</code>. If the timeout is 10 seconds, -and looking up the first recipient in DNS takes more than -10 seconds, libmilter will give up and break the connection. -<code>Milter.runmilter()</code> defaulted to 10 seconds in 0.3.4. In 0.3.5 -it will keep the libmilter default of 2 hours. -<p> - -<li> Q. Why does milter block messages with big5 encoding? What if I -want to receive them? -<p> A. sample.py is a sample. It is supposed to be easily modified -for your specific needs. We will of course continue to move generic -code out of the sample as the project evolves. Think of sample.py as -an active config file. -<p> - -<li> Q. Why does sendmail coredump with milters on OpenBSD? -<p> A. Sendmail has a problem with unix sockets on OpenBSD. Use -an internet domain socket instead. For example, in <code>sendmail.cf</code> use -<pre> -Xpythonfilter, S=inet:1234@localhost -</pre> -and change sample.py accordingly. -<p> - -<li> Q. How can I change the bounce message for an invalid recipient? -I can only change the recipient in the eom callback, but the eom callback -is never called when the recipient is invalid! -<p> A. Configure sendmail to use virtusertable, and send all unknown -addresses to /dev/null. For example, -<h4>/etc/mail/virtusertable</h4> -<pre> -@mycorp.com dev-null -dan@mycorp.com dan -sally@mycorp.com sally -</pre> -<h4>/etc/aliases</h4> -<pre> -dev-null: /dev/null -</pre> -Now your milter will get to the eom callback, and can change the -envelope recipient at will. Thanks to Dredd at -<a href=http://www.milter.org/>milter.org</a> for this solution. -<p> - -<li> Q. I am having trouble with the setreply method. It always outputs - "milter.error: cannot set reply". -<p> A. Check the sendmail log for errors. If sendmail is getting -milter timeouts, then your milter is taking too long and sendmail gave -up waiting. You can adjust the timeouts in your sendmail config. Here -is a milter declaration for sendmail.cf with all timeouts specified: -<pre> -Xpythonfilter, S=local:/var/log/milter/pythonsock, F=T, T=C:5m;S:20s;R:60s;E:5m -</pre> - -<a name="spf"> -<li> Q. So how do I use the SPF support? The sample.py milter doesn't seem - to use it. -<p> A. The bms.py milter supports spf. The RedHat RPMs will set almost -everything up for you. For other systems: -<ol type=i> -<li> Arrange to run bms.py in the background (as a service perhaps) and - redirect output and errors to a logfile. For instance, on AIX you'll want - to use SRC (System Resource Controller). -<li> Copy milter.cfg to the directory you run bms.py in, and edit it. The - comments should explain the options. -<li> Start bms.py in the background as arranged. -<li> Add Xpythonfilter to sendmail.cf or add an INPUT_MAIL_FILTER to - sendmail.mc. Regen sendmail.cf if you use sendmail.mc and restart - sendmail. -<li> Arrange to rotate log files and remove old defang files in - <code>tempdir</code>. The RedHat RPM uses <code>logrotate</code> for - logfiles and a simple cron script using <code>find</code> to clean - <code>tempdir</code>. -</ol> - -</ol> -</html> diff --git a/milter.cfg b/milter.cfg deleted file mode 100644 index 0e9c405..0000000 --- a/milter.cfg +++ /dev/null @@ -1,160 +0,0 @@ -[milter] -# the socket used to communicate with sendmail. Must match sendmail.cf -socket=/var/run/milter/pythonsock -# where to save original copies of defanged and failed messages -tempdir = /var/log/milter/save -# how long to wait for a response from sendmail before giving up -;timeout=600 -log_headers = 0 -# connection ips and hostnames are matched against this glob style list -# to recognize internal senders. -;internal_connect = 192.168.*.* - -# mail that is not an internal_connect and claims to be from an -# internal domain is rejected. Furthermore, internal mail that -# does not claim to be from an internal domain is rejected. -# You should enable SPF instead if you can. SPF is much more comprehensive and -# flexible. However, SPF is not currently checked for outgoing -# (internal_connect) mail because it doesn't yet handle authorizing -# internal IPs locally. -;internal_domains = mycorp.com - -# connections from a trusted relay can trust the first Received header -# SPF checks are bypassed for internal connections and trusted relays. -;trusted_relay = 1.2.3.4, 66.12.34.56 - -# Reject external senders with hello names no legit external sender would use. -# SPF will do this also, but listing your own domain and mailserver here -# will save some DNS lookups when rejecting certain viruses. -;hello_blacklist = mycorp.com, 66.12.34.56 - -# Reject mail for domains mentioned unless user is mentioned here also -;check_user = joe@mycorp.com, mary@mycorp.com, file:bigcorp.com - -# features intended to filter or block incoming mail -[defang] - -# do virus scanning on attached messages also -scan_rfc822 = 1 -# do virus scanning on attached zipfiles also -scan_zip = 0 -# Comment out scripts in HTML attachments. Can be CPU intensive. -scan_html = 0 -# reject messages with asian fonts because we can't read them -block_chinese = 1 -# list users who hate forwarded mail -;block_forward = egghead@mycorp.com, busybee@mycorp.com -# reject mail with these case insensitive strings in the subject -porn_words = penis, breast, pussy, horse cock, porn, xenical, diet pill, d1ck, - vi*gra, vi-a-gra, viag, tits, p0rn, hunza, horny, sexy, c0ck, xanaax, - p-e-n-i-s, hydrocodone, vicodin, xanax, vicod1n, x@nax, diazepam, - v1@gra, xan@x, cialis, ci@lis, fr�e, x�nax, val�um, v�lium, via-gra, - x@n3x, vicod3n, pen�s, c0d1n, phentermine, en1arge, dip1oma, v1codin, - valium, rolex, sexual, fuck -# reject mail with these case sensitive strings in the subject -spam_words = $$$, !!!, XXX, FREE, HGH -# attachments with these extensions will be replaced with a warning -# message. A copy of the original will be saved. -banned_exts = ade,adp,asd,asx,asp,bas,bat,chm,cmd,com,cpl,crt,dll,exe,hlp,hta, - inf,ins,isp,js,jse,lnk,mdb,mde,msc,msi,msp,mst,ocx,pcd,pif,reg,scr,sct, - shs,url,vb,vbe,vbs,wsc,wsf,wsh - -# See http://bmsi.com/python/pysrs.html for details -[srs] -config=/etc/mail/pysrs.cfg -# SRS options can be set here also, but must match the sendmail plugin -;secret="shhhh!" -;maxage=21 -;hashlength=4 -;database=/var/log/milter/srsdata -;fwdomain = mydomain.com -# turn this on after a grace period to reject spoofed DSNs -reject_spoofed = 0 - -# See http://spf.pobox.com for more info on SPF. -[spf] -# namespace where SPF records can be supplied for domains without one -# records are searched for under _spf.domain.com -;delegate = domain.com -# domains where a neutral SPF result should cause mail to be rejected -;reject_neutral = aol.com -# use a default (v=spf1 a/24 mx/24 ptr) when no SPF records are published -;best_guess = 0 -# Reject senders that have neither PTR nor valid HELO nor SPF records, or send -# DSN otherwise -;reject_noptr = 0 -# always accept softfail from these domains, or send DSN otherwise -;accept_softfail = bounces.amazon.com - -# features intended to clean up outgoing mail -[scrub] -# domains that block visible private nodes -;hide_path = jcpenney.com -# reject, don't just replace with warning, viruses from these domains -;reject_virus_from = mycorp.com - -# features intended for spying on users and coworkers -[wiretap] -blind = 1 -# -# wiretap lets you surreptitiously monitor a users outgoing email -# (sendmail aliases let you monitor incoming mail) -# -;users = disloyal@bigcorp.com, bigmouth@bigcorp.com -;dest = spy@bigcorp.com -# discard outgoing mail without alerting sender -# can be used in conjunction with wiretap to censor outgoing mail -;discard_users = canned@bigcorp.com -# -# smart aliases trigger on both sender and recipient -# -;smart_alias = copycust,walter -# mail from client@clientcorp.com to sue@bigcorp.com is redirected to -# local alias copycust -;copycust = client@clientcorp.com,sue@bigcorp.com -# mail from cust@othercorp.com to walter@bigcorp.com is redirected to -# boss@bigcorp.com -;walter = cust@othercorp.com,walter@bigcorp.com,boss@bigcorp.com -# additional copies can be added -;walter1 = cust@othercorp.com,walter@bigcorp.com,boss@bigcorp.com, -; walter@bigcorp.com -;bulk = soruce@telex.com,bob@jsconnor.com -;bulk = soruce@telex.com,larry@jsconnor.com - -# See http://bmsi.com/python/dspam.html -[dspam] -# Select a well moderated dspam dictionary to reject spammy headers. -# To filter on the entire message, use the full setup below. -# only EXTERNAL messages are dspam filtered -;dspam_dict=/var/lib/dspam/moderator.dict - -# Opt-opt recipients from dspam screening and header triage -;dspam_exempt=getitall@mycorp.com -# Do not scan mail (ostensibly) from these senders -;dspam_whitelist=getitall@sender.com -# Reject spam to these domains instead of quarantining it. -;dspam_reject=othercorp.com -# Scan internal mail - often a good source of stats on legit mail. -;dspam_internal=1 - -# directory for dspam user quarantine, signature db, and dictionaries -# defining this activates the dspam application -# dspam and dspam-python must be installed -;dspam_userdir=/var/lib/dspam -# do not dspam messages larger than this -;dspam_sizelimit=180000 - -# Map email addresses and aliases to dspam users -;dspam_users=david,goliath,spam,falsepositive -;david=david@foocorp.com,david.yelnetz@foocorp.com,david@bar.foocorp.com -;goliath=giant@foocorp.com,goliath.philistine@foocorp.com -# address to forward spam to. milter will process these and not deliver -;spam=spam@foocorp.com -# address to forward false positives to. milter will process and not deliver -;falsepositive=ham@foocorp.com -# the dspam_screener is a list of dspam users who screen mail for all -# recipients who are not dspam_users. Spam goes to the screeners quarantine, -# and the original recipients are saved so that false positives can be properly -# delivered. -;dspam_screener=david,goliath -# The dspam CGI can also be used: logins must match dspam users diff --git a/milter.html b/milter.html deleted file mode 100644 index 674d528..0000000 --- a/milter.html +++ /dev/null @@ -1,502 +0,0 @@ -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> -<html> -<head> -<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> -<title>Python Milters</title> -</head><body> - -<P ALIGN="CENTER"><A HREF="http://www.anybrowser.org/campaign/"> -<IMG SRC="/art/brain1.gif" -ALT="Viewable With Any Browser" BORDER="0"></A> - -<img src="/art/banner_4.gif" width="468" height="60" border="0" - usemap="#banner_4" alt="Your vote?"> -<map name="banner_4"> - <area shape="rect" coords="330,25,426,59" - href="http://education-survey.org/" alt="I Disagree"> - <area shape="rect" coords="234,28,304,57" href="http://www.honestEd.com/" alt="I Agree"> -</map> - -</P> -<h1 align=center>Sendmail Milters in Python</h1> -<h4 align=center>by <a href="mailto:%75%72%6D%61%6E%65%40%6E%65%75%72%61l%61%63%63%65%73%73%2E%63%6F%6D">Jim Niemira</a> - and <a href="mailto:%73%74%75%61%72%74%40%62%6D%73%69%2E%63%6F%6D"> - Stuart D. Gathman</a><br> -This web page is written by Stuart D. Gathman<br>and<br>sponsored by -<a href="http://www.bmsi.com">Business Management Systems, Inc.</a> <br> -Last updated Jun 09, 2005</h4> - -See the <a href="faq.html">FAQ</a> | <a href="http://sourceforge.net/project/showfiles.php?group_id=139894">Download now</a> | -<a href="/mailman/listinfo/pymilter">Subscribe to mailing list</a> | -<a href="#overview">Overview</a> | -<a href="/python/dspam.html">pydspam</a> | -<a href="/libdspam/dspam.html">libdspam</a> -<p> -<a href="//www.python.org"> -<img src="python55.gif" align=left alt="A Python"></a> -<a href="//www.sendmail.org/">Sendmail</a> introduced a -<a href="http://www.milter.org/milter_api/api.html"> new API</a> beginning with version 8.10 - -libmilter. The milter module for <a href="//www.python.org">Python</a> -provides a python interface to libmilter that exploits all its features. -<p> -Sendmail 8.12 officially releases libmilter. -Version 8.12 seems to be more robust, and includes new privilege -separation features to enhance security. Even better, sendmail 8.13 -supports socket maps, which makes <a href="pysrs.html">pysrs</a> much more -efficient and secure. I recommend upgrading. - -<h2> Recent Changes </h2> - -Python milter is being moved to -<a href="http://sourceforge.net/projects/pymilter/">pymilter Sourceforge -project</a> for development. -<p> -Release 0.8.0 is the first <a href="http://sourceforge.net/">Sourceforge</a> -release. It supports Python-2.4, and provides an option to accept mail -that gets an SPF softfail or fails the 3 strikes rule, provided the -alleged sender accepts a DSN explaining the problem. Python-2.3 is -no longer supported by the reworked mime.py module, although API changes -could be backported. There are too many incompatible changes to the -python email package. -<p> -Release 0.7.2 tightens the authentication screws with a "3 strikes and -you're out" policy. A sender must have a valid PTR, HELO, or SPF record -to send email. Specific senders can be whitelisted using the -"delegate" option in the spf configuration section by adding a -default SPF record for them. The PTR and HELO are required -by RFC anyway, so this is not an unreasonable requirement. -There is now a coherent policy for an SPF softfail result. A softfail -is accepted if there is a valid PTR or HELO, or if the domain -is listed in the "accept_softfail" option of the spf configuration section. -A neutral result is accepted by default if there is a valid PTR or -HELO, (and the SPF record was not guessed), unless the domain is listed in the -"reject_neutral" option. Common forms of PTR records for dynamic IPs are -recognized, and do not count as a valid PTR. This does not prevent anyone -from sending mail from a dynamic IP - they just need to configure a -valid HELO name or publish an SPF record. -<p> -As SPF adoption continues to rise, forged spam is not getting through. So -spammers are publishing their SPF records as predicted. The 0.7.2 RPM -now provides the <code>rhsbl</code> sendmail hack so that spammer domains -can be blacklisted. With the RPM installed, add a line like the following -to your <code>sendmail.mc</code>. -<pre> -HACK(rhsbl,`blackholes.example.com',"550 Rejected: " $&{RHS} " has been spamming our customers.")dnl -</pre> -<p> -Of course, spammers are now starting to register -throwaway domains. The next thing we need is a custom DNS server, -in Python, that -can recognize patterns. For instance, one spammer registers ded304.com, -ded305.com, ded306.com, etc. We also need the custom DNS server to -let SPF classic clients check SES (which will be part of pysrs). -The <a href="http://twistedmatrix.com/products/twisted">Twisted Python</a> -framework provides a custom DNS server - but I -would like a smaller implementation for our use. -<p> -The RPM for release 0.7.0 moves the config file and socket locations to -/etc/mail and /var/run/milter respectively. We now parse Microsoft CID records -- but only hotmail.com uses them. They seem to have applied for a patent on -the brilliant idea of examining the mail headers to see who the message is -from. We aren't doing that here, so not to worry - but I am not a lawyer, so -if you are worried, change spf.py around line 626 to return None instead of -calling CIDParser(). There is a new option to reject mail with no PTR -and no SPF. -<p> -Microsoft is pushing an anti-opensource license for their pending patent -along with their sender-ID proposal before the IETF. -It is royalty free - but requires anyone distributing a binary they've -compiled from source to sign a license agreement. The Apache Software -Foundation <a -href="http://www.apache.org/foundation/docs/sender-id-position.html"> explains -the problem with sender-ID</a>, and Debian <a -href="http://www.debian.org/News/2004/20040904">concurs</a>. Since -the <a href="http://download.microsoft.com/download/4/3/9/439b024b-09fd-44ee-8ff0-10e834004c36/senderid_FAQ.PDF">Microsoft license</a> is -<a href="http://www.circleid.com/article/732_0_1_0_C/">incompatible with free -software in general</a> and the <a -href="http://www.imc.org/ietf-mxcomp/mail-archive/msg03678.html">GPL in -particular</a>, Python milter will not be able to implement sender-ID in its -current form. This was, no doubt, Microsoft's intent all along. -<p> -Sender-ID attempts to do for RFC2822 headers what SPF does for RFC2821 headers. -Unlike SPF, it has never been tried, and is encumbered by a stupid patent. I -recommend ignoring it and continuing to implement and improve SPF until a -working and unencumbered proposal for RFC2822 headers surfaces. - -<p> -<a href="http://spf.pobox.com"> -<img src="SPF.gif" align=left alt="SPF logo"></a> -Release 0.6.6 adds support for <a href="http://spf.pobox.com/">SPF</a>, -a protocol to prevent forging of the envelope from address. -SPF support requires <a href="http://pydns.sourceforge.net/">pydns</a>. -The included spf.py module is an updated version of the original 1.6 -version at <a href="http://www.wayforward.net/spf/">wayforward.net</a>. -The updated version tracks the draft RFC and test suite. -<p> -The FAQ addresses <a href="faq.html#spf">how to get started with SPF</a>. -<p> -Release 0.6.1 adds a full milter based dspam application. -<p> -I have selected the <a href="http://www.nuclearelephant.com/projects/dspam/"> -dspam bayes filter project</a> and <a href="dspam.html"> -packaged it for python</a>. -Release 0.6.0 offers a simple application of dspam I call "header triage", -which rejects messages with spammy headers. -To use header triage, you must have <a href="dspam.html">DSPAM</a> installed, -and select a dictionary that is well moderated by someone who gets -lots of spam. That dictionary can be used to block spam that is -obvious from the headers (e.g. X-Mailer and Subject) before it ties -up any more resources. I have yet to see any false positives from this -approach (check the milter log), but if there are, the sender will -get a REJECT with the message "Your message looks spammy." - -<h2> Enough Already! </h2> - -Nearly a dozen people have emailed me begging for a feature to copy -outgoing and/or incoming mail to a backup directory by user. Ok, it -looks like this is a most requested feature for 0.5.6. In the meantime, -here are some things to consider: -<ul> -<li> If you want to equivalent of a Bcc added to each message, this -is very easy to do in the python code for bms.py. See below. -<li> If you want to copy to a file in a directory (thus avoiding having to -set up aliases), this is slightly more involved. The bms.py milter already -copies the message to a temporary file for use in replacing the message body -when banned attachments are found. You have to open a file, and copy the -Mesage object to it in eom(). -<li> Finally, you are probably aware that most email clients already -keep a copy of outgoing mail? Presumably there is a good reason for -keeping another copy on the server. -</ul> -<p> -To Bcc a message, call <code>self.add_recipient(rcpt)</code> in envfrom after -determining whether you want to copy (e.g. whether the sender is local). For -example, -<pre> - def envfrom(... - ... - if len(t) == 2: - self.rejectvirus = t[1] in reject_virus_from - if t[0] in wiretap_users.get(t[1],()): - self.add_recipient(wiretap_dest) - if t[1] == 'mydomain.com': - self.add_recipient('<copy-%s>' % t[0]) - ... -</pre> -<p> -To make this a generic feature requires thinking about how the configuration -would look. Feel free to make specific suggestions about config file -entries. Be sure to handle both Bcc and file copies, and designating what -mail should be copied. How should "outgoing" be defined? Implementing it is -easy once the configuration is designed. - -<h3><a name=overview>Overview</a></h3> - -This package provides a robust toolkit for Python <a -href="#milter">milters</a>, and the beginnings of a general purpose mail -filtering system written in Python. -<p> -At the lowest level, the 'milter' module provides a thin wrapper around the -<a href="http://www.milter.org/milter_api/api.html"> -sendmail libmilter API</a>. This API lets you register callbacks for -a number of events in the -<a href="http://www.cs.concordia.ca/~group/fig/public/email/relay/milter+ruleset-checks.html">process of sendmail receiving a message via SMTP</a>. -These events include the initial connection from a MTA, -the envelope sender and recipients, the top level mail headers, and -the message body. There are options to mangle all of these components -of the message as it passes through the milter. -<p> -At the next level, the 'Milter' module (note the case difference) provides a -Python friendly object oriented wrapper for the low level API. To use the -Milter module, an application registers a 'factory' to create an object -for each connection from a MTA to sendmail. These connection objects -must provide methods corresponding to the libmilter callback events. -<p> -Each event method returns a code to tell sendmail whether to proceed -with processing the message. This is a big advantage of milters over -other mail filtering systems. Unwanted mail can be stopped in its -tracks at the earliest possible point. -<p> -The Milter.Milter class provides default implementations for event -methods that -do nothing, and also provides wrappers for the libmilter methods to mutate -the message. -<p> -The 'spf' module provides an implementation of <a href="http://spf.pobox.com"> -SPF</a> useful for detecting email forgery. -<p> -The 'mime' module provides a wrapper for the Python email package that -fixes some bugs, and simplifies modifying selected parts of a MIME message. -<p> -Finally, the bms.py application is both a sample of how to use the -Milter and spf modules, and the beginnings of a general purpose SPAM filtering, -wiretapping, SPF checking, and Win32 virus protecting milter. It can -make use of the <a href="pysrs.html">pysrs</a> package when available for -SRS/SES checking and the <a href="dspam.html">pydspam</a> package for Bayesian -content filtering. SPF checking -requires <a href="http://pydns.sourceforge.net/"> -pydns</a>. Configuration documentation is currently included as comments -in the <a href="milter.cfg">sample config file</a> for the bms.py milter. -<p> -Python milter is under GPL. The authors can probably be convinced to -change this to LGPL if needed. - -<h3>What is a <a name="milter">milter</a>?</h3> - -Milters can run on the same machine as sendmail, or another machine. The -milter can even run with a different operating system or processor than -sendmail. -Sendmail talks to the milter via a local or internet socket. -Sendmail keeps the -milter informed of events as it processes a mail connection. At any -point, the milter can cut the conversation short by telling sendmail -to ACCEPT, REJECT, or DISCARD the message. After receiving a complete -message from sendmail, the milter can again REJECT or DISCARD it, but it -can also ACCEPT it with changes to the headers or body. - -<h3> What can you do with a milter? </h3> - -<menu> -<li> A milter can DISCARD or REJECT spam based based on algorithms scripted -in python rather than sendmail's cryptic "cf" language. -<li> A milter can alter or remove attachments from mail that are poisonous to -Windows. -<li> A milter can scan for viruses and clean them when detected. -<li> A milter scans outgoing as well as incoming mail. -<li> A milter can add and delete recipients to forward or secretly -copy mail. -<li> For more ideas, check the <a href="//www.milter.org">Milter Web Page</a>. -</menu> - -<a href="http://www.milter.org/milter_api/api.html"> -Documentation</a> for the C API is provided with sendmail. Miltermodule -provides a thin python wrapper for the C API. Milter.py provides a simple -OO wrapper on top of that. -<p> -The Python milter package includes a sample milter that replaces dangerous -attachments with a warning message, discards mail addressed to -MAILER-DAEMON, and demonstrates several SPAM abatement strategies. -The MimeMessage class to do this used to be based on the -<code>mimetools</code> and <code>multifile</code> standard python packages. -As of milter version 0.6.0, it is based on the email standard -python packages, which were derived from the -<a href="http://sourceforge.net/projects/mimelib">mimelib</a> project. -The MimeMessage class patches several bugs in the email package, -and provides some backward compatibility. - -<p> -The "defang" function of the sample milter was inspired by -<a href="http://www.roaringpenguin.com/mimedefang/">MIMEDefang</a>, -a Perl milter with flexible attachment processing options. The latest -version of MIMEDefang uses an apache style process pool to avoid reloading -the Perl interpreter for each message. This makes it fast enough for -production without using Perl threading. -<p> -<a href="http://sourceforge.net/projects/mailchecker">mailchecker</a> is -a Python project to provide flexible attachment processing for mail. I -will be looking at plugging mailchecker into a milter. -<p> -<a href="http://software.libertine.org/tmda/">TMDA</a> is a Python project -to require confirmation the first time someone tries to send to your -mailbox. This would be a nice feature to have in a milter. -<p> -There is also a <a href="http://www.milter.org/">Milter community website</a> -where milter software and gory details of the API are discussed. - -<h3> Is a milter written in python efficient? </h3> - -The python milter process is multi-threaded and startup cost is incurred -only once. This is much more efficient than some implementations that -start a new interpreter for each connection. Testing in a production -environment did not use a significant percentage of the CPU. Furthermore, -python is easily extended in C for any step requiring expensive CPU -processing. -<p> -For example, the HTML parsing feature to remove scripts from HTML attachments -is rather CPU intensive in pure python. Using the C replacement for sgmllib -greatly speeds things up. - -<h3> Goals </h3> - -<menu> -<li> Implement RRS - a backdoor for non-SRS forwarders. User lists non-SRS - forwarder accounts (perhaps in <code>~/.forwarders</code>), and a util - provides a special local alias for the user to give to the forwarder. - Alias only works for mail from that forwarder. Milter gets forwarder - domain from alias and uses it to SPF check forwarder. Requires - milter to have read access to <code>~/.forwarders</code> or else - a way for user to submit entries to milter database. -<li> The bms.py milter has too many features. Create a framework where - numerous small feature modules can be plugged together in the - configuration. -<li> Create a pure python substitute for miltermodule and libmilter that - implements the <a -href="http://www.duh.org/cvsweb.cgi/~checkout~/pmilter/doc/milter-protocol.txt?rev=1"> - libmilter protocol</a> in python. -<li> Find or write a faster implementation of sgmllib. The - <a href="http://www.effbot.org/zone/sgmlop-index.htm">sgmlop package</a> - is not very compatible with - <a href="http://www.python.org/doc/2.1.3/lib/module-sgmllib.html"> - Python-2.1 sgmllib</a>, but it is a start, and is supported in - milter-0.4.5 or later. -<li> Implement all or most of the features of - <a href="http://www.roaringpenguin.com/mimedefang/">MIMEDefang</a>. -<li> Follow the official <a href="http://www.python.org/peps/pep-0008.html"> - Python coding standards</a> more closely. -<li> Make unit test code more like other python modules. -</menu> - -<h3> Confirmed Installations </h3> - -Please <a href="mailto:%73%74%75%61%72%74%40%62%6D%73%69%2E%63%6F%6D">email</a> -me if you successfully install milter on a system not mentioned below. -<p> -<table> -<tr> -<th>Operating System</th> <th>Compiler</th> <th>Python</th> <th>Sendmail</th> -<th>milter</th> -<tr> -<td>Mandrake 8.0</td><td>gcc-3.0.1</td><td>2.1.1</td><td>8.12.0</td> -<td>0.3.3</td><tr> -<td>Mandrake 8.0</td><td>gcc-2.96</td><td>2.0</td><td>8.11.2</td> -<td>0.3.6</td><tr> -<td>RedHat 6.2</td><td>egcs-1.1.2</td><td>2.2.2</td><td>8.11.6</td> -<td>0.5.4</td><tr> -<td>RedHat 7.1</td><td>gcc-2.96</td><td>?</td><td>8.12.1</td> -<td>0.3.5</td><tr> -<td>RedHat 7.3</td><td>gcc-2.96</td><td>2.2.2</td><td>8.11.6</td> -<td>0.5.5</td><tr> -<td>RedHat 7.3</td><td>gcc-2.96</td><td>2.3.3</td><td>8.13.1</td> -<td>0.7.2</td><tr> -<td>RedHat 8.0</td><td>gcc-3.2</td><td>2.2.1</td><td>8.12.6</td> -<td>0.5.2</td><tr> -<td>Debian Linux</td><td>gcc-2.95.2</td><td>2.1.1</td><td>8.12.0</td> -<td>0.3.7</td><tr> -<td>Debian Linux</td><td>gcc-3.2.2</td><td>2.2.2</td><td>8.12.7</td> -<td>0.5.4</td><tr> -<td>AIX-4.1.5</td><td>gcc-2.95.2</td><td>2.1.1</td><td>8.11.5</td> -<td>0.3.3</td><tr> -<td>AIX-4.1.5</td><td>gcc-2.95.2</td><td>2.1.1</td><td>8.12.1</td> -<td>0.3.4</td><tr> -<td>AIX-4.1.5</td><td>gcc-2.95.2</td><td>2.1.3</td><td>8.12.3</td> -<td>0.4.2</td><tr> -<td>AIX-4.1.5</td><td>gcc-2.95.2</td><td>2.2.3</td><td>8.13.1</td> -<td>0.7.1</td><tr> -<td>Slackware 7.1</td><td>?</td><td>?</td><td>8.12.1</td> -<td>0.3.8</td><tr> -<td>Slackware 9.0</td><td>gcc-3.2.2</td><td>2.2.3</td><td>8.12.9</td> -<td>0.5.4</td><tr> -<td>OpenBSD</td><td>?</td><td>2.3.3?</td><td>8.13.1?</td> -<td>0.7.2</td><tr> -<td>SuSE 7.3</td><td>gcc-2.95.3</td><td>2.1.1</td><td>8.12.2</td> -<td>0.3.9</td><tr> -<td>FreeBSD</td><td>gcc-2.95.3</td><td>2.2.1</td><td>8.12.3</td> -<td>0.4.0</td><tr> -<td>FreeBSD</td><td>gcc-2.95.3</td><td>2.2.2</td><td>?</td> -<td>0.5.5</td><tr> -<td>FreeBSD 4.4</td><td>gcc-2.95.3</td><td>?</td><td>8.12.10</td> -<td>0.6.6</td><tr> - -</table> - -<h3> Requirements </h3> - -<menu> -<li> While the miltermodule will work with python 1.5, you probably -want to use python 2.0 or better. The python code uses a number of -python 2 features. -<li> Python must be configured with thread support. This is because -sendmail's libmilter requires thread support. -<li> You must compile sendmail with libmilter enabled. In versions of -sendmail prior to 8.12 libmilter is marked FFR (For Future Release) and -is not installed by default. -Sendmail 8.12 still does not enable libmilter by default. You must -explicitly select the "MILTER" option when compiling. -<li> Python milter has been tested against sendmail-8.11 and sendmail-8.12. -<li> Python milter must be compiled for the specific version of sendmail -it will run with. (Since the result is dynamically loaded, there could -conceivably be multiple versions available and selected at startup - but -that will have to wait.) This situation may only exist for sendmail -versions prior to 8.12. The protocol seems designed for backward -compatibility - and 8.12 is the first official milter release. -<li> Mea Culpa! After reading the Python Style guide, I realize that -my Python code is not up to snuff. Apparently mixed tabs and spaces -are anathema to those using Windows editors, where tabs can be expanded using -any arbitrary algorithm. Other than that, my -intuition matched Guido's pretty well - although I like to indent by 2 -rather than 4. I will arrange to have tabs expanded to spaces when -exporting new versions. Until then, beware! -</menu> - -<h3> <a name="aix4"> AIX 4.1.5 Requirements </a> </h3> -To create sendmail RPMs for AIX, you can download my AIX 4.1.5 spec files -for <a href="/aix/sendmail.spec">sendmail-8.11.5</a> -or <a href="/aix/sendmail12.spec">sendmail-8.12.3</a>. If you have -not already set it up, I use a <a href="/aix/aix.spec">dummy RPM package</a> -to represent the stuff that comes with AIX. You might also want -my <a href="/aix/python.spec">python-2.1.1</a> spec file for AIX. It -does not include Tk or curses modules, sorry. If y'all trust me, you can -download rpms for AIX 4.x from my <a href="/aix">AIX RPM directory</a>. -<p> -Sendmail-8.12 renames -libsmutil.a to libsm.a. Unfortunately, libsm.a is an important AIX system -shared library. Therefore, I rename libsm.a back to libsmutil.a for -AIX. This presents a problem for setup.py. - -<h3> <a name="rh72"> RedHat 7.2 Requirements </a> </h3> - -If you are running Redhat 7.2, the distributed version of sendmail -now enables libmilter by default. RedHat 7.2 bundles -the development libraries with the main sendmail package, so -there is no sendmail-devel package. However, they forgot to include the -headers! So you'll have to get the SRPM and modify it. I suggest -moving the static libs to a devel package and adding the headers. If -this is too much trouble, you can get the <a href="mfapi.h">mfapi.h</a> -header for sendmail-8.6.11 from here and manually install it as -<code>/usr/include/libmilter/mfapi.h</code>. -<p> -If you do modify the SRPM, I suggest renaming libsmutil.a -to libsm.a - just like sendmail-8.12 will. If you manually install -mfapi.h or don't rename libsmutil.a, you'll -need to force <code>libs = ["milter", "smutil"]</code> in setup.py. -<p> -If you have installed python2, and want -python-milter to use python2, add <code>python=python2</code> to setup.cfg -and build with <code>python2 setup.py bdist_rpm</code>. - -<h3> <a name="rh62"> Redhat 6.2 Requirements </a> </h3> - -If you are running Redhat 6.2, the distributed version of sendmail -does not enable libmilter. You can download the Redhat 7.2 sendmail.spec -modified to compile on RedHat 6.2: -<a href="http://www.bmsi.com/linux/rh62/sendmail-rhmilter.spec"> -sendmail-rhmilter.spec</a>. The <a -href="ftp://updates.redhat.com/7.0/en/os/SRPMS/sendmail-8.11.6-1.7.0.src.rpm"> -SRPM for sendmail-8.11.6</a> is available from -<a href="http://www.redhat.com">Redhat</a> under -<a href="http://www.redhat.com/support/errata/RHSA-2001-106.html"> -Errata for RH6.2</a>. But that doesn't include the latest security -patches since RH6.2 is no longer supported. -<p> -If y'all trust me, you can pick up source and binary sendmail RPMs for RH6.2 -from my <a href="http://www.bmsi.com/linux/rh62">linux downloads</a> directory. -The lastest RPMs were built by taking a RH7.2 SRPMS and removing some -RPM features from the spec file that RH6.2 doesn't support, then -recompiling on RH6.2. You can check this by installing the RH7.2 SRPM, -then diffing my sendmail.spec with theirs. Then run -"rpm -bb sendmail-rhmilter.spec" when you are satisfied. -<p> -If you have installed python2, and want -python-milter to use python2, add <code>python=python2</code> to setup.cfg -and build with <code>python2 setup.py bdist_rpm</code>. -You'll need to install the sendmail-devel package to compile milter. - -<hr> -<p> -<a href="http://validator.w3.org/check/referer"> -<img border=0 src="/vh32.png" alt=" [ Valid HTML 3.2! ] " height=31 width=88></a> -<a href="http://www.redhat.com"> -<img src="/art/powered_by.gif" width="88" height="31" alt=" [ Powered By Red Hat Linux ] " border="0"></a> -</p> - -</body></html> diff --git a/milter.rc b/milter.rc deleted file mode 100755 index cd43cf9..0000000 --- a/milter.rc +++ /dev/null @@ -1,81 +0,0 @@ -#!/bin/bash -# -# milter This shell script takes care of starting and stopping milter. -# -# chkconfig: 2345 80 30 -# description: Milter is a process that filters messages sent through sendmail. -# processname: milter -# config: /var/log/milter/bms.py -# pidfile: /var/run/milter/milter.pid - -python="python2.3" - -pidof() { - set - "" - if set - `ps -e -o pid,cmd | grep "${python} bms.py"` && - [ "$2" != "grep" ]; then - echo $1 - return 0 - fi - return 1 -} - -# Source function library. -. /etc/rc.d/init.d/functions - -[ -x /var/log/milter/start.sh ] || exit 0 - -RETVAL=0 -prog="milter" - -start() { - # Start daemons. - - echo -n "Starting $prog: " - daemon --check milter --user mail /var/log/milter/start.sh - RETVAL=$? - echo - [ $RETVAL -eq 0 ] && touch /var/lock/subsys/milter - return $RETVAL -} - -stop() { - # Stop daemons. - echo -n "Shutting down $prog: " - killproc milter - RETVAL=$? - echo - [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/milter - return $RETVAL -} - -# See how we were called. -case "$1" in - start) - start - ;; - stop) - stop - ;; - restart|reload) - stop - start - RETVAL=$? - ;; - condrestart) - if [ -f /var/lock/subsys/milter ]; then - stop - start - RETVAL=$? - fi - ;; - status) - status milter - RETVAL=$? - ;; - *) - echo "Usage: $0 {start|stop|restart|condrestart|status}" - exit 1 -esac - -exit $RETVAL diff --git a/milter.rc7 b/milter.rc7 deleted file mode 100755 index 5445ca2..0000000 --- a/milter.rc7 +++ /dev/null @@ -1,81 +0,0 @@ -#!/bin/bash -# -# milter This shell script takes care of starting and stopping milter. -# -# chkconfig: 2345 80 30 -# description: Milter is a process that filters messages sent through sendmail. -# processname: milter -# config: /var/log/milter/bms.py -# pidfile: /var/run/milter/milter.pid - -python="python2.3" - -pidof() { - set - "" - if set - `ps -e -o pid,wchan,cmd | grep "rt_sig ${python} bms.py"` && - [ "$3" != "grep" ]; then - echo $1 - return 0 - fi - return 1 -} - -# Source function library. -. /etc/rc.d/init.d/functions - -[ -x /var/log/milter/start.sh ] || exit 0 - -RETVAL=0 -prog="milter" - -start() { - # Start daemons. - - echo -n "Starting $prog: " - daemon --check milter --user mail /var/log/milter/start.sh - RETVAL=$? - echo - [ $RETVAL -eq 0 ] && touch /var/lock/subsys/milter - return $RETVAL -} - -stop() { - # Stop daemons. - echo -n "Shutting down $prog: " - killproc milter - RETVAL=$? - echo - [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/milter - return $RETVAL -} - -# See how we were called. -case "$1" in - start) - start - ;; - stop) - stop - ;; - restart|reload) - stop - start - RETVAL=$? - ;; - condrestart) - if [ -f /var/lock/subsys/milter ]; then - stop - start - RETVAL=$? - fi - ;; - status) - status milter - RETVAL=$? - ;; - *) - echo "Usage: $0 {start|stop|restart|condrestart|status}" - exit 1 -esac - -exit $RETVAL diff --git a/miltermodule.c b/miltermodule.c deleted file mode 100644 index c35c6d5..0000000 --- a/miltermodule.c +++ /dev/null @@ -1,1233 +0,0 @@ -/* Copyright (C) 2001 James Niemira (niemira@colltech.com, urmane@urmane.org) - * Portions Copyright (C) 2001,2002,2003,2004 Stuart Gathman (stuart@bmsi.com) - * - * 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. - * - * milterContext object and thread interface contributed by - * Stuart D. Gathman <stuart@bmsi.com> - */ - -/* This is a Python extension to use Sendmail's libmilter functionality. - It is built using distutils. To install it: - -# python setup.py install - - For additional options: - -$ python setup.py help - - You may need to add additional libraries to setup.py. For instance, - Solaris2.6 requires - - libraries=["milter","smutil","resolv"] - - * $Log$ - * Revision 1.1.1.2 2005/05/31 18:09:06 customdesigned - * Release 0.7.1 - * - * Revision 2.31 2004/08/23 02:24:36 stuart - * Support setbacklog - * - * Revision 2.30 2004/08/21 20:29:53 stuart - * Support option of 11 lines max for mlreply. - * - * Revision 2.29 2004/08/21 04:14:29 stuart - * mlreply support - * - * Revision 2.28 2004/08/21 02:45:21 stuart - * Don't leak int constants if module unloaded. - * - * Revision 2.27 2004/04/06 03:19:59 stuart - * Release 0.6.8 - * - * Revision 2.26 2004/03/04 21:43:06 stuart - * Fix memory leak by removing unused dynamic template buffer, - * thanks again to Alexander Kourakos. - * - * Revision 2.25 2004/03/01 19:45:03 stuart - * Release 0.6.5 - * - * Revision 2.24 2004/03/01 18:56:50 stuart - * Support progress reporting. - * - * Revision 2.23 2004/03/01 18:36:09 stuart - * Plug memory leak. Thanks to Alexander Kourakos. - * - * Revision 2.22 2003/11/02 03:01:46 stuart - * Adjust SMTP error codes after careful reading of standard. - * - * Revision 2.21 2003/06/24 19:57:04 stuart - * Allow removing a python milter callback by setting to None. - * - * Revision 2.20 2003/02/13 17:08:57 stuart - * IPV6 support - * - * Revision 2.19 2003/02/13 16:58:29 stuart - * Support passing None to setreply and chgheader. - * - * Revision 2.18 2002/12/11 16:44:06 stuart - * Support QUARANTINE if supported by libmilter. - * - * Revision 2.17 2002/04/18 20:20:35 stuart - * Fix for NULL hostaddr in connect callback from Jason Erickson. - * - * Revision 2.16 2001/09/26 13:29:09 stuart - * sa_len not supported by linux. - * - * Revision 2.15 2001/09/25 17:28:40 stuart - * Copyrights, documentation, release 0.3.1 - * - * Revision 2.14 2001/09/25 00:36:57 stuart - * Pass hostaddr to python code in format used by standard socket module. - * - * Revision 2.13 2001/09/24 23:44:55 stuart - * Return old callback from setcallback functions. - * - * Revision 2.12 2001/09/24 20:02:30 stuart - * Remove redundant setpriv - * - * Revision 2.11 2001/09/23 22:26:35 stuart - * Update docs. Streamline Milter.py - * update testbms.py to reflect actual sendmail behaviour with multiple - * messages per connection. - * - * Revision 2.10 2001/09/22 15:33:42 stuart - * More doc comment updates. - * - * Revision 2.9 2001/09/22 14:52:27 stuart - * Actually return retval in _generic_return. - * Go over doc comments. - * - * Revision 2.8 2001/09/22 01:59:32 stuart - * Prevent reentrant call of milter_main, which libmilter doesn't support. - * - * Revision 2.7 2001/09/22 01:47:37 stuart - * Forgot to set milter interp. - * - * Revision 2.6 2001/09/22 01:23:53 stuart - * Added proper threading after research in python docs. - * - * Revision 2.5 2001/09/21 20:08:51 stuart - * Release 0.2.3 - * - * Revision 2.4 2001/09/20 16:18:16 stuart - * libmilter checks in_eom state, so we don't have to. - * - * Revision 2.3 2001/09/19 06:02:33 stuart - * Make more stuff static. - * - * Revision 2.1 2001/09/19 04:24:13 stuart - * Use extension type to track context in python. - * - * Revision 1.4 2001/09/18 18:48:28 stuart - * clear private data reference in _clear_context - * - * Revision 1.3 2001/09/15 04:19:37 stuart - * nasty off by 1 mem overwrite bugs in wrap_env - * generic_set_callback - * - * Revision 1.2 2001/09/15 03:15:39 stuart - * several bugs fixed, works smoothly - * - */ - -#ifndef MAX_ML_REPLY -#define MAX_ML_REPLY 32 -#endif -#if MAX_ML_REPLY != 1 && MAX_ML_REPLY != 32 && MAX_ML_REPLY != 11 -#error MAX_ML_REPLY must be 1 or 11 or 32 -#endif -#define _FFR_MULTILINE (MAX_ML_REPLY > 1) - -#include <pthread.h> -#include <netinet/in.h> -#include <Python.h> -#include <libmilter/mfapi.h> - - -/* See if we have IPv4 and/or IPv6 support in this OS and in - * libmilter. We need to make several macro tests because some OS's - * may define some if IPv6 is only partially supported, and we may - * have a sendmail without IPv4 (compiled for IPv6-only). - */ -#ifdef SMFIA_INET -#ifdef AF_INET -#define HAVE_IPV4_SUPPORT /* use this for #ifdef's later on */ -#endif -#endif - -#ifdef SMFIA_INET6 -#ifdef AF_INET6 -#ifdef IN6ADDR_ANY_INIT -#ifdef INET6_ADDRSTRLEN -#define HAVE_IPV6_SUPPORT /* use this for #ifdef's later on */ -/* Now see if it supports the RFC-2553 socket's API spec. Early - * IPv6 "prototype" implementations existed before the RFC was - * published. Unfortunately I know of now good way to do this - * other than with OS-specific tests. - */ -#ifdef linux -#define HAVE_IPV6_RFC2553 -#include <arpa/inet.h> -#endif -#ifdef __HPUX -/* only HP-UX 11.1 or greater supports IPv6 */ -#define HAVE_IPV6_RFC2553 -#endif - -#endif -#endif -#endif -#endif - - -/* Yes, these are static. If you need multiple different callbacks, */ -/* it's cleaner to use multiple filters. */ -static PyObject *connect_callback = NULL; -static PyObject *helo_callback = NULL; -static PyObject *envfrom_callback = NULL; -static PyObject *envrcpt_callback = NULL; -static PyObject *header_callback = NULL; -static PyObject *eoh_callback = NULL; -static PyObject *body_callback = NULL; -static PyObject *eom_callback = NULL; -static PyObject *abort_callback = NULL; -static PyObject *close_callback = NULL; - -staticforward struct smfiDesc description; /* forward declaration */ - -static PyObject *MilterError; -/* The interpreter instance that called milter.main */ -static PyInterpreterState *interp; - -staticforward PyTypeObject milter_ContextType; - -typedef struct { - PyObject_HEAD - SMFICTX *ctx; /* libmilter thread state */ - PyObject *priv; /* user python object */ - PyThreadState *t; /* python thread state */ -} milter_ContextObject; - -/* Return a borrowed reference to the python Context. Create a - new Context if needed. The new Python Context is owned by - the SMFICTX. The python interpreter is locked on successful - return, otherwise not. */ -static milter_ContextObject * -_get_context(SMFICTX *ctx) { - milter_ContextObject *self = smfi_getpriv(ctx); - if (self) { - /* Can't pass on exception since we are called from libmilter */ - if (self->ctx != ctx) return NULL; - PyEval_AcquireThread(self->t); - } - else { - PyThreadState *t = PyThreadState_New(interp); - if (t == NULL) return NULL; - PyEval_AcquireThread(t); /* lock interp */ - self = PyObject_New(milter_ContextObject,&milter_ContextType); - if (!self) { - /* Can't pass on exception since we are called from libmilter */ - PyErr_Clear(); - PyThreadState_Clear(t); - PyEval_ReleaseThread(t); - PyThreadState_Delete(t); - return NULL; - } - self->t = t; - self->ctx = ctx; - Py_INCREF(Py_None); - self->priv = Py_None; /* User Python object */ - smfi_setpriv(ctx, self); - } - return self; -} - -/* Find the SMFICTX from a Python Context. The interpreter must be locked. */ -static SMFICTX * -_find_context(PyObject *c) { - SMFICTX *ctx = NULL; - if (c->ob_type == &milter_ContextType) { - milter_ContextObject *self = (milter_ContextObject *)c; - ctx = self->ctx; - if (smfi_getpriv(ctx) != self) - ctx = NULL; - } - if (ctx == NULL) - PyErr_SetString(MilterError, "bad context"); - return ctx; -} - -/* Release the Python Context for a SMFICTX. */ -static void -_clear_context(SMFICTX *ctx) { - milter_ContextObject *self = smfi_getpriv(ctx); - if (self) { - PyThreadState *t = self->t; - PyEval_AcquireThread(t); - self->t = 0; - self->ctx = 0; - smfi_setpriv(ctx,0); - Py_DECREF(self); - PyThreadState_Clear(t); - PyEval_ReleaseThread(t); - PyThreadState_Delete(t); - } -} - -static void -milter_Context_dealloc(PyObject *s) { - milter_ContextObject *self = (milter_ContextObject *)s; - SMFICTX *ctx = self->ctx; - if (ctx) { - /* Should never happen. If libmilter closes SMFICTX first, then - ctx will be 0. Otherwise, SMFICTX will still hold a reference - to the ContextObject. But if it does, make sure SMFICTX can't - reach us anymore. */ - smfi_setpriv(ctx,0); - } - Py_DECREF(self->priv); - PyObject_DEL(self); -} - -/* Throw an exception if an smfi call failed, otherwise return PyNone. */ -static PyObject * -_generic_return(int val, char *errstr) { - if (val == MI_SUCCESS) { - Py_INCREF(Py_None); - return Py_None; - } else { - PyErr_SetString(MilterError, errstr); - return NULL; - } -} - -static PyObject * -_thread_return(PyThreadState *t,int val,char *errstr) { - PyEval_RestoreThread(t); /* lock interpreter again */ - return _generic_return(val,errstr); -} - -static char milter_set_flags__doc__[] = -"set_flags(int) -> None\n\ -Set flags for filter capabilities; OR of one or more of:\n\ -ADDHDRS - filter may add headers\n\ -CHGBODY - filter may replace body\n\ -ADDRCPT - filter may add recipients\n\ -DELRCPT - filter may delete recipients\n\ -CHGHDRS - filter may change/delete headers"; - -static PyObject * -milter_set_flags(PyObject *self, PyObject *args) { - if (!PyArg_ParseTuple(args, "i", &description.xxfi_flags)) return NULL; - Py_INCREF(Py_None); - return Py_None; -} - -static PyObject * -generic_set_callback(PyObject *args,char *t,PyObject **cb) { - PyObject *callback; - PyObject *oldval; - - if (!PyArg_ParseTuple(args, t, &callback)) return NULL; - if (callback == Py_None) - callback = 0; - else { - if (!PyCallable_Check(callback)) { - PyErr_SetString(PyExc_TypeError, "parameter must be callable"); - return NULL; - } - Py_INCREF(callback); - } - oldval = *cb; - *cb = callback; - if (oldval) - return oldval; - Py_INCREF(Py_None); - return Py_None; -} - -static char milter_set_connect_callback__doc__[] = -"set_connect_callback(Function) -> None\n\ -Sets the Python function invoked when a connection is made to sendmail.\n\ -Function takes args (ctx, hostname, integer, hostaddr) -> int\n\ -ctx -> milterContext for connection, also on remaining callbacks\n\ -hostname -> String - the connecting remote hostname\n\ -integer -> int - the protocol family, one of socket.AF_* values\n\ -hostaddr -> ? - the connecting host address in format used by socket:\n\ - for unix -> pathname - like '/tmp/sockets/s24823'\n\ - for inet -> (ipaddress, port) - like ('10.1.2.3',3701)\n\ - for inet6 -> (ip6address, port, flowlabel, scope) - like ('fec0:0:0:2::4e', 3701, 0, 5)\n\ -\n\ -The return value on this and remaining callbacks should be one of:\n\ -CONTINUE - continue processing\n\ -REJECT - sendmail refuses to accept any more data for message\n\ -ACCEPT - sendmail accepts the message\n\ -DISCARD - sendmail accepts the message and discards it\n\ -TEMPFAIL - milter problem, sendmail will try again later\n\ -\n\ -A python exception encountered in a callback will return TEMPFAIL."; - -static PyObject * -milter_set_connect_callback(PyObject *self, PyObject *args) { - return generic_set_callback(args, - "O:set_connect_callback", &connect_callback); -} - -static char milter_set_helo_callback__doc__[] = -"set_helo_callback(Function) -> None\n\ -Sets the Python function invoked upon SMTP HELO.\n\ -Function takes args (ctx, hostname) -> int\n\ -hostname -> String - the name given with the helo command."; - -static PyObject * -milter_set_helo_callback(PyObject *self, PyObject *args) { - return generic_set_callback(args, "O:set_helo_callback", &helo_callback); -} - -static char milter_set_envfrom_callback__doc__[] = -"set_envfrom_callback(Function) -> None\n\ -Sets the Python function invoked on envelope from.\n\ -Function takes args (ctx, from, *str) -> int\n\ -from -> sender\n\ -str -> Tuple of additional parameters defined by ESMTP."; - -static PyObject * -milter_set_envfrom_callback(PyObject *self, PyObject *args) { - return generic_set_callback(args, "O:set_envfrom_callback", - &envfrom_callback); -} - -static char milter_set_envrcpt_callback__doc__[] = -"set_envrcpt_callback(Function) -> None\n\ -Sets the Python function invoked on each envelope recipient.\n\ -Function takes args (ctx, rcpt, *str) -> int\n\ -tcpt -> string - recipient\n\ -str -> Tuple of additional parameters defined by ESMTP."; - -static PyObject * -milter_set_envrcpt_callback(PyObject *self, PyObject *args) { - return generic_set_callback(args, "O:set_envrcpt_callback", - &envrcpt_callback); -} - -static char milter_set_header_callback__doc__[] = -"set_header_callback(Function) -> None\n\ -Sets the Python function invoked on each message header.\n\ -Function takes args (ctx, field, value) ->int\n\ -field -> String - the header\n\ -value -> String - the header's value"; - -static PyObject * -milter_set_header_callback(PyObject *self, PyObject *args) { - return generic_set_callback(args, "O:set_header_callback", - &header_callback); -} - -static char milter_set_eoh_callback__doc__[] = -"set_eoh_callback(Function) -> None\n\ -Sets the Python function invoked at end of header.\n\ -Function takes args (ctx) -> int"; - -static PyObject * -milter_set_eoh_callback(PyObject *self, PyObject *args) { - return generic_set_callback(args, "O:set_eoh_callback", &eoh_callback); -} - -static char milter_set_body_callback__doc__[] = -"set_body_callback(Function) -> None\n\ -Sets the Python function invoked for each body chunk. There may\n\ -be multiple body chunks passed to the filter. End-of-lines are\n\ -represented as received from SMTP (normally Carriage-Return/Line-Feed).\n\ -Function takes args (ctx, chunk) -> int\n\ -chunk -> String - body data"; - -static PyObject * -milter_set_body_callback(PyObject *self, PyObject *args) { - return generic_set_callback(args, "O:set_body_callback", &body_callback); -} - -static char milter_set_eom_callback__doc__[] = -"set_eom_callback(Function) -> None\n\ -Sets the Python function invoked at end of message.\n\ -This routine is the only place where special operations\n\ -such as modifying the message header, body, or\n\ -envelope can be used.\n\ -Function takes args (ctx) -> int"; - -static PyObject * -milter_set_eom_callback(PyObject *self, PyObject *args) { - return generic_set_callback(args, "O:set_eom_callback", &eom_callback); -} - -static char milter_set_abort_callback__doc__[] = -"set_abort_callback(Function) -> None\n\ -Sets the Python function invoked if message is aborted\n\ -outside of the control of the filter, for example,\n\ -if the SMTP sender issues an RSET command. If the \n\ -abort callback is called, the eom callback will not be\n\ -called and vice versa.\n\ -Function takes args (ctx) -> int"; - -static PyObject * -milter_set_abort_callback(PyObject *self, PyObject *args) { - return generic_set_callback(args, "O:set_abort_callback", &abort_callback); -} - -static char milter_set_close_callback__doc__[] = -"set_close_callback(Function) -> None\n\ -Sets the Python function invoked at end of the connection. This\n\ -is called on close even if the previous mail transaction was aborted.\n\ -Function takes args (ctx) -> int"; - -static PyObject * -milter_set_close_callback(PyObject *self, PyObject *args) { - return generic_set_callback(args, "O:set_close_callback", &close_callback); -} - -/** Report and clear any python exception before returning to libmilter. - The interpreter is locked when we are called, and we unlock it. */ -static int _report_exception(milter_ContextObject *self) { - if (PyErr_Occurred()) { - PyErr_Print(); - PyErr_Clear(); /* must clear since not returning to python */ - PyEval_ReleaseThread(self->t); - smfi_setreply(self->ctx, "451", "4.3.0", "Filter failure"); - return SMFIS_TEMPFAIL; - } - PyEval_ReleaseThread(self->t); - return SMFIS_CONTINUE; -} - -/* Return to libmilter. The ctx must have been initialized or - checked by a successfull call to _get_context(), thereby locking - the interpreter. */ -static int -_generic_wrapper(milter_ContextObject *self, PyObject *cb, PyObject *arglist) { - PyObject *result; - int retval; - - if (arglist == NULL) return _report_exception(self); - result = PyEval_CallObject(cb, arglist); - Py_DECREF(arglist); - if (result == NULL) return _report_exception(self); - retval = PyInt_AsLong(result); - Py_DECREF(result); - if (PyErr_Occurred()) return _report_exception(self); - PyEval_ReleaseThread(self->t); - return retval; -} - -/* Create a string object representing an IP address. - This is always a string of the form 'dd.dd.dd.dd' (with variable - size numbers). Copied from standard socket module. */ - -static PyObject * -makeipaddr(struct sockaddr_in *addr) { - long x = ntohl(addr->sin_addr.s_addr); - char buf[100]; - sprintf(buf, "%d.%d.%d.%d", - (int) (x>>24) & 0xff, (int) (x>>16) & 0xff, - (int) (x>> 8) & 0xff, (int) (x>> 0) & 0xff); - return PyString_FromString(buf); -} - -#ifdef HAVE_IPV6_SUPPORT -static PyObject * -makeip6addr(struct sockaddr_in6 *addr) { - char buf[100]; /* must be at least INET6_ADDRSTRLEN + 1 */ - const char *s = inet_ntop(AF_INET6, &addr->sin6_addr, buf, sizeof buf); - if (s) return PyString_FromString(s); - return PyString_FromString("inet6:unknown"); -} -#endif - -/* These are wrapper functions to call the Python callbacks for each event */ -static int -milter_wrap_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr) { - PyObject *arglist; - milter_ContextObject *c; - if (connect_callback == NULL) return SMFIS_CONTINUE; - c = _get_context(ctx); - if (!c) return SMFIS_TEMPFAIL; - if (hostaddr != NULL) { - switch (hostaddr->sa_family) { - case AF_INET: - { struct sockaddr_in *sa = (struct sockaddr_in *)hostaddr; - PyObject *ipaddr_obj = makeipaddr(sa); - arglist = Py_BuildValue("(Osh(Oi))", c, hostname, hostaddr->sa_family, - ipaddr_obj, ntohs(sa->sin_port)); - Py_DECREF(ipaddr_obj); - } - break; - case AF_UNIX: - arglist = Py_BuildValue("(Oshs)", c, hostname, hostaddr->sa_family, - hostaddr->sa_data); - break; -#ifdef HAVE_IPV6_SUPPORT - case AF_INET6: - { struct sockaddr_in6 *sa = (struct sockaddr_in6 *)hostaddr; - PyObject *ip6addr_obj = makeip6addr(sa); - long scope_id = 0; -#ifdef HAVE_IPV6_RFC2553 - scope_id = ntohl(sa->sin6_scope_id); -#endif - arglist = Py_BuildValue("(Osh(Oiii))", c, hostname, hostaddr->sa_family, - ip6addr_obj, - ntohs(sa->sin6_port), - ntohl(sa->sin6_flowinfo), - scope_id); - Py_DECREF(ip6addr_obj); - } - break; -#endif - default: - arglist = Py_BuildValue("(OshO)", c, hostname, hostaddr->sa_family, - Py_None); - } - } - else - arglist = Py_BuildValue("(OshO)", c, hostname, 0, Py_None); - return _generic_wrapper(c, connect_callback, arglist); -} - -static int -milter_wrap_helo(SMFICTX *ctx, char *helohost) { - PyObject *arglist; - milter_ContextObject *c; - - if (helo_callback == NULL) return SMFIS_CONTINUE; - c = _get_context(ctx); - if (!c) return SMFIS_TEMPFAIL; - arglist = Py_BuildValue("(Os)", c, helohost); - return _generic_wrapper(c, helo_callback, arglist); -} - -static int -generic_env_wrapper(SMFICTX *ctx, PyObject*cb, char **argv, const char *name) { - PyObject *arglist; - milter_ContextObject *self; - int count = 0; - int i; - char **p = argv; - - if (cb == NULL) return SMFIS_CONTINUE; - - self = _get_context(ctx); - if (!self) return SMFIS_TEMPFAIL; - - /* Count how many strings we've been passed. */ - while (*p++ != NULL) count++; - /* how to build the value in steps? Cheat by copying from */ - /* Python/modsupport.c do_mktuple() and do_mkvalue() */ - if ((arglist = PyTuple_New(count+1)) == NULL) - return _report_exception(self); - /* Add in the context first */ - Py_INCREF(self); /* PyTuple_SetItem takes over reference */ - PyTuple_SetItem(arglist, 0, (PyObject *)self); - /* Now do all the strings */ - for (i=0;i<count;i++) { - /* There's some error checking performed in do_mkvalue() for a string */ - /* that's not currently done here - it probably should be */ - PyObject *o = PyString_FromStringAndSize(argv[i], strlen(argv[i])); - if (o == NULL) { /* out of memory */ - Py_DECREF(arglist); - return _report_exception(self); - } - PyTuple_SetItem(arglist, i + 1, o); - } - return _generic_wrapper(self, cb, arglist); -} - -static int -milter_wrap_envfrom(SMFICTX *ctx, char **argv) { - return generic_env_wrapper(ctx,envfrom_callback,argv,"milter_wrap_envfrom"); -} - -static int -milter_wrap_envrcpt(SMFICTX *ctx, char **argv) { - return generic_env_wrapper(ctx,envrcpt_callback,argv,"milter_wrap_envrcpt"); -} - -static int -milter_wrap_header(SMFICTX *ctx, char *headerf, char *headerv) { - PyObject *arglist; - milter_ContextObject *c; - - if (header_callback == NULL) return SMFIS_CONTINUE; - c = _get_context(ctx); - if (!c) return SMFIS_TEMPFAIL; - arglist = Py_BuildValue("(Oss)", c, headerf, headerv); - return _generic_wrapper(c, header_callback, arglist); -} - -static int -generic_noarg_wrapper(SMFICTX *ctx,PyObject *cb,const char *name) { - PyObject *arglist; - milter_ContextObject *c; - if (cb == NULL) return SMFIS_CONTINUE; - c = _get_context(ctx); - if (!c) return SMFIS_TEMPFAIL; - arglist = Py_BuildValue("(O)", c); - return _generic_wrapper(c, cb, arglist); -} - -static int -milter_wrap_eoh(SMFICTX *ctx) { - return generic_noarg_wrapper(ctx,eoh_callback,"milter_wrap_eoh"); -} - -static int -milter_wrap_body(SMFICTX *ctx, u_char *bodyp, size_t bodylen) { - PyObject *arglist; - milter_ContextObject *c; - - if (body_callback == NULL) return SMFIS_CONTINUE; - c = _get_context(ctx); - if (!c) return SMFIS_TEMPFAIL; - /* Unclear whether this should be s#, z#, or t# */ - arglist = Py_BuildValue("(Os#)", c, bodyp, bodylen); - return _generic_wrapper(c, body_callback, arglist); -} - -static int -milter_wrap_eom(SMFICTX *ctx) { - return generic_noarg_wrapper(ctx,eom_callback,"milter_wrap_eom"); -} - -static int -milter_wrap_abort(SMFICTX *ctx) { - /* libmilter still calls close after abort */ - return generic_noarg_wrapper(ctx,abort_callback,"milter_wrap_abort"); -} - -static int -milter_wrap_close(SMFICTX *ctx) { - int r = generic_noarg_wrapper(ctx,close_callback,"milter_wrap_close"); - /* FIXME: It is inefficient to have released the interp lock only to - acquire it again in _clear_context. We can tell _generic_return and - friends not to release the lock by, for instance, setting self->t to NULL. - However, first we make it work. */ - _clear_context(ctx); - return r; -} - -static char milter_register__doc__[] = -"register(name) -> None\n\ -Registers the milter name with current callbacks, and flags.\n\ -Required before main() is called."; - -static PyObject * -milter_register(PyObject *self, PyObject *args) { - if (!PyArg_ParseTuple(args, "s:register", &description.xxfi_name)) - return NULL; - return _generic_return(smfi_register(description), "cannot register"); -} - -static char milter_main__doc__[] = -"main() -> None\n\ -Main milter routine. Set any callbacks, and flags desired, then call\n\ -setconn(), then call register(name), and finally call main()."; - -static PyObject * -milter_main(PyObject *self, PyObject *args) { - PyThreadState *_main; - PyObject *o; - if (!PyArg_ParseTuple(args, ":main")) return NULL; - if (interp != NULL) { - PyErr_SetString(MilterError,"milter module in use"); - return NULL; - } - /* libmilter requires thread support */ - PyEval_InitThreads(); - /* let other threads run while in smfi_main() */ - interp = PyThreadState_Get()->interp; - _main = PyEval_SaveThread(); /* must be done before smfi_main() */ - o = _thread_return(_main,smfi_main(), "cannot run main"); - interp = NULL; - return o; -} - -static char milter_setdbg__doc__[] = -"setdbg(int) -> None\n\ -Sets debug level in sendmail/libmilter source. Dubious usefulness."; - -static PyObject * -milter_setdbg(PyObject *self, PyObject *args) { - int val; - if (!PyArg_ParseTuple(args, "i:setdbg", &val)) return NULL; - return _generic_return(smfi_setdbg(val), "cannot set debug value"); -} - -static char milter_setbacklog__doc__[] = -"setbacklog(int) -> None\n\ -Set the TCP connection queue size for the milter socket."; - -static PyObject * -milter_setbacklog(PyObject *self, PyObject *args) { - int val; - - if (!PyArg_ParseTuple(args, "i:setbacklog", &val)) return NULL; - return _generic_return(smfi_setbacklog(val), "cannot set backlog"); -} - -static char milter_settimeout__doc__[] = -"settimeout(int) -> None\n\ -Set the time (in seconds) that sendmail will wait before\n\ -considering this filter dead."; - -static PyObject * -milter_settimeout(PyObject *self, PyObject *args) { - int val; - - if (!PyArg_ParseTuple(args, "i:settimeout", &val)) return NULL; - return _generic_return(smfi_settimeout(val), "cannot set timeout"); -} - -static char milter_setconn__doc__[] = -"setconn(filename) -> None\n\ -Sets the pathname to the unix, inet, or inet6 socket that\n\ -sendmail will use to communicate with this filter. By default,\n\ -a unix domain socket is used. It must not exist,\n\ -and sendmail will throw warnings if, eg, the file is under a\n\ -group or world writable directory. This call is \n\ -mandatory, and is invoked before register() and main().\n\ - setconn('unix:/var/run/pythonfilter')\n\ - setconn('inet:8800') # listen on ANY interface\n\ - setconn('inet:7871@publichost')\n\ - setconn('inet6:8020')"; - -static PyObject * -milter_setconn(PyObject *self, PyObject *args) { - char *str; - if (!PyArg_ParseTuple(args, "s:setconn", &str)) return NULL; - return _generic_return(smfi_setconn(str), "cannot set connection"); -} - -static char milter_stop__doc__[] = -"stop() -> None\n\ -This function appears to be a controlled method to tell sendmail to\n\ -stop using this filter. It will close the socket."; - -static PyObject * -milter_stop(PyObject *self, PyObject *args) { - PyThreadState *t; - if (!PyArg_ParseTuple(args, ":stop")) return NULL; - t = PyEval_SaveThread(); - return _thread_return(t,smfi_stop(), "cannot stop"); -} - -static char milter_getsymval__doc__[] = -"getsymval(String) -> String\n\ -Returns a symbol's value. Context-dependent, and unclear from the dox."; - -static PyObject * -milter_getsymval(PyObject *self, PyObject *args) { - char *str; - SMFICTX *ctx; - - if (!PyArg_ParseTuple(args, "s:getsymval", &str)) return NULL; - ctx = _find_context(self); - if (ctx == NULL) return NULL; - return Py_BuildValue("s", smfi_getsymval(ctx, str)); -} - -static char milter_setreply__doc__[] = -"setreply(rcode, xcode, message) -> None\n\ -Sets the specific reply code to be used in response\n\ -to the active command.\n\ -rcode - The three-digit (RFC 821) SMTP reply code to be returned\n\ -xcode - The extended (RFC 2034) reply code\n\ -message - The text part of the SMTP reply\n\ -These should all be strings."; - -static PyObject * -milter_setreply(PyObject *self, PyObject *args) { - char *rcode; - char *xcode; - char *message[MAX_ML_REPLY]; - char fmt[MAX_ML_REPLY + 16]; - SMFICTX *ctx; - int i; - strcpy(fmt,"sz|"); - for (i = 0; i < MAX_ML_REPLY; ++i) { - message[i] = 0; - fmt[i+3] = 's'; - } - strcpy(fmt+i+3,":setreply"); - if (!PyArg_ParseTuple(args, fmt, - &rcode, &xcode, message -#if MAX_ML_REPLY > 1 - ,message+1,message+2,message+3,message+4,message+5,message+6, - message+7,message+8,message+9,message+10 -#if MAX_ML_REPLY > 11 - ,message+11,message+12,message+13,message+14,message+15, - message+16,message+17,message+18,message+19,message+20, - message+21,message+22,message+23,message+24,message+25, - message+26,message+27,message+28,message+29,message+30, - message+31 -#endif -#endif - )) - return NULL; - ctx = _find_context(self); - if (ctx == NULL) return NULL; -#if MAX_ML_REPLY > 1 - /* - * C varargs might be convenient for some things, but they sure are a pain - * when the number of args is not known at compile time. - */ - if (message[0] && message[1]) - return _generic_return(smfi_setmlreply(ctx, rcode, xcode, - message[0], - message[1],message[2],message[3],message[4],message[5], - message[6],message[7],message[8],message[9],message[10], -#if MAX_ML_REPLY > 11 - message[11],message[12],message[13],message[14],message[15], - message[16],message[17],message[18],message[19],message[20], - message[21],message[22],message[23],message[24],message[25], - message[26],message[27],message[28],message[29],message[30], - message[31], -#endif - (char *)0 - ), "cannot set reply"); -#endif - return _generic_return(smfi_setreply(ctx, rcode, xcode, message[0]), - "cannot set reply"); -} - -static char milter_addheader__doc__[] = -"addheader(field, value) -> None\n\ -Add a header to the message. This header is not passed to other\n\ -filters. It is not checked for standards compliance;\n\ -the mail filter must ensure that no protocols are violated\n\ -as a result of adding this header.\n\ -field - header field name\n\ -value - header field value\n\ -Both are strings. This function can only be called from the EOM callback."; - -static PyObject * -milter_addheader(PyObject *self, PyObject *args) { - char *headerf; - char *headerv; - SMFICTX *ctx; - PyThreadState *t; - - if (!PyArg_ParseTuple(args, "ss:addheader", &headerf, &headerv)) return NULL; - ctx = _find_context(self); - if (ctx == NULL) return NULL; - t = PyEval_SaveThread(); - return _thread_return(t,smfi_addheader(ctx, headerf, headerv), - "cannot add header"); -} - -static char milter_chgheader__doc__[] = -"chgheader(field, int, value) -> None\n\ -Change/delete a header in the message. \n\ -It is not checked for standards compliance; the mail filter\n\ -must ensure that no protocols are violated as a result of adding this header.\n\ -field - header field name\n\ -int - the Nth occurence of this header\n\ -value - header field value\n\ -field and value are strings.\n\ -This function can only be called from the EOM callback."; - -static PyObject * -milter_chgheader(PyObject *self, PyObject *args) { - char *headerf; - int index; - char *headerv; - SMFICTX *ctx; - PyThreadState *t; - - if (!PyArg_ParseTuple(args, "siz:chgheader", &headerf, &index, &headerv)) - return NULL; - ctx = _find_context(self); - if (ctx == NULL) return NULL; - t = PyEval_SaveThread(); - return _thread_return(t,smfi_chgheader(ctx, headerf, index, headerv), - "cannot change header"); -} - -static char milter_addrcpt__doc__[] = -"addrcpt(string) -> None\n\ -Add a recipient to the envelope. It must be in the same format\n\ -as is passed to the envrcpt callback in the first tuple element.\n\ -This function can only be called from the EOM callback."; - -static PyObject * -milter_addrcpt(PyObject *self, PyObject *args) { - char *rcpt; - SMFICTX *ctx; - PyThreadState *t; - - if (!PyArg_ParseTuple(args, "s:addrcpt", &rcpt)) return NULL; - ctx = _find_context(self); - if (ctx == NULL) return NULL; - t = PyEval_SaveThread(); - return _thread_return(t,smfi_addrcpt(ctx, rcpt), "cannot add recipient"); -} - -static char milter_delrcpt__doc__[] = -"delrcpt(string) -> None\n\ -Delete a recipient from the envelope.\n\ -This function can only be called from the EOM callback."; - -static PyObject * -milter_delrcpt(PyObject *self, PyObject *args) { - char *rcpt; - SMFICTX *ctx; - PyThreadState *t; - - if (!PyArg_ParseTuple(args, "s:delrcpt", &rcpt)) return NULL; - ctx = _find_context(self); - if (ctx == NULL) return NULL; - t = PyEval_SaveThread(); - return _thread_return(t,smfi_delrcpt(ctx, rcpt), - "cannot delete recipient"); -} - -static char milter_replacebody__doc__[] = -"replacebody(string) -> None\n\ -Replace the body of the message. This routine may be called multiple\n\ -times if the body is longer than convenient to send in one call. End of\n\ -line should be represented as Carriage-Return/Line Feed. This function\n\ -can only be called from the EOM callback."; - -static PyObject * -milter_replacebody(PyObject *self, PyObject *args) { - char *bodyp; - int bodylen; - SMFICTX *ctx; - PyThreadState *t; - - if (!PyArg_ParseTuple(args, "s#", &bodyp, &bodylen)) return NULL; - ctx = _find_context(self); - if (ctx == NULL) return NULL; - t = PyEval_SaveThread(); - return _thread_return(t,smfi_replacebody(ctx, bodyp, bodylen), - "cannot replace message body"); -} - -static char milter_setpriv__doc__[] = -"setpriv(object) -> object\n\ -Associates any Python object with this context, and returns\n\ -the old value or None. Use this to\n\ -provide thread-safe storage for data instead of using global variables\n\ -for things like filenames, etc. This function stores only one object\n\ -per context, but that object can in turn store many others."; - -static PyObject * -milter_setpriv(PyObject *self, PyObject *args) { - PyObject *o; - PyObject *old; - milter_ContextObject *s = (milter_ContextObject *)self; - - if (!PyArg_ParseTuple(args, "O:setpriv", &o)) return NULL; - /* PyArg_ParseTuple's O format does not increase the reference count on - the target. Since we're going to save it and almost certainly assign - to another object later, we incref it here, and only decref it in - the dealloc method. */ - Py_INCREF(o); - old = s->priv; - s->priv = o; - /* We return the old value. The caller will DECREF it if not used. */ - return old; -} - -static char milter_getpriv__doc__[] = -"getpriv() -> None\n\ -Returns the Python object associated with the current context (if any).\n\ -Use this in conjunction with setpriv to keep track of data in a thread-safe\n\ -manner."; - -static PyObject * -milter_getpriv(PyObject *self, PyObject *args) { - PyObject *o; - milter_ContextObject *s = (milter_ContextObject *)self; - - if (!PyArg_ParseTuple(args, ":getpriv")) return NULL; - o = s->priv; - Py_INCREF(o); - return o; -} - -#ifdef SMFIF_QUARANTINE -static char milter_quarantine__doc__[] = -"quarantine(string) -> None\n\ -Place the message in quarantine. A string with a description of the reason\n\ -is the only argument."; - -static PyObject * -milter_quarantine(PyObject *self, PyObject *args) { - char *reason; - SMFICTX *ctx; - PyThreadState *t; - - if (!PyArg_ParseTuple(args, "s:quarantine", &reason)) return NULL; - ctx = _find_context(self); - if (ctx == NULL) return NULL; - t = PyEval_SaveThread(); - return _thread_return(t,smfi_quarantine(ctx, reason), - "cannot quarantine message"); -} -#endif - -#if _FFR_SMFI_PROGRESS -static char milter_progress__doc__[] = -"progress() -> None\n\ -Notify the MTA that we are working on a message so it will reset timeouts."; - -static PyObject * -milter_progress(PyObject *self, PyObject *args) { - SMFICTX *ctx; - PyThreadState *t; - - if (!PyArg_ParseTuple(args, ":progress")) return NULL; - ctx = _find_context(self); - if (ctx == NULL) return NULL; - t = PyEval_SaveThread(); - return _thread_return(t,smfi_progress(ctx), "cannot notify progress"); -} -#endif - -static PyMethodDef context_methods[] = { - { "getsymval", milter_getsymval, METH_VARARGS, milter_getsymval__doc__}, - { "setreply", milter_setreply, METH_VARARGS, milter_setreply__doc__}, - { "addheader", milter_addheader, METH_VARARGS, milter_addheader__doc__}, - { "chgheader", milter_chgheader, METH_VARARGS, milter_chgheader__doc__}, - { "addrcpt", milter_addrcpt, METH_VARARGS, milter_addrcpt__doc__}, - { "delrcpt", milter_delrcpt, METH_VARARGS, milter_delrcpt__doc__}, - { "replacebody", milter_replacebody, METH_VARARGS, milter_replacebody__doc__}, - { "setpriv", milter_setpriv, METH_VARARGS, milter_setpriv__doc__}, - { "getpriv", milter_getpriv, METH_VARARGS, milter_getpriv__doc__}, -#ifdef SMFIF_QUARANTINE - { "quarantine", milter_quarantine, METH_VARARGS, milter_quarantine__doc__}, -#endif -#if _FFR_SMFI_PROGRESS - { "progress", milter_progress, METH_VARARGS, milter_progress__doc__}, -#endif - { NULL, NULL } -}; - -static PyObject * -milter_Context_getattr(PyObject *self, char *name) { - return Py_FindMethod(context_methods, self, name); -} - -static struct smfiDesc description = { /* Set some reasonable defaults */ - "pythonfilter", - SMFI_VERSION, - SMFI_CURR_ACTS, - milter_wrap_connect, - milter_wrap_helo, - milter_wrap_envfrom, - milter_wrap_envrcpt, - milter_wrap_header, - milter_wrap_eoh, - milter_wrap_body, - milter_wrap_eom, - milter_wrap_abort, - milter_wrap_close -}; - -static PyMethodDef milter_methods[] = { - { "set_flags", milter_set_flags, METH_VARARGS, milter_set_flags__doc__}, - { "set_connect_callback", milter_set_connect_callback, METH_VARARGS, milter_set_connect_callback__doc__}, - { "set_helo_callback", milter_set_helo_callback, METH_VARARGS, milter_set_helo_callback__doc__}, - { "set_envfrom_callback", milter_set_envfrom_callback, METH_VARARGS, milter_set_envfrom_callback__doc__}, - { "set_envrcpt_callback", milter_set_envrcpt_callback, METH_VARARGS, milter_set_envrcpt_callback__doc__}, - { "set_header_callback", milter_set_header_callback, METH_VARARGS, milter_set_header_callback__doc__}, - { "set_eoh_callback", milter_set_eoh_callback, METH_VARARGS, milter_set_eoh_callback__doc__}, - { "set_body_callback", milter_set_body_callback, METH_VARARGS, milter_set_body_callback__doc__}, - { "set_eom_callback", milter_set_eom_callback, METH_VARARGS, milter_set_eom_callback__doc__}, - { "set_abort_callback", milter_set_abort_callback, METH_VARARGS, milter_set_abort_callback__doc__}, - { "set_close_callback", milter_set_close_callback, METH_VARARGS, milter_set_close_callback__doc__}, - { "register", milter_register, METH_VARARGS, milter_register__doc__}, - { "main", milter_main, METH_VARARGS, milter_main__doc__}, - { "setdbg", milter_setdbg, METH_VARARGS, milter_setdbg__doc__}, - { "settimeout", milter_settimeout, METH_VARARGS, milter_settimeout__doc__}, - { "setbacklog", milter_setbacklog, METH_VARARGS, milter_setbacklog__doc__}, - { "setconn", milter_setconn, METH_VARARGS, milter_setconn__doc__}, - { "stop", milter_stop, METH_VARARGS, milter_stop__doc__}, - { NULL, NULL } -}; - -static PyTypeObject milter_ContextType = { - PyObject_HEAD_INIT(&PyType_Type) - 0, - "milterContext", - sizeof(milter_ContextObject), - 0, - milter_Context_dealloc, /* tp_dealloc */ - 0, /* tp_print */ - milter_Context_getattr, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_compare */ - 0, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - 0, /* tp_hash */ - 0, /* tp_call */ - 0, /* tp_str */ - 0, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT, /* tp_flags */ -}; - -static char milter_documentation[] = -"This module interfaces with Sendmail's libmilter functionality,\n\ -allowing one to write email filters directly in Python.\n\ -Libmilter is currently marked FFR, and needs to be explicitly installed.\n\ -See <sendmailsource>/libmilter/README for details on setting it up.\n"; - -static void setitem(PyObject *d,const char *name,long val) { - PyObject *v = PyInt_FromLong(val); - PyDict_SetItemString(d,name,v); - Py_DECREF(v); -} - -void -initmilter(void) { - PyObject *m, *d; - - m = Py_InitModule4("milter", milter_methods, milter_documentation, - (PyObject*)NULL, PYTHON_API_VERSION); - d = PyModule_GetDict(m); - MilterError = PyErr_NewException("milter.error", NULL, NULL); - PyDict_SetItemString(d,"error", MilterError); - setitem(d,"SUCCESS", MI_SUCCESS); - setitem(d,"FAILURE", MI_FAILURE); - setitem(d,"VERSION", SMFI_VERSION); - setitem(d,"ADDHDRS", SMFIF_ADDHDRS); - setitem(d,"CHGBODY", SMFIF_CHGBODY); - setitem(d,"MODBODY", SMFIF_MODBODY); - setitem(d,"ADDRCPT", SMFIF_ADDRCPT); - setitem(d,"DELRCPT", SMFIF_DELRCPT); - setitem(d,"CHGHDRS", SMFIF_CHGHDRS); - setitem(d,"V1_ACTS", SMFI_V1_ACTS); - setitem(d,"V2_ACTS", SMFI_V2_ACTS); - setitem(d,"CURR_ACTS", SMFI_CURR_ACTS); -#ifdef SMFIF_QUARANTINE - setitem(d,"QUARANTINE",SMFIF_QUARANTINE); -#endif - setitem(d,"CONTINUE", SMFIS_CONTINUE); - setitem(d,"REJECT", SMFIS_REJECT); - setitem(d,"DISCARD", SMFIS_DISCARD); - setitem(d,"ACCEPT", SMFIS_ACCEPT); - setitem(d,"TEMPFAIL", SMFIS_TEMPFAIL); -} diff --git a/mime.py b/mime.py deleted file mode 100644 index df95373..0000000 --- a/mime.py +++ /dev/null @@ -1,503 +0,0 @@ -# $Log$ -# Revision 1.2 2005/06/02 04:18:55 customdesigned -# Update copyright notices after reading article on /. -# -# Revision 1.1.1.4 2005/05/31 18:23:49 customdesigned -# Development changes since 0.7.2 -# -# Revision 1.62 2005/02/14 22:31:17 stuart -# _parseparam replacement not needed for python2.4 -# -# Revision 1.61 2005/02/12 02:11:11 stuart -# Pass unit tests with python2.4. -# -# Revision 1.60 2005/02/11 18:34:14 stuart -# Handle garbage after quote in boundary. -# -# Revision 1.59 2005/02/10 01:10:59 stuart -# Fixed MimeMessage.ismodified() -# -# Revision 1.58 2005/02/10 00:56:49 stuart -# Runs with python2.4. Defang not working correctly - more work needed. -# -# Revision 1.57 2004/11/20 16:37:52 stuart -# fix regex for splitting header and body -# -# Revision 1.56 2004/11/09 20:33:51 stuart -# Recognize more dynamic PTR variations. -# -# Revision 1.55 2004/10/06 21:39:20 stuart -# Handle message attachments with boundary errors by not parsing them -# until needed. -# -# Revision 1.54 2004/08/18 01:59:46 stuart -# Handle mislabeled multipart messages -# -# Revision 1.53 2004/04/24 22:53:20 stuart -# Rename some local variables to avoid shadowing builtins -# -# Revision 1.52 2004/04/24 22:47:13 stuart -# Convert header values to str -# -# Revision 1.51 2004/03/25 03:19:10 stuart -# Correctly defang rfc822 attachments when boundary specified with -# content-type message/rfc822. -# -# Revision 1.50 2003/10/15 22:01:00 stuart -# Test for and work around email bug with encoded filenames. -# -# Revision 1.49 2003/09/04 18:48:13 stuart -# Support python-2.2.3 -# -# Revision 1.48 2003/09/02 00:27:27 stuart -# Should have full milter based dspam support working -# -# Revision 1.47 2003/08/26 06:08:18 stuart -# Use new python boolean since we now require 2.2.2 -# -# Revision 1.46 2003/08/26 05:01:38 stuart -# Release 0.6.0 -# -# Revision 1.45 2003/08/26 04:01:24 stuart -# Use new email module for parsing mail. Still need mime module to -# provide various bug fixes to email module, and maintain some compatibility -# with old milter code. -# - -# This module provides a "defang" function to replace naughty attachments -# with a warning message. - -# Author: Stuart D. Gathman <stuart@bmsi.com> -# Copyright 2001,2002,2003,2004,2005 Business Management Systems, Inc. -# This code is under the GNU General Public License. See COPYING for details. - -import StringIO -import socket -import Milter -import zipfile - -import email -import email.Message -from email.Message import Message -from email.Generator import Generator -from email.Utils import quote -from email import Utils -from email.Parser import Parser -from email import Errors - -from types import ListType,StringType - -class MimeGenerator(Generator): - def _dispatch(self, msg): - # Get the Content-Type: for the message, then try to dispatch to - # self._handle_<maintype>_<subtype>(). If there's no handler for the - # full MIME type, then dispatch to self._handle_<maintype>(). If - # that's missing too, then dispatch to self._writeBody(). - main = msg.get_content_maintype() - if msg.is_multipart() and main.lower() != 'multipart': - self._handle_multipart(msg) - else: - Generator._dispatch(self,msg) - -def unquote(s): - """Remove quotes from a string.""" - if len(s) > 1: - if s.startswith('"'): - if s.endswith('"'): - s = s[1:-1] - else: # remove garbage after trailing quote - try: s = s[1:s[1:].index('"')+1] - except: - return s - return s.replace('\\\\', '\\').replace('\\"', '"') - if s.startswith('<') and s.endswith('>'): - return s[1:-1] - return s - -from types import TupleType - -def _unquotevalue(value): - if isinstance(value, TupleType): - return value[0], value[1], unquote(value[2]) - else: - return unquote(value) - -#email.Message._unquotevalue = _unquotevalue - -from email.Message import _parseparam - -# Enhance email.Message -# - Provide a headerchange event for integration with Milter -# Headerchange attribute can be assigned a function to be called when -# changing headers. The signature is: -# headerchange(msg,name,value) -> None -# - Track modifications to headers of body or any part independently - -class MimeMessage(Message): - """Version of email.Message.Message compatible with old mime module - """ - def __init__(self,fp=None,seekable=1): - Message.__init__(self) - self.headerchange = None - self.submsg = None - self.modified = False - - def get_param(self, param, failobj=None, header='content-type', unquote=True): - val = Message.get_param(self,param,failobj,header,unquote) - if val != failobj and param == 'boundary' and unquote: - # unquote boundaries an extra time, test case testDefang5 - return _unquotevalue(val) - return val - - getfilename = Message.get_filename - ismultipart = Message.is_multipart - getheaders = Message.get_all - gettype = Message.get_content_type - getparam = Message.get_param - - def getparams(self): return self.get_params([]) - - def getname(self): - return self.get_param('name') - - def getnames(self,scan_zip=False): - """Return a list of (attr,name) pairs of attributes that IE might - interpret as a name - and hence decide to execute this message.""" - names = [] - for attr,val in self._get_params_preserve([],'content-type'): - if isinstance(val, TupleType): - # It's an RFC 2231 encoded parameter - newvalue = _unquotevalue(val) - if val[0]: - val = unicode(newvalue[2], newvalue[0]) - else: - val = unicode(newvalue[2]) - else: - val = _unquotevalue(val.strip()) - names.append((attr,val)) - names += [("filename",self.get_filename())] - if scan_zip: - for key,name in names: - if name and name.lower().endswith('.zip'): - txt = self.get_payload(decode=True) - fp = StringIO.StringIO(txt) - zipf = zipfile.ZipFile(fp,'r') - for nm in zipf.namelist(): - names.append(('zipname',nm)) - return names - - def ismodified(self): - "True if this message or a subpart has been modified." - if not self.is_multipart(): - if isinstance(self.submsg,Message): - return self.submsg.ismodified() - return self.modified - if self.modified: return True - for i in self.get_payload(): - if i.ismodified(): return True - return False - - def dump(self,file,unixfrom=False): - "Write this message (and all subparts) to a file" - g = MimeGenerator(file) - g.flatten(self,unixfrom=unixfrom) - - def as_string(self, unixfrom=False): - "Return the entire formatted message as a string." - fp = StringIO.StringIO() - self.dump(fp,unixfrom=unixfrom) - return fp.getvalue() - - def getencoding(self): - return self.get('content-transfer-encoding',None) - - # Decode body to stream according to transfer encoding, return encoding name - def decode(self,filt): - try: - filt.write(self.get_payload(decode=True)) - except: - pass - return self.getencoding() - - def get_payload_decoded(self): - return self.get_payload(decode=True) - - def __setitem__(self, name, value): - rc = Message.__setitem__(self,name,value) - self.modified = True - if self.headerchange: self.headerchange(self,name,str(value)) - return rc - - def __delitem__(self, name): - if self.headerchange: self.headerchange(self,name,None) - rc = Message.__delitem__(self,name) - self.modified = True - return rc - - def get_payload(self,i=None,decode=False): - msg = self.submsg - if isinstance(msg,Message) and msg.ismodified(): - self.set_payload([msg]) - return Message.get_payload(self,i,decode) - - def set_payload(self, val, charset=None): - self.modified = True - try: - val.seek(0) - val = val.read() - except: pass - Message.set_payload(self,val,charset) - self.submsg = None - - def get_submsg(self): - t = self.get_content_type().lower() - if t == 'message/rfc822' or t.startswith('multipart/'): - if not self.submsg: - txt = self.get_payload() - if type(txt) == str: - txt = self.get_payload(decode=True) - self.submsg = email.message_from_string(txt,MimeMessage) - for part in self.submsg.walk(): - part.modified = False - else: - self.submsg = txt[0] - return self.submsg - return None - -def message_from_file(fp): - msg = email.message_from_file(fp,MimeMessage) - for part in msg.walk(): - part.modified = False - assert not msg.ismodified() - return msg - -extlist = ''.join(""" -ade,adp,asd,asx,asp,bas,bat,chm,cmd,com,cpl,crt,dll,exe,hlp,hta,inf,ins,isp,js, -jse,lnk,mdb,mde,msc,msi,msp,mst,ocx,pcd,pif,reg,scr,sct,shs,url,vb,vbe,vbs,wsc, -wsf,wsh -""".split()) -bad_extensions = map(lambda x:'.' + x,extlist.split(',')) - -def check_ext(name): - "Check a name for dangerous Winblows extensions." - if not name: return name - lname = name.lower() - for ext in bad_extensions: - if lname.endswith(ext): return name - return None - -virus_msg = """This message appeared to contain a virus. -It was originally named '%s', and has been removed. -A copy of your original message was saved as '%s:%s'. -See your administrator. -""" - -def check_name(msg,savname=None,ckname=check_ext,scan_zip=False): - "Replace attachment with a warning if its name is suspicious." - for key,name in msg.getnames(scan_zip): - badname = ckname(name) - if badname: - hostname = socket.gethostname() - if key == 'zipname': - badname = msg.get_filename() - msg.set_payload(virus_msg % (badname,hostname,savname)) - del msg["content-type"] - del msg["content-disposition"] - del msg["content-transfer-encoding"] - name = "WARNING.TXT" - msg["Content-Type"] = "text/plain; name="+name - break - return Milter.CONTINUE - -import email.Iterators - -def check_attachments(msg,check): - """Scan attachments. -msg MimeMessage -check function(MimeMessage): int - Return CONTINUE, REJECT, ACCEPT - """ - if msg.is_multipart(): - for i in msg.get_payload(): - rc = check_attachments(i,check) - if rc != Milter.CONTINUE: return rc - return Milter.CONTINUE - return check(msg) - -# save call context for Python without nested_scopes -class _defang: - - def __init__(self,scan_html=True): - self.scan_html = scan_html - - def _chk_name(self,msg): - rc = check_name(msg,self._savname,self._check,self.scan_zip) - if self.scan_html: - check_html(msg,self._savname) # remove scripts from HTML - if self.scan_rfc822: - msg = msg.get_submsg() - if isinstance(msg,Message): - return check_attachments(msg,self._chk_name) - return rc - - def __call__(self,msg,savname=None,check=check_ext,scan_rfc822=True, - scan_zip=False): - """Compatible entry point. - Replace all attachments with dangerous names.""" - self._savname = savname - self._check = check - self.scan_rfc822 = scan_rfc822 - self.scan_zip = scan_zip - check_attachments(msg,self._chk_name) - if msg.ismodified(): - return True - return False - -# emulate old defang function -defang = _defang() - -import sgmllib - -import re -declname = re.compile(r'[a-zA-Z][-_.a-zA-Z0-9]*\s*') -declstringlit = re.compile(r'(\'[^\']*\'|"[^"]*")\s*') - -class SGMLFilter(sgmllib.SGMLParser): - """Parse HTML and pass through all constructs unchanged. It is intended for - derived classes to implement exceptional processing for selected cases. - """ - def __init__(self,out): - sgmllib.SGMLParser.__init__(self) - self.out = out - - def handle_comment(self,comment): - self.out.write("<!--%s-->" % comment) - - def unknown_starttag(self,tag,attr): - if hasattr(self,"get_starttag_text"): - self.out.write(self.get_starttag_text()) - else: - self.out.write("<%s" % tag) - for (key,val) in attr: - self.out.write(' %s="%s"' % (key,val)) - self.out.write('>') - - def handle_data(self,data): - self.out.write(data) - - def handle_entityref(self,ref): - self.out.write("&%s;" % ref) - - def handle_charref(self,ref): - self.out.write("&#%s;" % ref) - - def unknown_endtag(self,tag): - self.out.write("</%s>" % tag) - - def handle_special(self,data): - self.out.write("<!%s>" % data) - - def write(self,buf): - "Act like a writer. Why doesn't SGMLParser do this by default?" - self.feed(buf) - - # Python-2.1 sgmllib rejects illegal declarations. Since various Microsoft - # products accept and output them, we need to pass them through - - # at least until we discover that MS will execute them. - # sgmlop-1.1 will not use this method, but calls handle_special to - # do what we want. - def parse_declaration(self, i): - rawdata = self.rawdata - n = len(rawdata) - j = i + 2 - while j < n: - c = rawdata[j] - if c == ">": - # end of declaration syntax - self.handle_special(rawdata[i+2:j]) - return j + 1 - if c in "\"'": - m = declstringlit.match(rawdata, j) - if not m: - # incomplete or an error? - return -1 - j = m.end() - elif c in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ": - m = declname.match(rawdata, j) - if not m: - # incomplete or an error? - return -1 - j = m.end() - else: - j += 1 - # end of buffer between tokens - return -1 - -class HTMLScriptFilter(SGMLFilter): - "Remove scripts from an HTML document." - def __init__(self,out): - SGMLFilter.__init__(self,out) - self.ignoring = 0 - self.modified = False - self.msg = "<!-- WARNING: embedded script removed -->" - def start_script(self,unused): - self.ignoring += 1 - self.modified = True - self.out.write(self.msg) - def end_script(self): - self.ignoring -= 1 - def handle_data(self,data): - if not self.ignoring: SGMLFilter.handle_data(self,data) - def handle_comment(self,comment): - if not self.ignoring: SGMLFilter.handle_comment(self,comment) - -def check_html(msg,savname=None): - "Remove scripts from HTML attachments." - msgtype = msg.get_content_type().lower() - # check for more MSIE braindamage - if msgtype == 'application/octet-stream': - for (attr,name) in msg.getnames(): - if name and name.lower().endswith(".htm"): - msgtype = 'text/html' - if msgtype == 'text/html': - out = StringIO.StringIO() - htmlfilter = HTMLScriptFilter(out) - try: - htmlfilter.write(msg.get_payload(decode=True)) - htmlfilter.close() - #except sgmllib.SGMLParseError: - except: - #mimetools.copyliteral(msg.get_payload(),open('debug.out','w') - htmlfilter.close() - hostname = socket.gethostname() - msg.set_payload( - "An HTML attachment could not be parsed. The original is saved as '%s:%s'" - % (hostname,savname)) - del msg["content-type"] - del msg["content-disposition"] - del msg["content-transfer-encoding"] - name = "WARNING.TXT" - msg["Content-Type"] = "text/plain; name="+name - return Milter.CONTINUE - if htmlfilter.modified: - msg.set_payload(out) # remove embedded scripts - del msg["content-transfer-encoding"] - email.Encoders.encode_quopri(msg) - return Milter.CONTINUE - -if __name__ == '__main__': - import sys - def _list_attach(msg): - t = msg.get_content_type() - p = msg.get_payload(decode=True) - print msg.get_filename(),msg.get_content_type(),type(p) - msg = msg.get_submsg() - if isinstance(msg,Message): - return check_attachments(msg,_list_attach) - return Milter.CONTINUE - - for fname in sys.argv[1:]: - fp = open(fname) - msg = message_from_file(fp) - email.Iterators._structure(msg) - check_attachments(msg,_list_attach) diff --git a/rejects.py b/rejects.py deleted file mode 100644 index 1460ceb..0000000 --- a/rejects.py +++ /dev/null @@ -1,38 +0,0 @@ -# Analyze milter log to find abusers - -fp = open('/var/log/milter/milter.log','r') -subdict = {} -ipdict = {} -spamcnt = {} -for line in fp: - a = line.split(None,4) - if len(a) < 4: continue - dt,tm,id,op = a[:4] - if op == 'Subject:': - if len(a) > 4: subdict[id] = a[4].rstrip() - elif op == 'connect': - ipdict[id] = a[4].rstrip() - elif op in ('eom','dspam'): - if id in subdict: del subdict[id] - if id in ipdict: del ipdict[id] - elif op in ('REJECT:','DSPAM:','SPAM:','abort'): - if id in subdict: - if id in ipdict: - ip = ipdict[id] - del ipdict[id] - f,host,raw = ip.split(None,2) - if host in spamcnt: - spamcnt[host] += 1 - else: - spamcnt[host] = 1 - else: ip = '' - print dt,tm,op,a[4].rstrip(),subdict[id] - del subdict[id] - else: - print line.rstrip() -print len(subdict),'leftover entries' - -spamlist = filter(lambda x: x[1] > 1,spamcnt.items()) -spamlist.sort(lambda x,y: x[1] - y[1]) -for ip,cnt in spamlist: - print cnt,ip diff --git a/rhsbl.m4 b/rhsbl.m4 deleted file mode 100644 index 8f886c7..0000000 --- a/rhsbl.m4 +++ /dev/null @@ -1,44 +0,0 @@ -divert(-1) -# -# Copyright (c) 2002 Derek J. Balling -# All rights reserved. -# -# Permission to use granted for all purposes. If modifications are made -# they are requested to be sent to <dredd@megacity.org> for inclusion in future -# versions -# -# Allows (hopefully) for checking of access.db whitelisting now. This ONLY -# works on sendmail-8.12.x ... use on any other version may require tinkering -# by you the downloader. -# -# Incorporates many changes by Sergey S. Mokryshev <mokr@mokr.net> -# -# - -divert(0) -ifdef(`_RHSBL_R_',`dnl',`dnl -VERSIONID(`$Id$') -define(`_RHSBL_R_',`') -ifdef(`_DNSBL_R_',`dnl',`dnl -LOCAL_CONFIG -# map for DNS based blacklist lookups based on the sender RHS -Kdnsbl host -T<TMP>')') -divert(-1) -define(`_RHSBL_SRV_', `_ARG_')dnl -define(`_RHSBL_MSG_', `ifelse(len(X`'_ARG2_),`1',`"550 Mail from " $`'&{RHS} " refused by blackhole site '_RHSBL_SRV_`"',`_ARG2_')')dnl -define(`_RHSBL_MSG_TMP_', `ifelse(_ARG3_,`t',`"451 Temporary lookup failure of " $`'&{RHS} " at '_RHSBL_SRV_`"',`_ARG3_')')dnl - -MAILER_DEFINITIONS - -SLocal_check_mail -# DNS based RHS spam list blackholes.bmsi.com -R$* $: <?> $>CanonAddr $1 -R<?> $*<@$+.> $: <?> $1<@$2.> $| $>SearchList <+ rhs> $| <F:$1@$2> <D:$2> <> -R<?> $* $| <$={Accept}> $: OKSOFAR -R<?> $*<@$+.> $| $* $: <?> $(dnsbl $2._RHSBL_SRV_. $: OK $) $(macro {RHS} $@ $2 $) -R<?> OK $: OKSOFAR -R<?> $*<@$*> $: OKSOFAR -ifelse(len(X`'_ARG3_),`1', -`R<?>$+<TMP> $: TMPOK', -`R<?>$+<TMP> $#error $@ 4.7.1 $: _RHSBL_MSG_TMP_') -R<?>$+ $#error $@ 5.7.1 $: _RHSBL_MSG_ diff --git a/sample.py b/sample.py deleted file mode 100644 index 5fc8df2..0000000 --- a/sample.py +++ /dev/null @@ -1,181 +0,0 @@ - -# A simple milter. - -# Author: Stuart D. Gathman <stuart@bmsi.com> -# Copyright 2001 Business Management Systems, Inc. -# This code is under GPL. See COPYING for details. - -import sys -import os -import StringIO -import rfc822 -import mime -import Milter -import tempfile -from time import strftime -#import syslog - -#syslog.openlog('milter') - -class sampleMilter(Milter.Milter): - "Milter to replace attachments poisonous to Windows with a WARNING message." - - def log(self,*msg): - print "%s [%d]" % (strftime('%Y%b%d %H:%M:%S'),self.id), - for i in msg: print i, - print - - def __init__(self): - self.tempname = None - self.mailfrom = None - self.fp = None - self.bodysize = 0 - self.id = Milter.uniqueID() - - # multiple messages can be received on a single connection - # envfrom (MAIL FROM in the SMTP protocol) seems to mark the start - # of each message. - def envfrom(self,f,*str): - self.log("mail from",f,str) - self.fp = StringIO.StringIO() - self.tempname = None - self.mailfrom = f - self.bodysize = 0 - return Milter.CONTINUE - - def envrcpt(self,to,*str): - # mail to MAILER-DAEMON is generally spam that bounced - if to.startswith('<MAILER-DAEMON@'): - self.log('DISCARD: RCPT TO:',to,str) - return Milter.DISCARD - self.log("rcpt to",to,str) - return Milter.CONTINUE - - def header(self,name,val): - lname = name.lower() - if lname == 'subject': - - # even if we wanted the Taiwanese spam, we can't read Chinese - # (delete if you read chinese mail) - if val.startswith('=?big5') or val.startswith('=?ISO-2022-JP'): - self.log('REJECT: %s: %s' % (name,val)) - #self.setreply('550','','Go away spammer') - return Milter.REJECT - - # check for common spam keywords - if val.find("$$$") >= 0 or val.find("XXX") >= 0 \ - or val.find("!!!") >= 0 or val.find("FREE") >= 0: - self.log('REJECT: %s: %s' % (name,val)) - #self.setreply('550','','Go away spammer') - return Milter.REJECT - - # check for spam that pretends to be legal - lval = val.lower() - if lval.startswith("adv:") or lval.startswith("adv.") \ - or lval.find('viagra') >= 0: - self.log('REJECT: %s: %s' % (name,val)) - return Milter.REJECT - - # check for invalid message id - if lname == 'message-id' and len(val) < 4: - self.log('REJECT: %s: %s' % (name,val)) - #self.setreply('550','','Go away spammer') - return Milter.REJECT - - # check for common bulk mailers - if lname == 'x-mailer' and \ - val.lower() in ('direct email','calypso','mail bomber'): - self.log('REJECT: %s: %s' % (name,val)) - #self.setreply('550','','Go away spammer') - return Milter.REJECT - - # log selected headers - if lname in ('subject','x-mailer'): - self.log('%s: %s' % (name,val)) - if self.fp: - self.fp.write("%s: %s\n" % (name,val)) # add header to buffer - return Milter.CONTINUE - - def eoh(self): - if not self.fp: return Milter.TEMPFAIL # not seen by envfrom - self.fp.write("\n") - self.fp.seek(0) - # copy headers to a temp file for scanning the body - headers = self.fp.getvalue() - self.fp.close() - self.tempname = fname = tempfile.mktemp(".defang") - self.fp = open(fname,"w+b") - self.fp.write(headers) # IOError (e.g. disk full) causes TEMPFAIL - return Milter.CONTINUE - - def body(self,chunk): # copy body to temp file - if self.fp: - self.fp.write(chunk) # IOError causes TEMPFAIL in milter - self.bodysize += len(chunk) - return Milter.CONTINUE - - def _headerChange(self,msg,name,value): - if value: # add header - self.addheader(name,value) - else: # delete all headers with name - h = msg.getheaders(name) - cnt = len(h) - for i in range(cnt,0,-1): - self.chgheader(name,i-1,'') - - def eom(self): - if not self.fp: return Milter.ACCEPT - self.fp.seek(0) - msg = mime.message_from_file(self.fp) - msg.headerchange = self._headerChange - if not mime.defang(msg,self.tempname): - os.remove(self.tempname) - self.tempname = None # prevent re-removal - self.log("eom") - return Milter.ACCEPT # no suspicious attachments - self.log("Temp file:",self.tempname) - self.tempname = None # prevent removal of original message copy - # copy defanged message to a temp file - out = tempfile.TemporaryFile() - try: - msg.dump(out) - out.seek(0) - msg = rfc822.Message(out) - msg.rewindbody() - while 1: - buf = out.read(8192) - if len(buf) == 0: break - self.replacebody(buf) # feed modified message to sendmail - return Milter.ACCEPT # ACCEPT modified message - finally: - out.close() - return Milter.TEMPFAIL - - def close(self): - sys.stdout.flush() # make log messages visible - if self.tempname: - os.remove(self.tempname) # remove in case session aborted - if self.fp: - self.fp.close() - return Milter.CONTINUE - - def abort(self): - self.log("abort after %d body chars" % self.bodysize) - return Milter.CONTINUE - -if __name__ == "__main__": - #tempfile.tempdir = "/var/log/milter" - #socketname = "/var/log/milter/pythonsock" - socketname = os.getenv("HOME") + "/pythonsock" - Milter.factory = sampleMilter - Milter.set_flags(Milter.CHGBODY + Milter.CHGHDRS + Milter.ADDHDRS) - print """To use this with sendmail, add the following to sendmail.cf: - -O InputMailFilters=pythonfilter -Xpythonfilter, S=local:%s - -See the sendmail README for libmilter. -sample milter startup""" % socketname - sys.stdout.flush() - Milter.runmilter("pythonfilter",socketname,240) - print "sample milter shutdown" diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 7503af5..0000000 --- a/setup.cfg +++ /dev/null @@ -1,5 +0,0 @@ -[bdist_rpm] -python=python2 -doc_files=README NEWS TODO -packager=Stuart D. Gathman <stuart@bmsi.com> -release=2.4 diff --git a/setup.py b/setup.py deleted file mode 100644 index 2d80f2f..0000000 --- a/setup.py +++ /dev/null @@ -1,49 +0,0 @@ -import os -import sys -from distutils.core import setup, Extension - -# FIXME: on some versions of sendmail, smutil is renamed to sm -libs = ["milter", "smutil"] - -# patch distutils if it can't cope with the "classifiers" or -# "download_url" keywords -if sys.version < '2.2.3': - from distutils.dist import DistributionMetadata - DistributionMetadata.classifiers = None - DistributionMetadata.download_url = None - -setup(name = "milter", version = "0.8.0", - description="Python interface to sendmail milter API", - long_description="""\ -This is a python extension module to enable python scripts to -attach to sendmail's libmilter functionality. Additional python -modules provide for navigating and modifying MIME parts, and -querying SPF records. -""", - author="Jim Niemira", - author_email="urmane@urmane.org", - maintainer="Stuart D. Gathman", - maintainer_email="stuart@bmsi.com", - license="GPL", - url="http://www.bmsi.com/python/milter.html", - py_modules=["mime","spf"], - packages = ['Milter'], - ext_modules=[ - Extension("milter", ["miltermodule.c"], - libraries=libs, - define_macros = [ ('MAX_ML_REPLY',32) ] - ), - ], - keywords = ['sendmail','milter'], - classifiers = [ - 'Development Status :: 5 - Production/Stable', - 'Environment :: No Input/Output (Daemon)', - 'Intended Audience :: System Administrators', - 'License :: OSI Approved :: GNU General Public License (GPL)', - 'Natural Language :: English', - 'Operating System :: POSIX', - 'Programming Language :: Python', - 'Topic :: Communications :: Email :: Mail Transport Agents', - 'Topic :: Communications :: Email :: Filters' - ] -) diff --git a/softfail.txt b/softfail.txt deleted file mode 100644 index 263cecf..0000000 --- a/softfail.txt +++ /dev/null @@ -1,23 +0,0 @@ -Subject: SPF softfail (POSSIBLE FORGERY) - -This is an automatically generated Delivery Status Notification. - -THIS IS A WARNING MESSAGE ONLY. - -YOU DO *NOT* NEED TO RESEND YOUR MESSAGE. - -Delivery to the following recipients has been delayed. - - %(rcpt)s - -Subject: %(subject)s -Received-SPF: %(spf_result)s - -Your sender policy indicated that the above email was likely forged and that -feedback was desired. - -If you need further assistance, please do not hesitate to contact me. - -Kind regards, - -postmaster@%(receiver)s diff --git a/spf.py b/spf.py deleted file mode 100755 index a7bb4c9..0000000 --- a/spf.py +++ /dev/null @@ -1,1039 +0,0 @@ -#!/usr/bin/env python -"""SPF (Sender-Permitted From) implementation. - -Copyright (c) 2003, Terence Way -Portions Copyright (c) 2004,2005 Stuart Gathman <stuart@bmsi.com> -This module is free software, and you may redistribute it and/or modify -it under the same terms as Python itself, so long as this copyright message -and disclaimer are retained in their original form. - -IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, -SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF -THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGE. - -THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, -AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, -SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. - -For more information about SPF, a tool against email forgery, see - http://spf.pobox.com - -For news, bugfixes, etc. visit the home page for this implementation at - http://www.wayforward.net/spf/ -""" - -# Changes: -# 9-dec-2003, v1.1, Meng Weng Wong added PTR code, THANK YOU -# 11-dec-2003, v1.2, ttw added macro expansion, exp=, and redirect= -# 13-dec-2003, v1.3, ttw added %{o} original domain macro, -# print spf result on command line, support default=, -# support localhost, follow DNS CNAMEs, cache DNS results -# during query, support Python 2.2 for Mac OS X -# 16-dec-2003, v1.4, ttw fixed include handling (include is a mechanism, -# complete with status results, so -include: should work. -# Expand macros AFTER looking for status characters ?-+ -# so altavista.com SPF records work. -# 17-dec-2003, v1.5, ttw use socket.inet_aton() instead of DNS.addr2bin, so -# n, n.n, and n.n.n forms for IPv4 addresses work, and to -# ditch the annoying Python 2.4 FutureWarning -# 18-dec-2003, v1.6, Failures on Intel hardware: endianness. Use ! on -# struct.pack(), struct.unpack(). -# -# Development taken over by Stuart Gathman <stuart@bmsi.com> since -# Terrence is not responding to email. -# -# $Log$ -# Revision 1.4 2005/06/02 04:18:55 customdesigned -# Update copyright notices after reading article on /. -# -# Revision 1.3 2005/06/02 02:08:12 customdesigned -# Reject on PermErr -# -# Revision 1.2 2005/05/31 18:57:59 customdesigned -# Clear unknown mechanism list at proper time. -# -# Revision 1.24 2005/03/16 21:58:39 stuart -# Change Milter module to package. -# -# Revision 1.22 2005/02/09 17:52:59 stuart -# Report DNS errors as PermError rather than unknown. -# -# Revision 1.21 2004/11/20 16:37:03 stuart -# Handle multi-segment TXT records. -# -# Revision 1.20 2004/11/19 06:10:30 stuart -# Use PermError exception instead of reporting unknown. -# -# Revision 1.19 2004/11/09 23:00:18 stuart -# Limit recursion and DNS lookups separately. -# -# -# Revision 1.17 2004/09/10 18:08:26 stuart -# Return unknown for null mechanism -# -# Revision 1.16 2004/09/04 23:27:06 stuart -# More mechanism aliases. -# -# Revision 1.15 2004/08/30 21:19:05 stuart -# Return unknown for invalid ip syntax in mechanism -# -# Revision 1.14 2004/08/23 02:28:24 stuart -# Remove Perl usage message. -# -# Revision 1.13 2004/07/23 19:23:12 stuart -# Always fail to match on ip6, until we support it properly. -# -# Revision 1.12 2004/07/23 18:48:15 stuart -# Fold CID parsing into spf -# -# Revision 1.11 2004/07/21 21:32:01 stuart -# Handle CID records (Microsoft XML format). -# -# Revision 1.10 2004/04/19 22:12:11 stuart -# Release 0.6.9 -# -# Revision 1.9 2004/04/18 03:29:35 stuart -# Pass most tests except -local and -rcpt-to -# -# Revision 1.8 2004/04/17 22:17:55 stuart -# Header comment method. -# -# Revision 1.7 2004/04/17 18:22:48 stuart -# Support default explanation. -# -# Revision 1.6 2004/04/06 20:18:02 stuart -# Fix bug in include -# -# Revision 1.5 2004/04/05 22:29:46 stuart -# SPF best_guess, -# -# Revision 1.4 2004/03/25 03:27:34 stuart -# Support delegation of SPF records. -# -# Revision 1.3 2004/03/13 12:23:23 stuart -# Expanded result codes. Tolerate common method misspellings. -# - -__author__ = "Terence Way" -__email__ = "terry@wayforward.net" -__version__ = "1.6: December 18, 2003" -MODULE = 'spf' - -USAGE = """To check an incoming mail request: - % python spf.py {ip} {sender} {helo} - % python spf.py 69.55.226.139 tway@optsw.com mx1.wayforward.net - -To test an SPF record: - % python spf.py "v=spf1..." {ip} {sender} {helo} - % python spf.py "v=spf1 +mx +ip4:10.0.0.1 -all" 10.0.0.1 tway@foo.com a - -To fetch an SPF record: - % python spf.py {domain} - % python spf.py wayforward.net - -To test this script (and to output this usage message): - % python spf.py -""" - -import re -import socket # for inet_ntoa() and inet_aton() -import struct # for pack() and unpack() -import time # for time() - -import DNS # http://pydns.sourceforge.net -import xml.sax - -# ------------------------------------------------------------------------- -# Convert a MS Caller-ID entry (XML) to a SPF entry -# -# (c) 2004 by Ernesto Baschny -# (c) 2004 Python version by Stuart Gathman -# -# Date: 2004-02-25 -# -# A complete reverse translation (SPF -> CID) might be impossible, since -# there are no ways to handle: -# - PTR and EXISTS mechanism -# - MX mechanism with an different domain as argument -# - macros -# -# References: -# http://www.microsoft.com/mscorp/twc/privacy/spam_callerid.mspx -# http://spf.pobox.com/ -# -# Known bugs: -# - Currently it won't handle the exclusions provided in the A and R -# tags (prefix '!'). They will show up "as-is" in the SPF record -# - I really haven't read the MS-CID specs in-depth, so there are probably -# other bugs too :) -# -# Ernesto Baschny <ernst@baschny.de> -# - -class CIDParser(xml.sax.ContentHandler): - "Convert a MS Caller-ID entry (XML) to a SPF entry." - - def __init__(self,q=None): - self.spf = [] - self.action = '-all' - self.has_servers = None - self.spf_entry = None - if q: - self.spf_query = q - else: - self.spf_query = query(i='127.0.0.1', s='localhost', h='unknown') - - def startElement(self,tag,attr): - if tag == 'm': - if self.has_servers != None and not self.has_servers: - raise ValueError( - "Declared <noMailServers\> and later <m>, this CID entry is not valid." - ) - self.has_servers = True - elif tag == 'noMailServers': - if self.has_servers: - raise ValueError( - "Declared <m> and later <noMailServers\>, this CID entry is not valid." - ) - self.has_servers = False - elif tag == 'ep': - if attr.has_key('testing') and attr.getValue('testing') == 'true': - # A CID with 'testing' found: - # From the MS-specs: - # "Documents in which such attribute is present with a true - # value SHOULD be entirely ignored (one should act as if the - # document were absent)" - # From the SPF-specs: - # "Neutral (?): The SPF client MUST proceed as if a domain did - # not publish SPF data." - # So we set SPF action to "neutral": - self.action = '?all' - elif tag == 'mx': - # The empty MX-tag, same as SPF's MX-mechanism - self.spf.append('mx') - self.tag = tag - - def characters(self,text): - tag = self.tag - # Remove starting and trailing spaces from text: - text = text.strip() - - if tag == 'a' or tag == 'r': - # The A and R tags from MS-CID are both handled by the - # ipv4/6-mechanisms from SPF: - if text.find(':') < 0: - mechanism = 'ip4' - else: - mechanism = 'ip6' - self.spf.append(mechanism + ':' + text) - elif tag == 'indirect': - # MS-CID's indirect is "sort of" the include from SPF: - # Not really true, because the <indirect> tag from MS-CID also - # provides a fallback in case the included domain doesn't provide - # _ep-records: The inbound MX-servers of the included domains - # are added to the list of allowed outgoing mailservers for the - # domain that declared the _ep-record with the <indirect> tag. - # In SPF you would use the 'mx:domain' to handle this, but this - # wouldn't depend on referred domain having or not SPF-records. - cid_xml = self.cid_txt(text) - if cid_xml: - p = CIDParser() - xml.sax.parseString(cid_xml,p) - if p.has_servers != False: - self.spf += p.spf - else: - self.spf.append('mx:' + text) - - def cid_txt(self,domain): - q = self.spf_query - domain='_ep.' + domain - a = q.dns_txt(domain) - if not a: return None - if a[0].lower().startswith('<ep ') and a[-1].lower().endswith('</ep>'): - return ''.join(a) - return None - - def endElement(self,tag): - if tag == 'ep': - # This is the end... assemble what we've got - spf_entry = ['v=spf1'] - if self.has_servers != False: - spf_entry += self.spf - spf_entry.append(self.action) - self.spf_entry = ' '.join(spf_entry) - - def spf_txt(self,cid_xml): - if not cid_xml.startswith('<'): - cid_xml = self.cid_txt(cid_xml) - if not cid_xml: return None - # Parse the beast. Any XML-problem will be reported by xlm.sax - self.spf_entry = None - xml.sax.parseString(cid_xml,self) - return self.spf_entry - -# 32-bit IPv4 address mask -MASK = 0xFFFFFFFFL - -# Regular expression to look for modifiers -RE_MODIFIER = re.compile(r'^([a-zA-Z]+)=') - -# Regular expression to find macro expansions -RE_CHAR = re.compile(r'%(%|_|-|(\{[a-zA-Z][0-9]*r?[^\}]*\}))') - -# Regular expression to break up a macro expansion -RE_ARGS = re.compile(r'([0-9]*)(r?)([^0-9a-zA-Z]*)') - -# Local parts and senders have their delimiters replaced with '.' during -# macro expansion -# -JOINERS = {'l': '.', 's': '.'} - -RESULTS = {'+': 'pass', '-': 'fail', '?': 'neutral', '~': 'softfail', - 'pass': 'pass', 'fail': 'fail', 'unknown': 'unknown', - 'neutral': 'neutral', 'softfail': 'softfail', - 'none': 'none', 'deny': 'fail' } - -EXPLANATIONS = {'pass': 'sender SPF verified', 'fail': 'access denied', - 'unknown': 'SPF unknown', - 'softfail': 'domain in transition', - 'neutral': 'access neither permitted nor denied', - 'none': '' - } - -# if set to a domain name, search _spf.domain namespace if no SPF record -# found in source domain. - -DELEGATE = None - -# support pre 2.2.1.... -try: - bool, True, False = bool, True, False -except NameError: - False, True = 0, 1 - def bool(x): return not not x -# ...pre 2.2.1 - -# standard default SPF record -DEFAULT_SPF = 'v=spf1 a/24 mx/24 ptr' - -# maximum DNS lookups allowed -MAX_LOOKUP = 100 -MAX_RECURSION = 20 - -class TempError(Exception): - "Temporary SPF error" - -class PermError(Exception): - "Permanent SPF error" - def __init__(self,msg,mech=None): - Exception.__init__(self,msg,mech) - self.msg = msg - self.mech = mech - def __str__(self): - if self.mech: - return '%s: %s'%(self.msg,self.mech) - return self.msg - -def check(i, s, h,local=None,receiver=None): - """Test an incoming MAIL FROM:<s>, from a client with ip address i. - h is the HELO/EHLO domain name. - - Returns (result, mta-status-code, explanation) where result in - ['pass', 'unknown', 'fail', 'error', 'softfail', 'none', 'neutral' ]. - - Example: - >>> check(i='127.0.0.1', s='terry@wayforward.net', h='localhost') - ('pass', 250, 'local connections always pass') - - #>>> check(i='61.51.192.42', s='liukebing@bcc.com', h='bmsi.com') - - """ - return query(i=i, s=s, h=h,local=local,receiver=receiver).check() - -class query(object): - """A query object keeps the relevant information about a single SPF - query: - - i: ip address of SMTP client - s: sender declared in MAIL FROM:<> - l: local part of sender s - d: current domain, initially domain part of sender s - h: EHLO/HELO domain - v: 'in-addr' for IPv4 clients and 'ip6' for IPv6 clients - t: current timestamp - p: SMTP client domain name - o: domain part of sender s - - This is also, by design, the same variables used in SPF macro - expansion. - - Also keeps cache: DNS cache. - """ - def __init__(self, i, s, h,local=None,receiver=None): - self.i, self.s, self.h = i, s, h - if not s and h: - self.s = 'postmaster@' + h - self.l, self.o = split_email(s, h) - self.t = str(int(time.time())) - self.v = 'in-addr' - self.d = self.o - self.p = None - if receiver: - self.r = receiver - self.cache = {} - self.exps = dict(EXPLANATIONS) - self.local = local # local policy - self.lookups = 0 - - def set_default_explanation(self,exp): - exps = self.exps - for i in 'softfail','fail','unknown': - exps[i] = exp - - def getp(self): - if not self.p: - p = self.dns_ptr(self.i) - if len(p) > 0: - self.p = p[0] - else: - self.p = self.i - return self.p - - def best_guess(self,spf=DEFAULT_SPF): - """Return a best guess based on a default SPF record""" - return self.check(spf) - - def check(self, spf=None): - """ - Returns (result, mta-status-code, explanation) where - result in ['fail', 'softfail', 'neutral' 'unknown', 'pass', 'error'] - """ - self.mech = [] # unknown mechanisms - if self.i.startswith('127.'): - return ('pass', 250, 'local connections always pass') - - try: - self.lookups = 0 - if not spf: - spf = self.dns_spf(self.d) - if self.local and spf: - spf += ' ' + self.local - return self.check1(spf, self.d, 0) - except DNS.DNSError,x: - return ('error', 450, 'SPF DNS Error: ' + str(x)) - except TempError,x: - return ('error', 450, 'SPF Temporary Error: ' + str(x)) - except PermError,x: - self.prob = x.msg - self.mech.append(x.mech) - # Pre-Lentczner draft treats this as an unknown result - # and equivalent to no SPF record. - # return ('unknown', 550, 'SPF Permanent Error: ' + str(x)) - return ('error', 550, 'SPF Permanent Error: ' + str(x)) - - def check1(self, spf, domain, recursion): - # spf rfc: 3.7 Processing Limits - # - if recursion > MAX_RECURSION: - self.prob = 'Too many levels of recursion' - return ('unknown', 250, 'SPF recursion limit exceeded') - try: - tmp, self.d = self.d, domain - return self.check0(spf,recursion) - finally: - self.d = tmp - - def check0(self, spf,recursion): - """Test this query information against SPF text. - - Returns (result, mta-status-code, explanation) where - result in ['fail', 'unknown', 'pass', 'none'] - """ - - if not spf: - return ('none', 250, EXPLANATIONS['none']) - - # split string by whitespace, drop the 'v=spf1' - # - spf = spf.split()[1:] - - # copy of explanations to be modified by exp= - exps = self.exps - redirect = None - - # no mechanisms at all cause unknown result, unless - # overridden with 'default=' modifier - # - default = 'neutral' - - # Look for modifiers - # - for m in spf: - m = RE_MODIFIER.split(m)[1:] - if len(m) != 2: continue - - if m[0] == 'exp': - exps['fail'] = exps['unknown'] = \ - self.get_explanation(m[1]) - elif m[0] == 'redirect': - redirect = self.expand(m[1]) - elif m[0] == 'default': - # default=- is the same as default=fail - default = RESULTS.get(m[1], default) - - # spf rfc: 3.6 Unrecognized Mechanisms and Modifiers - - # Look for mechanisms - # - for mech in spf: - if RE_MODIFIER.match(mech): continue - m, arg, cidrlength = parse_mechanism(mech, self.d) - - # map '?' '+' or '-' to 'unknown' 'pass' or 'fail' - if m: - result = RESULTS.get(m[0]) - if result: - # eat '?' '+' or '-' - m = m[1:] - else: - # default pass - result = 'pass' - - if m in ['a', 'mx', 'ptr', 'prt', 'exists', 'include']: - arg = self.expand(arg) - - if m == 'include': - if arg != self.d: - res,code,txt = self.check1(self.dns_spf(arg), - arg, recursion + 1) - if res == 'pass': - break - if res == 'none': - raise PermError( - 'No valid SPF record for included domain: %s'%arg, - mech) - continue - else: - raise PermError('include mechanism missing domain',mech) - elif m == 'all': - break - - elif m == 'exists': - if len(self.dns_a(arg)) > 0: - break - - elif m == 'a': - if cidrmatch(self.i, self.dns_a(arg), - cidrlength): - break - - elif m == 'mx': - if cidrmatch(self.i, self.dns_mx(arg), - cidrlength): - break - - elif m in ('ip4', 'ipv4', 'ip') and arg != self.d: - try: - if cidrmatch(self.i, [arg], cidrlength): - break - except socket.error: - raise PermError('syntax error',mech) - - elif m in ('ip6', 'ipv6'): - # Until we support IPV6, we should never - # get an IPv6 connection. So this mech - # will never match. - pass - - elif m in ('ptr', 'prt'): - if domainmatch(self.validated_ptrs(self.i), - arg): - break - - else: - # unknown mechanisms cause immediate unknown - # abort results - raise PermError('Unknown mechanism found',mech) - else: - # no matches - if redirect: - return self.check1(self.dns_spf(redirect), - redirect, recursion + 1) - else: - result = default - - if result == 'fail': - return (result, 550, exps[result]) - else: - return (result, 250, exps[result]) - - def get_explanation(self, spec): - """Expand an explanation.""" - if spec: - return self.expand(''.join(self.dns_txt(self.expand(spec)))) - else: - return 'explanation : Required option is missing' - - def expand(self, str): - """Do SPF RFC macro expansion. - - Examples: - >>> q = query(s='strong-bad@email.example.com', - ... h='mx.example.org', i='192.0.2.3') - >>> q.p = 'mx.example.org' - - >>> q.expand('%{d}') - 'email.example.com' - - >>> q.expand('%{d4}') - 'email.example.com' - - >>> q.expand('%{d3}') - 'email.example.com' - - >>> q.expand('%{d2}') - 'example.com' - - >>> q.expand('%{d1}') - 'com' - - >>> q.expand('%{p}') - 'mx.example.org' - - >>> q.expand('%{p2}') - 'example.org' - - >>> q.expand('%{dr}') - 'com.example.email' - - >>> q.expand('%{d2r}') - 'example.email' - - >>> q.expand('%{l}') - 'strong-bad' - - >>> q.expand('%{l-}') - 'strong.bad' - - >>> q.expand('%{lr}') - 'strong-bad' - - >>> q.expand('%{lr-}') - 'bad.strong' - - >>> q.expand('%{l1r-}') - 'strong' - - >>> q.expand('%{ir}.%{v}._spf.%{d2}') - '3.2.0.192.in-addr._spf.example.com' - - >>> q.expand('%{lr-}.lp._spf.%{d2}') - 'bad.strong.lp._spf.example.com' - - >>> q.expand('%{lr-}.lp.%{ir}.%{v}._spf.%{d2}') - 'bad.strong.lp.3.2.0.192.in-addr._spf.example.com' - - >>> q.expand('%{ir}.%{v}.%{l1r-}.lp._spf.%{d2}') - '3.2.0.192.in-addr.strong.lp._spf.example.com' - - >>> q.expand('%{p2}.trusted-domains.example.net') - 'example.org.trusted-domains.example.net' - - >>> q.expand('%{p2}.trusted-domains.example.net') - 'example.org.trusted-domains.example.net' - - """ - end = 0 - result = '' - for i in RE_CHAR.finditer(str): - result += str[end:i.start()] - macro = str[i.start():i.end()] - if macro == '%%': - result += '%' - elif macro == '%_': - result += ' ' - elif macro == '%-': - result += '%20' - else: - letter = macro[2].lower() - if letter == 'p': - self.getp() - expansion = getattr(self, letter, '') - if expansion: - result += expand_one(expansion, - macro[3:-1], - JOINERS.get(letter)) - - end = i.end() - return result + str[end:] - - def dns_spf(self, domain): - """Get the SPF record recorded in DNS for a specific domain - name. Returns None if not found, or if more than one record - is found. - """ - a = [t for t in self.dns_txt(domain) if t.startswith('v=spf1')] - if not a: - if DELEGATE: - a = [t - for t in self.dns_txt(domain+'._spf.'+DELEGATE) - if t.startswith('v=spf1') - ] - if not a: - # No SPF record: convert and return CID if present - p = CIDParser(q=self) - try: - return p.spf_txt(domain) - except xml.sax._exceptions.SAXParseException: - raise PermError("Caller-ID parse error",domain) - - if len(a) == 1: - return a[0] - return None - - def dns_txt(self, domainname): - "Get a list of TXT records for a domain name." - if domainname: - return [''.join(a) for a in self.dns(domainname, 'TXT')] - return [] - - def dns_mx(self, domainname): - """Get a list of IP addresses for all MX exchanges for a - domain name. - """ - return [a for mx in self.dns(domainname, 'MX') \ - for a in self.dns_a(mx[1])] - - def dns_a(self, domainname): - """Get a list of IP addresses for a domainname.""" - return self.dns(domainname, 'A') - - def dns_aaaa(self, domainname): - """Get a list of IPv6 addresses for a domainname.""" - return self.dns(domainname, 'AAAA') - - def validated_ptrs(self, i): - """Figure out the validated PTR domain names for a given IP - address. - """ - return [p for p in self.dns_ptr(i) if i in self.dns_a(p)] - - def dns_ptr(self, i): - """Get a list of domain names for an IP address.""" - return self.dns(reverse_dots(i) + ".in-addr.arpa", 'PTR') - - def dns(self, name, qtype): - """DNS query. - - If the result is in cache, return that. Otherwise pull the - result from DNS, and cache ALL answers, so additional info - is available for further queries later. - - CNAMEs are followed. - - If there is no data, [] is returned. - - pre: qtype in ['A', 'AAAA', 'MX', 'PTR', 'TXT', 'SPF'] - post: isinstance(__return__, types.ListType) - """ - self.lookups += 1 - if self.lookups > MAX_LOOKUP: - raise PermError('Too many DNS lookups') - result = self.cache.get( (name, qtype) ) - cname = None - if not result: - req = DNS.DnsRequest(name, qtype=qtype) - resp = req.req() - for a in resp.answers: - # key k: ('wayforward.net', 'A'), value v - k, v = (a['name'], a['typename']), a['data'] - if k == (name, 'CNAME'): - cname = v - self.cache.setdefault(k, []).append(v) - result = self.cache.get( (name, qtype), []) - if not result and cname: - result = self.dns(cname, qtype) - return result - - def get_header(self,res,receiver): - if res in ('pass','fail','softfail'): - return '%s (%s: %s) client-ip=%s; envelope-from=%s; helo=%s;' % ( - res,receiver,self.get_header_comment(res),self.i, - self.l + '@' + self.o, self.h) - if res == 'unknown': - return '%s (%s: %s)' % (' '.join([res] + self.mech), - receiver,self.get_header_comment(res)) - return '%s (%s: %s)' % (res,receiver,self.get_header_comment(res)) - - def get_header_comment(self,res): - """Return comment for Received-SPF header. - """ - sender = self.o - if res == 'pass': - if self.i.startswith('127.'): - return "localhost is always allowed." - else: return \ - "domain of %s designates %s as permitted sender" \ - % (sender,self.i) - elif res == 'softfail': return \ - "transitioning domain of %s does not designate %s as permitted sender" \ - % (sender,self.i) - elif res == 'neutral': return \ - "%s is neither permitted nor denied by domain of %s" \ - % (self.i,sender) - elif res == 'none': return \ - "%s is neither permitted nor denied by domain of %s" \ - % (self.i,sender) - #"%s does not designate permitted sender hosts" % sender - elif res == 'unknown': return \ - "error in processing during lookup of domain of %s: %s" \ - % (sender, self.prob) - elif res == 'error': return \ - "error in processing during lookup of %s" % sender - elif res == 'fail': return \ - "domain of %s does not designate %s as permitted sender" \ - % (sender,self.i) - raise ValueError("invalid SPF result for header comment: "+res) - -def split_email(s, h): - """Given a sender email s and a HELO domain h, create a valid tuple - (l, d) local-part and domain-part. - - Examples: - >>> split_email('', 'wayforward.net') - ('postmaster', 'wayforward.net') - - >>> split_email('foo.com', 'wayforward.net') - ('postmaster', 'foo.com') - - >>> split_email('terry@wayforward.net', 'optsw.com') - ('terry', 'wayforward.net') - """ - if not s: - return 'postmaster', h - else: - parts = s.split('@', 1) - if len(parts) == 2: - return tuple(parts) - else: - return 'postmaster', s - -def parse_mechanism(str, d): - """Breaks A, MX, IP4, and PTR mechanisms into a (name, domain, - cidr) tuple. The domain portion defaults to d if not present, - the cidr defaults to 32 if not present. - - Examples: - >>> parse_mechanism('a', 'foo.com') - ('a', 'foo.com', 32) - - >>> parse_mechanism('a:bar.com', 'foo.com') - ('a', 'bar.com', 32) - - >>> parse_mechanism('a/24', 'foo.com') - ('a', 'foo.com', 24) - - >>> parse_mechanism('a:bar.com/16', 'foo.com') - ('a', 'bar.com', 16) - """ - a = str.split('/') - if len(a) == 2: - a, port = a[0], int(a[1]) - else: - a, port = str, 32 - - b = a.split(':') - if len(b) == 2: - return b[0], b[1], port - else: - return a, d, port - -def reverse_dots(name): - """Reverse dotted IP addresses or domain names. - - Example: - >>> reverse_dots('192.168.0.145') - '145.0.168.192' - - >>> reverse_dots('email.example.com') - 'com.example.email' - """ - a = name.split('.') - a.reverse() - return '.'.join(a) - -def domainmatch(ptrs, domainsuffix): - """grep for a given domain suffix against a list of validated PTR - domain names. - - Examples: - >>> domainmatch(['FOO.COM'], 'foo.com') - 1 - - >>> domainmatch(['moo.foo.com'], 'FOO.COM') - 1 - - >>> domainmatch(['moo.bar.com'], 'foo.com') - 0 - - """ - domainsuffix = domainsuffix.lower() - for ptr in ptrs: - ptr = ptr.lower() - - if ptr == domainsuffix or ptr.endswith('.' + domainsuffix): - return True - - return False - -def cidrmatch(i, ipaddrs, cidr_length = 32): - """Match an IP address against a list of other IP addresses. - - Examples: - >>> cidrmatch('192.168.0.45', ['192.168.0.44', '192.168.0.45']) - 1 - - >>> cidrmatch('192.168.0.43', ['192.168.0.44', '192.168.0.45']) - 0 - - >>> cidrmatch('192.168.0.43', ['192.168.0.44', '192.168.0.45'], 24) - 1 - """ - c = cidr(i, cidr_length) - for ip in ipaddrs: - if cidr(ip, cidr_length) == c: - return True - return False - -def cidr(i, n): - """Convert an IP address string with a CIDR mask into a 32-bit - integer. - - i must be a string of numbers 0..255 separated by dots '.':: - pre: forall([0 <= int(p) < 256 for p in i.split('.')]) - - n is a number of bits to mask:: - pre: 0 <= n <= 32 - - Examples: - >>> bin2addr(cidr('192.168.5.45', 32)) - '192.168.5.45' - >>> bin2addr(cidr('192.168.5.45', 24)) - '192.168.5.0' - >>> bin2addr(cidr('192.168.0.45', 8)) - '192.0.0.0' - """ - return ~(MASK >> n) & MASK & addr2bin(i) - -def addr2bin(str): - """Convert a string IPv4 address into an unsigned integer. - - Examples:: - >>> addr2bin('127.0.0.1') - 2130706433L - - >>> addr2bin('127.0.0.1') == socket.INADDR_LOOPBACK - 1 - - >>> addr2bin('255.255.255.254') - 4294967294L - - >>> addr2bin('192.168.0.1') - 3232235521L - - Unlike DNS.addr2bin, the n, n.n, and n.n.n forms for IP addresses - are handled as well:: - >>> addr2bin('10.65536') - 167837696L - >>> 10 * (2 ** 24) + 65536 - 167837696 - - >>> addr2bin('10.93.512') - 173867520L - >>> 10 * (2 ** 24) + 93 * (2 ** 16) + 512 - 173867520 - """ - return struct.unpack("!L", socket.inet_aton(str))[0] - -def bin2addr(addr): - """Convert a numeric IPv4 address into string n.n.n.n form. - - Examples:: - >>> bin2addr(socket.INADDR_LOOPBACK) - '127.0.0.1' - - >>> bin2addr(socket.INADDR_ANY) - '0.0.0.0' - - >>> bin2addr(socket.INADDR_NONE) - '255.255.255.255' - """ - return socket.inet_ntoa(struct.pack("!L", addr)) - -def expand_one(expansion, str, joiner): - if not str: - return expansion - ln, reverse, delimiters = RE_ARGS.split(str)[1:4] - if not delimiters: - delimiters = '.' - expansion = split(expansion, delimiters, joiner) - if reverse: expansion.reverse() - if ln: expansion = expansion[-int(ln)*2+1:] - return ''.join(expansion) - -def split(str, delimiters, joiner=None): - """Split a string into pieces by a set of delimiter characters. The - resulting list is delimited by joiner, or the original delimiter if - joiner is not specified. - - Examples: - >>> split('192.168.0.45', '.') - ['192', '.', '168', '.', '0', '.', '45'] - - >>> split('terry@wayforward.net', '@.') - ['terry', '@', 'wayforward', '.', 'net'] - - >>> split('terry@wayforward.net', '@.', '.') - ['terry', '.', 'wayforward', '.', 'net'] - """ - result, element = [], '' - for c in str: - if c in delimiters: - result.append(element) - element = '' - if joiner: - result.append(joiner) - else: - result.append(c) - else: - element += c - result.append(element) - return result - -def _test(): - import doctest, spf - return doctest.testmod(spf) - -DNS.DiscoverNameServers() # Fails on Mac OS X? Add domain to /etc/resolv.conf - -if __name__ == '__main__': - import sys - if len(sys.argv) == 1: - print USAGE - _test() - elif len(sys.argv) == 2: - q = query(i='127.0.0.1', s='localhost', h='unknown', - receiver=socket.gethostname()) - print q.dns_spf(sys.argv[1]) - elif len(sys.argv) == 4: - print check(i=sys.argv[1], s=sys.argv[2], h=sys.argv[3], - receiver=socket.gethostname()) - elif len(sys.argv) == 5: - i, s, h = sys.argv[2:] - q = query(i=i, s=s, h=h, receiver=socket.gethostname()) - print q.check(sys.argv[1]) - else: - print USAGE diff --git a/spfquery.py b/spfquery.py deleted file mode 100755 index 96f813c..0000000 --- a/spfquery.py +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/python2.3 - -# Author: Stuart D. Gathman <stuart@bmsi.com> -# Copyright 2004 Business Management Systems, Inc. -# This code is under the GNU General Public License. See COPYING for details. - -# $Log$ -# Revision 1.1.1.1 2005/05/31 18:07:19 customdesigned -# Release 0.6.9 -# -# Revision 2.3 2004/04/19 22:12:11 stuart -# Release 0.6.9 -# -# Revision 2.2 2004/04/18 03:29:35 stuart -# Pass most tests except -local and -rcpt-to -# -# Revision 2.1 2004/04/08 18:41:15 stuart -# Reject numeric hello names -# -# Driver for SPF test system - -import spf -import sys - -from optparse import OptionParser - -class PerlOptionParser(OptionParser): - def _process_args (self, largs, rargs, values): - """_process_args(largs : [string], - rargs : [string], - values : Values) - - Process command-line arguments and populate 'values', consuming - options and arguments from 'rargs'. If 'allow_interspersed_args' is - false, stop at the first non-option argument. If true, accumulate any - interspersed non-option arguments in 'largs'. - """ - while rargs: - arg = rargs[0] - # We handle bare "--" explicitly, and bare "-" is handled by the - # standard arg handler since the short arg case ensures that the - # len of the opt string is greater than 1. - if arg == "--": - del rargs[0] - return - elif arg[0:2] == "--": - # process a single long option (possibly with value(s)) - self._process_long_opt(rargs, values) - elif arg[:1] == "-" and len(arg) > 1: - # process a single perl style long option - rargs[0] = '-' + arg - self._process_long_opt(rargs, values) - elif self.allow_interspersed_args: - largs.append(arg) - del rargs[0] - else: - return - -def format(q): - res,code,txt = q.check() - print res - if res in ('pass','neutral','unknown'): print - else: print txt - print 'spfquery:',q.get_header_comment(res) - print 'Received-SPF:',q.get_header(res,'spfquery') - -def main(argv): - parser = PerlOptionParser() - parser.add_option("--file",dest="file") - parser.add_option("--ip",dest="ip") - parser.add_option("--sender",dest="sender") - parser.add_option("--helo",dest="hello_name") - parser.add_option("--local",dest="local_policy") - parser.add_option("--rcpt-to",dest="rcpt") - parser.add_option("--default-explanation",dest="explanation") - parser.add_option("--sanitize",type="int",dest="sanitize") - parser.add_option("--debug",type="int",dest="debug") - opts,args = parser.parse_args(argv) - if opts.ip: - q = spf.query(opts.ip,opts.sender,opts.hello_name,local=opts.local_policy) - if opts.explanation: - q.set_default_explanation(opts.explanation) - format(q) - if opts.file: - if opts.file == '0': - fp = sys.stdin - else: - fp = open(opts.file,'r') - for ln in fp: - ip,sender,helo,rcpt = ln.split(None,3) - q = spf.query(ip,sender,helo,local=opts.local_policy) - if opts.explanation: - q.set_default_explanation(opts.explanation) - format(q) - fp.close() - -if __name__ == "__main__": - import sys - main(sys.argv[1:]) diff --git a/strike3.txt b/strike3.txt deleted file mode 100644 index f76416c..0000000 --- a/strike3.txt +++ /dev/null @@ -1,66 +0,0 @@ -Subject: Critical mail server configuration error - -This is an automatically generated Delivery Status Notification. - -THIS IS A WARNING MESSAGE ONLY. - -YOU DO *NOT* NEED TO RESEND YOUR MESSAGE. - -Delivery to the following recipients has been delayed. - - %(rcpt)s - -Subject: %(subject)s - -Someone at IP address %(connectip)s sent an email claiming -to be from %(sender)s. - -If that wasn't you, then your domain, %(sender_domain)s, -was forged - i.e. used without your knowlege or authorization by -someone attempting to steal your mail identity. This is a very -serious problem, and you need to provide authentication for your -SMTP (email) servers to prevent criminals from forging your -domain. The simplest step is usually to publish an SPF record -with your Sender Policy. - -For more information, see: http://spfhelp.net - -I hate to annoy you with a DSN (Delivery Status -Notification) from a possibly forged email, but since you -have not published a sender policy, there is no other way -of bringing this to your attention. - -If it *was* you that sent the email, then your email domain -or configuration is in error. If you don't know anything -about mail servers, then pass this on to your SMTP (mail) -server administrator. We have accepted the email anyway, in -case it is important, but we couldn't find anything about -the mail submitter at %(connectip)s to distinguish it from a -zombie (compromised/infected computer - usually a Windows -PC). There was no PTR record for its IP address (PTR names -that contain the IP address don't count). RFC2821 requires -that your hello name be a FQN (Fully Qualified domain Name, -i.e. at least one dot) that resolves to the IP address of -the mail sender. In addition, just like for PTR, we don't -accept a helo name that contains the IP, since this doesn't -help to identify you. The hello name you used, -%(heloname)s, was invalid. - -Furthermore, there was no SPF record for the sending domain -%(sender_domain)s. We even tried to find its IP in any A or -MX records for your domain, but that failed also. We really -should reject mail from anonymous mail clients, but in case -it is important, we are accepting it anyway. - -We are sending you this message to alert you to the fact that - -Either - Someone is forging your domain. -Or - You have problems with your email configuration. -Or - Possibly both. - -If you need further assistance, please do not hesitate to -contact me again. - -Kind regards, - -postmaster@%(receiver)s diff --git a/test.py b/test.py deleted file mode 100644 index 0e12658..0000000 --- a/test.py +++ /dev/null @@ -1,17 +0,0 @@ -import unittest -import testbms -import testmime -import testsample -import os - -def suite(): - s = unittest.TestSuite() - s.addTest(testbms.suite()) - s.addTest(testmime.suite()) - s.addTest(testsample.suite()) - return s - -if __name__ == '__main__': - try: os.remove('test/milter.log') - except: pass - unittest.TextTestRunner().run(suite()) diff --git a/test/amazon b/test/amazon deleted file mode 100644 index b679c8c..0000000 --- a/test/amazon +++ /dev/null @@ -1,710 +0,0 @@ -From stuart@bmsi.com Wed May 1 14:37:14 2002 -Return-Path: <stuart@bmsi.com> -Received: from bmsi.com (IDENT:stuart@localhost [127.0.0.1]) - by gathman.bmsi.com (8.11.6/8.11.6) with ESMTP id g41IbCF01796 - for <stuart@gathman.bmsi.com>; Wed, 1 May 2002 14:37:13 -0400 -Sender: stuart@gathman.bmsi.com -Message-ID: <3CD035D7.18ADF27F@bmsi.com> -Date: Wed, 01 May 2002 14:37:11 -0400 -From: "Stuart D. Gathman" <stuart@bmsi.com> -Organization: Business Management Systems, Inc. -X-Mailer: Mozilla 4.78 [en] (X11; U; Linux 2.4.9-21 i586) -X-Accept-Language: en -MIME-Version: 1.0 -To: stuart@gathman.bmsi.com -Subject: Amazon.com--Earth's Biggest Selection -Content-Type: multipart/mixed; - boundary="------------59A46341C90BA737DD47867B" - -This is a multi-part message in MIME format. ---------------59A46341C90BA737DD47867B -Content-Type: multipart/alternative; - boundary="------------0B098FB91956AC123C61B151" - - ---------------0B098FB91956AC123C61B151 -Content-Type: text/plain; charset=us-ascii -Content-Transfer-Encoding: 7bit - -http://www.amazon.com/exec/obidos/subst/home/redirect.html/103-3111065-2579065 - --- - Stuart D. Gathman -Business Management Systems Inc. Phone: 703 591-0911 Fax: 703 591-6154 -"Confutatis maledictis, flamis acribus addictis" - background song for -a Microsoft sponsored "Where do you want to go from here?" commercial. - - - ---------------0B098FB91956AC123C61B151 -Content-Type: text/html; charset=us-ascii -Content-Transfer-Encoding: 7bit - -<!doctype html public "-//w3c//dtd html 4.0 transitional//en"> -<html> -<A HREF="http://www.amazon.com/exec/obidos/subst/home/redirect.html/103-3111065-2579065">http://www.amazon.com/exec/obidos/subst/home/redirect.html/103-3111065-2579065</A> -<pre>-- - Stuart D. Gathman <stuart@bmsi.com> -Business Management Systems Inc. Phone: 703 591-0911 Fax: 703 591-6154 -"Confutatis maledictis, flamis acribus addictis" - background song for -a Microsoft sponsored "Where do you want to go from here?" commercial.</pre> - </html> - ---------------0B098FB91956AC123C61B151-- - ---------------59A46341C90BA737DD47867B -Content-Type: text/html; charset=us-ascii; - name="103-3111065-2579065" -Content-Transfer-Encoding: 7bit -Content-Disposition: inline; - filename="103-3111065-2579065" -Content-Base: "http://www.amazon.com/exec/obidos/subs - t/home/redirect.html/103-3111065-25 - 79065" -Content-Location: "http://www.amazon.com/exec/obidos/subs - t/home/redirect.html/103-3111065-25 - 79065" - - -<html> -<head> -<title> -Amazon.com--Earth's Biggest Selection -</title> -<meta name="keywords" content="amazon.com,amazon books,amazon,amazon.com books,amazon music,amazon.com music,amazon video,amazon.com video,auctions,amazon auctions,amazon.com auctions,electronics,consumer electronics,gifts,amazon gifts,amazon.com gifts,cards,e-cards,e-mail cards,greeting cards,amazon cards,amazon.com cards,toys,amazon toys,amazon.com toys,games,amazon games,amazon.com games,toys & games,toys and games"> -<style type="text/css"><!-- .serif { font-family: times,serif; font-size: medium; } -.sans { font-family: verdana,arial,helvetica,sans-serif; font-size: medium; } -.small { font-family: verdana,arial,helvetica,sans-serif; font-size: small; } -.h1 { font-family: verdana,arial,helvetica,sans-serif; color: #CC6600; font-size: medium; } -.h3color { font-family: verdana,arial,helvetica,sans-serif; color: #CC6600; font-size: small; } -.tiny { font-family: verdana,arial,helvetica,sans-serif; font-size: x-small; } -.listprice { font-family: arial,verdana,helvetica,sans-serif; text-decoration: line-through; font-size: small; } -.price { font-family: verdana,arial,helvetica,sans-serif; color: #990000; font-size: small; } ---></style> -</head> -<body bgcolor="#FFFFFF" link="#003399" alink="#FF9933" vlink="#996633" text="#000000" onLoad="document.searchform.elements[1].focus()"> -<a name="top"></a> -<map name="right_top_nav_map"> -<area shape="rect" href=/exec/obidos/shopping-basket/ref=top_nav_sb_gateway/103-3111065-2579065 coords="0,0,80,21"> -<area shape="rect" href=/exec/obidos/wishlist/ref=cm_wl_topnav_gateway/103-3111065-2579065 coords="85,0,151,21"> -<area shape="rect" href=/exec/obidos/account-access-login/ref=top_nav_ya_gateway/103-3111065-2579065 coords="155,0,256,21"> -<area shape="rect" href=/exec/obidos/tg/browse/-/508510/ref=top_nav_hp_gateway/103-3111065-2579065 coords="260,0,299,21"> -</map> -<map name="gateway_nav_map"> -<area shape=rect coords="0,0,124,28" href=/exec/obidos/tg/stores/static/-/gateway/international-gateway/ref=gw_subnav_in/103-3111065-2579065> -<area shape=rect coords="125,0,228,28" href=/exec/obidos/tg/new-for-you/top-sellers/-/main/ref=gw_subnav_ts/103-3111065-2579065> -<area shape=rect coords="229,0,332,28" href=/exec/obidos/tg/browse/-/700060/ref=gw_subnav_target/103-3111065-2579065> -<area shape=rect coords="333,0,450,28" href=/exec/obidos/tg/browse/-/909656/ref=stuffandsubnav_td1_/103-3111065-2579065> -<area shape=rect coords="451,0,580,28" href=/exec/obidos/subst/misc/sell-your-stuff.html/ref=subnav_sys_/103-3111065-2579065> -</map> -<table border=0 width=100% cellspacing=0 cellpadding=0> -<tr><td width=100%> -<center> -<table width=100% border=0 cellspacing=0 cellpadding=0 vspace=0> -<tr> -<td width=25% rowspan=2> </td> -<td align=left valign=bottom><a href=/exec/obidos/subst/home/redirect.html/ref=nh_gateway/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/associates/navbar2000/logo-no-border(1).gif" width=148 height=43 alt="" border=0></a></td> -<td width=10%> </td> -<td align=right> -<img src="http://g-images.amazon.com/images/G/01/nav/personalized/cartwish/right-topnav-default-2.gif" width=300 height=22 alt="" USEMAP=#right_top_nav_map border=0></td> -<td align=right rowspan=2 width=25%> - -</td> -</tr> -<tr valign=bottom> -<td colspan=3 align=center> -<table align=center border=0 cellpadding=0 cellspacing=0><tr valign=bottom> -<td><a href=/exec/obidos/subst/home/home.html/ref%3Dtab%5Fgw%5Fgw%5F1/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/nav/personalized/tabs/welcome-on-whole.gif" width=60 height=26 border=0></a></td> -<td><a href=/exec/obidos/tg/stores/your/store-home/-/0/ref%3Dtab%5Fgw%5Ffr%5F2/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/nav/personalized/tabs/yourstore-off-sliced._ZCSTUART%27S,0,2,0,0,verdenab,7,90,90,80_.gif" width=81 height=26 border=0></a></td> -<td><a href=/exec/obidos/tg/browse/-/283155/ref%3Dtab%5Fgw%5Fb%5F3/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/nav/personalized/tabs/books-off-sliced.gif" width=39 height=26 border=0></a></td> -<td><a href=/exec/obidos/tg/browse/-/172282/ref%3Dtab%5Fgw%5Fe%5F4/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/nav/personalized/tabs/electronics-off-sliced.gif" width=74 height=26 border=0></a></td> -<td><a href=/exec/obidos/tg/browse/-/130/ref%3Dtab%5Fgw%5Fd%5F5/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/nav/personalized/tabs/dvd-off-sliced.gif" width=35 height=26 border=0></a></td> -<td><a href=/exec/obidos/tg/browse/-/171280/ref%3Dtab%5Fgw%5Ft%5F6/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/nav/personalized/tabs/toys-off-sliced.gif" width=47 height=26 border=0></a></td> -<td><a href=/exec/obidos/tg/browse/-/468642/ref%3Dtab%5Fgw%5Fvg%5F7/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/nav/personalized/tabs/videogames-off-sliced.gif" width=73 height=26 border=0></a></td> -<td><a href=/exec/obidos/tg/browse/-/600460/ref%3Dtab%5Fgw%5F%5F8/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/nav/personalized/tabs/corporate-off-sliced.gif" width=70 height=26 border=0></a></td> -<td><a href=/exec/obidos/subst/home/all-stores.html/ref%3Dtab_gw_storesdirectory/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/nav/personalized/tabs/see-more-off-sliced.gif" width=70 height=26 border=0></a></td> -</tr></table> -</td> -</tr> -</table> -</center> -</td></tr> -<tr align=center bgcolor=#006699> -<td><img src="http://g-images.amazon.com/images/G/01/nav/amazon/gateway/blue/gateway-subnav-default.gif" width=580 height=28 width=580 height=28 alt="" USEMAP="#gateway_nav_map" border=0></td> -</tr> -<tr> -<td bgcolor=#ffffdd align=center class=small> -<font face=verdana,arial,helvetica size=-1> -<font color="#CC6600"><B>Hello, Stuart D. Gathman.</B></font> -We have <A href="http://www.amazon.com/exec/obidos/ilm-redirect/103-3111065-2579065?append-uid=no&path=http://www.amazon.com/exec/obidos/subst%2Frecs%2Finstant-recs-home.html%2Fref%3Dpd_ir_gw_r/ref=ilm_stripe_272005/103-3111065-2579065&message=272005,m1,26">recommendations</A> for you. -</font><font face=verdana,arial,helvetica size=-2> -(If you're not Stuart D. Gathman, <A href="http://www.amazon.com/exec/obidos/ilm-redirect/103-3111065-2579065?append-uid=no&path=http://www.amazon.com/exec/obidos/flex-sign-in%2Fref%3Dpd_ir_gw_r%2F%3Fopt%3Doa%26page%3Drecs%2Fsign-in-secure.html%26response%3Dtg%2Frecs%2Frecs-post-login-dispatch%2F-%2Frecs%2Fpd_rw_gw_r/ref=ilm_stripe_272005/103-3111065-2579065&message=272005,m1,26">click here</A>.) -</font> -</td> -</tr> -</table> - -<br> -<table width=100% cellpadding=0 cellspacing=0 border=0> -<tr valign=top> -<td width=174> -<TABLE border=0 cellspacing=0 cellpadding=0><TR valign=bottom align=center> -<td><img src="http://g-images.amazon.com/images/G/01/v9/search-browse/search-gateway.gif" width=171 height=19 border=0 alt="Search Amazon.com"></td> -</TR> <TR valign=top align=center><TD> <TABLE border=0 width= 171 cellpadding=1 cellspacing=0 bgcolor=#708090 ><TR> <TD width=100%><TABLE width=100% border=0 cellpadding=4 cellspacing=0 bgcolor=#708090><TR> <TD bgcolor=#FFCC66 valign=top width=100%> -<form method="post" action="/exec/obidos/search-handle-form/103-3111065-2579065" name="searchform"> -<select name=index> -<option value=blended selected>All Products -<option value=books>Books -<option value=music>Popular Music -<option value=music-dd>Music Downloads -<option value=classical>Classical Music -<option value="dvd">DVD -<option value="vhs">VHS -<option value=theatrical>Movie Showtimes -<option value=toys>Toys -<option value=baby>Baby -<option value=pc-hardware>Computers -<option value=videogames>Video Games -<option value=electronics>Electronics -<option value=photo>Camera & Photo -<option value=software>Software -<option value=tools>Tools & Hardware -<option value=magazines>Magazines -<option value=garden>Outdoor Living -<option value=kitchen>Kitchen -<option value=travel>Travel -<option value=wireless-phones>Cell Phones & Service -<option value=outlet>Outlet -<option value=auction-redirect>Auctions -<option value=fixed-price-redirect>zShops -</select> -<input type="text" name="field-keywords" size="15"> -<input type="image" height="21" width="21" border=0 value="Go" name="Go" src="http://g-images.amazon.com/images/G/01/v9/search-browse/go-button-gateway.gif" align=absmiddle> -</TD> </TR> </TABLE> </TD> </TR> </TABLE> </TD> </form> -</TR> </TABLE> <br clear=left> -<TABLE border=0 cellspacing=0 cellpadding=0> -<TR valign=bottom align=center> -<td><img src="http://g-images.amazon.com/images/G/01/v9/search-browse/browse-gateway.gif" width=171 height=19 border=0 alt="Browse Amazon.com"></td> -</TR> <TR valign=top align=center> -<TD> <TABLE border=0 width= 171 cellpadding=1 cellspacing=0 bgcolor=#708090 ><TR> <TD width=100%><TABLE width=100% border=0 cellpadding=4 cellspacing=0 bgcolor=#708090><TR> <TD bgcolor=#ffffff valign=top width=100%> -<table cellpadding=3 cellspacing=0> -<tr> -<td class=small>• <b><a href="/exec/obidos/tg/browse/-/283155/ref=gw_br_bo/103-3111065-2579065">Books</a></b></td> -</tr> -<tr> -<td class=small>• <b><a href="/exec/obidos/tg/browse/-/172282/ref=gw_br_el/103-3111065-2579065">Electronics</a></b></td> -</tr> -<tr> -<td class=small>• <b><a href="/exec/obidos/tg/browse/-/540744/ref=gw_br_ba/103-3111065-2579065">Baby &</a><br> <a href="/exec/obidos/tg/browse/-/540744/ref=gw_br_ba/103-3111065-2579065">Baby Registry</a></b></td> -</tr> -<tr> -<td class=small>• <b><a href="/exec/obidos/tg/browse/-/5174/ref=gw_br_mu/103-3111065-2579065">Music</a></b></td> -</tr> -<tr> -<td class=small>• <b><a href="/exec/obidos/redirect-to-partner/ref=gw_br_dscm/103-3111065-2579065?name=dscm&aid=2&aparam=tb5270_bhp&trx=8056">Health & Beauty</a></b></td> -</tr> -<tr> -<td class=small>• <b><a href="/exec/obidos/tg/browse/-/130/ref=gw_br_dvd/103-3111065-2579065">DVD</a></b></td> -</tr> -<tr> -<td class=small>• <b><a href="/exec/obidos/tg/browse/-/229534/ref=gw_br_sw/103-3111065-2579065">Software</a></b></td> -</tr> -<tr> -<td class=small>• <b><a href="/exec/obidos/tg/browse/-/284507/ref=gw_br_ki/103-3111065-2579065">Kitchen &</a><br> <a href="/exec/obidos/tg/browse/-/284507/ref=gw_br_ki/103-3111065-2579065">Housewares</a></b></td> -</tr> -<tr> -<td class=small>• <b><a href="/exec/obidos/tg/browse/-/228013/ref=gw_br_hi/103-3111065-2579065">Tools &</a><br> <a href="/exec/obidos/tg/browse/-/228013/ref=gw_br_hi/103-3111065-2579065">Hardware</a></b></td> -</tr> -<tr> -<td class=small>• <b><a href="/exec/obidos/tg/browse/-/541966/ref=gw_br_pc/103-3111065-2579065">Computers</a></b></td> -</tr> -<tr> -<td class=small>• <b><a href="/exec/obidos/tg/browse/-/502394/ref=gw_br_p/103-3111065-2579065">Camera & Photo</a></b></td> -</tr> -<tr> -<td class=small>• <b><a href="/exec/obidos/tg/browse/-/562436/ref=gw_br_th/103-3111065-2579065">Movie Showtimes</a></b></td> -</tr> -<tr> -<td class=small>• <b><a href="/exec/obidos/tg/browse/-/468642/ref=gw_br_cvg/103-3111065-2579065">Computer &</a><br> <a href="/exec/obidos/tg/browse/-/468642/ref=gw_br_cvg/103-3111065-2579065">Video Games</a></b></td> -</tr> <tr> -<td class=small>• <b><a href="/exec/obidos/tg/browse/-/171280/ref=gw_br_tg/103-3111065-2579065">Toys & Games</a></b></td> -</tr> -<tr> -<td class=small>• <b><a href="/exec/obidos/tg/browse/-/301185/ref=gw_br_wi/103-3111065-2579065">Cell Phones</a><br> <a href="/exec/obidos/tg/browse/-/301185/ref=gw_br_wi/103-3111065-2579065">& Service</a></b></td> -</tr> -<tr> -<td class=small>• <b><a href="/exec/obidos/tg/browse/-/404272/ref=gw_br_vi/103-3111065-2579065">Video</a></b></td> -</tr> -<tr> -<td class=small>• <b><a href="/exec/obidos/tg/browse/-/599858/ref=gw_br_zi/103-3111065-2579065">Magazine</a><br> <a href="/exec/obidos/tg/browse/-/599858/ref=gw_br_zi/103-3111065-2579065">Subscriptions</a></b></td> -</tr> -<tr> -<td class=small>• <b><a href="/exec/obidos/tg/browse/-/286168/ref=gw_br_lp/103-3111065-2579065">Outdoor Living</a></b></td> -</tr> -<tr> -<td class=small>• <b><a href="/exec/obidos/tg/browse/-/605012/ref=gw_br_tr/103-3111065-2579065">Travel</a></b></td> -</tr> -<tr> -<td class=small>• <b><a href="/exec/obidos/acn-redirect-to-partner/ref=gw_br_cars/103-3111065-2579065?partner-name=carsdirect&partner-url=home%3Fpartner%3Damzn%26customerid%3Dbrowse">Cars</a></b></td> -</tr> -<tr> -<td class=small>• <b><a href="/exec/obidos/tg/browse/-/229220/ref=gw_br_gi/103-3111065-2579065">Gifts &</a><br> <a href="/exec/obidos/tg/browse/-/229220/ref=gw_br_gi/103-3111065-2579065">Gift Certificates</a></b></td> -</tr> -<tr> -<td class=small>• <b><a href="http://s1.amazon.com/exec/varzea/subst/home/home.html/ref=gw_br_au/103-3111065-2579065">Auctions</a></b></td> -</tr> -<tr> -<td class=small>• <b><a href="http://s1.amazon.com/exec/varzea/subst/home/fixed.html/ref=gw_br_zs/103-3111065-2579065">zShops</a></b></td> -</tr> -<tr> -<td class=small>• <b><a href="/exec/obidos/tg/browse/-/517808/ref=gw_br_ou/103-3111065-2579065">Outlet</a></b></td> -</tr> -<tr> -<td class=small>• <b><a href="/exec/obidos/tg/browse/-/600460/ref=gw_br_cb/103-3111065-2579065">Corporate</a> <br> <a href="/exec/obidos/tg/browse/-/600460/ref=gw_br_cb/103-3111065-2579065">Accounts</a></b></td> -</tr> -<tr> -<td class=small> -<a href="/exec/obidos/flex-sign-in/ref=pd_fr_gw_fav_edt/103-3111065-2579065?page=personalization/favorites/favorites-sign-in-secure.html&response=favorites-edit/personalization/favorites/edit-areas.html&pass_through=product-group-id.gateway.hp&method=GET"> -<img src="http://g-images.amazon.com/images/G/01/buttons/edit-favorites.gif" width=69 height=15 border=0 valign=top vspace=2></a><br> -</td> -</tr> -<tr> -<td class=small><b>Browse Partners</b></td> -</tr> -<tr> -<td class=small>• <a href="/exec/obidos/tg/browse/-/700060/ref=gw_tarb_/103-3111065-2579065"><img src="http://g-images.amazon.com/images/G/01/target/target-logo-sm.gif" width=71 height=17 border=0 alt=Target></a></td> -</tr> -<tr> -<td class=small>• <a href="/exec/obidos/tg/browse/-/171280/ref=gw_trub_/103-3111065-2579065"><img src="http://g-images.amazon.com/images/G/01/toys/navigation/tru-logo.gif" width=117 height=14 border=0 alt=Toysrus.com></a></td> -</tr> -<tr> -<td class=small>• <a href="/exec/obidos/tg/browse/-/540744/ref=gw_brub_/103-3111065-2579065"><img src="http://g-images.amazon.com/images/G/01/toys/navigation/bru-logo.gif" width=136 height=15 border=0 alt=Babiesrus.com></a></td> -</tr> -</table> -</TD> </TR> </TABLE> </TD> </TR> </TABLE> </TD> -</TR> -</TABLE> <br> -<TABLE border=0 width=171 cellpadding=1 cellspacing=0 bgcolor=#708090 ><TR> <TD width=100%><TABLE width=100% border=0 cellpadding=4 cellspacing=0 bgcolor=#708090><TR> <TD bgcolor=#ffffff valign=top width=100%> -<font face=verdana,arial,helvetica color=#000000 size=-1><b>Special Features</b></font><br> -<font face=verdana,arial,helvetica size=-1> -<ul><li> <A href="/exec/obidos/subst/alerts/signup.html/ref=gw_hp_ls_1_1/103-3111065-2579065">Alerts</A><li> <A href="/exec/obidos/subst/misc/anywhere/anywhere.html/ref=gw_hp_ls_1_2/103-3111065-2579065">Amazon.com -Anywhere</A><li> <A href="/exec/obidos/subst/misc/amazon-credit/marketing-page.html/ref=gw_hp_ls_1_3/103-3111065-2579065">Amazon Credit Account</A><li> <A href="/exec/obidos/subst/delivers/delivers-signup-combo.html/ref=gw_hp_ls_1_4/103-3111065-2579065">Delivers</A><li><A href="/exec/obidos/tg/browse/-/225840/ref=gw_hp_ls_1_5/103-3111065-2579065">Free e-Cards</A><li><A href="/exec/obidos/subst/community/community-home.html/ref=gw_hp_ls_1_6/103-3111065-2579065">Friends & Favorites</A><li> <A href="/exec/obidos/subst/gifts/gift-services/gift-certificates.html/ref=gw_hp_ls_1_7/103-3111065-2579065">Gift -Certificates</A><li> <A href="http://auctions.amazon.com/exec/varzea/subst/fx/home.html/ref=gw_hp_ls_1_8/103-3111065-2579065">Honor -System</A><li> <A href="/exec/obidos/subst/community/community.html/ref=gw_hp_ls_1_9/103-3111065-2579065">Purchase -Circles</A><li> -<A href="/exec/obidos/tg/browse/-/885446/ref=gw_hp_ls_1_10/103-3111065-2579065">Wedding -Registry</A></ul> -</font> -</TD> </TR> </TABLE> </TD> </TR> </TABLE> <br> -<TABLE border=0 width=171 cellpadding=1 cellspacing=0 bgcolor=#708090 ><TR> <TD width=100%><TABLE width=100% border=0 cellpadding=4 cellspacing=0 bgcolor=#708090><TR> <TD bgcolor=#ffffff valign=top width=100%> -<font face=verdana,arial,helvetica color=#000000 size=-1><b>Associates</b></font><br> -<font face=verdana,arial,helvetica size=-1> -Sell books, music, videos, and more from your -Web site. <A href="/exec/obidos/subst/associates/join/associates.html/ref=gw_hp_ls_2_1/103-3111065-2579065">Start earning -today</A>!<BR> -</font> -</TD> </TR> </TABLE> </TD> </TR> </TABLE> <br> -<p> -<br clear=all> -</td> -<td> </td> -<td> -<center> -</center> -<br clear=all><p> -<A href="http://www.amazon.com/exec/obidos/ilm-redirect/103-3111065-2579065?append-uid=no&path=http://www.amazon.com/exec/obidos/tg/browse/-/283155/ref=ilm_rc_285799/103-3111065-2579065&message=285799,m1,27"> -<center><img src="http://g-images.amazon.com/images/G/01/books/homepage-pricing/books-home-pricing-iii.gif" width=257 height=99 border=0></center> -</A> -<br clear=all><br> -<A href="http://www.amazon.com/exec/obidos/ilm-redirect/103-3111065-2579065?append-uid=no&path=http://www.amazon.com/exec/obidos/tg/browse/-/753570/ref=ilm_rc_283024/103-3111065-2579065&message=283024,gw_lr_dvd_lor,28"><img src="http://g-images.amazon.com/images/G/01/icons/thumbnails/b00003cwt6_thumb.gif" width=41 height=60 border=0 valign=top align=left></A> -Pre-order the Oscar®-winning blockbuster <A href="http://www.amazon.com/exec/obidos/ilm-redirect/103-3111065-2579065?append-uid=no&path=http://www.amazon.com/exec/obidos/tg/browse/-/753570/ref=ilm_rc_283024/103-3111065-2579065&message=283024,gw_lr_dvd_lor,28"><I>The Lord of the Rings: The Fellowship of the Ring</I></A>, arriving on <A href="http://www.amazon.com/exec/obidos/ilm-redirect/103-3111065-2579065?append-uid=no&path=http://www.amazon.com/exec/obidos/ASIN/B00003CWT6/ref=ilm_rc_283024/103-3111065-2579065&message=283024,gw_lr_dvd_lor,28">DVD</A> and <A href="http://www.amazon.com/exec/obidos/ilm-redirect/103-3111065-2579065?append-uid=no&path=http://www.amazon.com/exec/obidos/ASIN/B000065U6Q/ref=ilm_rc_283024/103-3111065-2579065&message=283024,gw_lr_dvd_lor,28">video</A> August 6. -<br clear=all><br> -<b class=small><A href="/exec/obidos/tg/browse/-/229220/ref=gw_hp_cs_1_1/103-3111065-2579065">In Gifts</A></b><br> -<A href="/exec/obidos/tg/browse/-/229220/ref=gw_hp_cs_2_1/103-3111065-2579065"><img src="http://g-images.amazon.com/images/G/01/marketing/mothers_day/md_sd_roto.jpg" width=100 height=95 border=0 align=left hspace=4></A> -<b><font face=verdana,arial,helvetica color=#CC6600>Mother's Day Is May 12</font></b><br> -We've made it fun and easy to buy the perfect -present for Mom. Shop by <A href="/exec/obidos/tg/stores/recs/gift-wizard-refine/-/holiday/ref=gw_hp_cs_2_2/103-3111065-2579065">recipient</A> -or <A href="/exec/obidos/tg/stores/recs/gift-wizard/-/price/ref=gw_hp_cs_2_3/103-3111065-2579065">price</A>, -browse <A href="/exec/obidos/tg/stores/recs/gift-wizard/-/topsellers/ref=gw_hp_cs_2_4/103-3111065-2579065">top -sellers</A>, or order <A href="http://www.amazon.com/exec/obidos/redirect-to-external-url/103-3111065-2579065?path=http%3A//www.proflowers.com/freechocolate/freechocolate.cfm%3FREF%3DFCHAmazonGatewayExp042702">flowers</A>. -Visit <A href="/exec/obidos/tg/browse/-/229220/ref=gw_hp_cs_2_5/103-3111065-2579065">Gifts</A> for -these and more great ideas for expressing your love and -appreciation.<BR> - <br clear=left> -<br clear=all> -<a href=/exec/obidos/instant-recs/recs/instant-recs-home.html/ref=pd_gw_qpt_h/103-3111065-2579065><b class=small>Your Recommendations</b></a> -<br> <b class=h1> -<i>War in Heaven</i> -</b> -</b><br> -<a href=/exec/obidos/ASIN/0802812198/ref=pd_gw_qpt_1/103-3111065-2579065><img src="http://images.amazon.com/images/P/0802812198.01.__PE20_PIm.arrow,TopLeft,-2,-19_SCTZZZZZZZ_.jpg" width=76 height=116 vspace=3 hspace=7 align=left border=0></a> -<b>Amazon.com</b><br> -"The telephone was ringing wildly," begins Charles Williams's novel <I>War in Heaven</I>, "but without result, since there was no-one in the room but the corpse." From this abrupt--and darkly humorous--start, Williams takes us on a 20th-century version of the Grail quest, with an Archdeacon, a Duke, and an... -<a href=/exec/obidos/ASIN/0802812198/ref=pd_gw_qpt_1/103-3111065-2579065> -<font size=-1>Read more</font></a> -<span class=tiny> -| -(<a href=/exec/obidos/tg/recs/ir-why/-/books/0/regular/none/0802812198/gw/1/pc/3/none/ref=pd_gw_qpt_1/103-3111065-2579065>Why was I recommended this?</a>) -</span> -<br clear=all> -<br><b class=small>More Recommendations</b><br> -<img src="http://g-images.amazon.com/images/G/01/icons/small-blue-books-icon.gif" width=18 height=18 border=0 alt=Icon > -<a href=/exec/obidos/ASIN/0471070408/ref=pd_gw_qpt_2/103-3111065-2579065><i>Reliable Linux</i></a> by Iain Campbell -<span class=tiny> -(<a href=/exec/obidos/tg/recs/ir-why/-/books/0/regular/none/0471070408/gw/1/pc/3/none/ref=pd_gw_qpt_2/103-3111065-2579065>Why?</a>) -</span> -<br> -<img src="http://g-images.amazon.com/images/G/01/icons/small-blue-books-icon.gif" width=18 height=18 border=0 alt=Icon > -<a href=/exec/obidos/ASIN/1565926102/ref=pd_gw_qpt_3/103-3111065-2579065><i>Programming PHP</i></a> by Rasmus Lerdorf, et al -<span class=tiny> -(<a href=/exec/obidos/tg/recs/ir-why/-/books/0/regular/none/1565926102/gw/1/pc/3/none/ref=pd_gw_qpt_3/103-3111065-2579065>Why?</a>) -</span> -<br> -<img src="http://g-images.amazon.com/images/G/01/icons/small-blue-books-icon.gif" width=18 height=18 border=0 alt=Icon > -<a href=/exec/obidos/ASIN/0802812201/ref=pd_gw_qpt_4/103-3111065-2579065><i>Descent into Hell</i></a> by Charles W. Williams -<span class=tiny> -(<a href=/exec/obidos/tg/recs/ir-why/-/books/0/regular/none/0802812201/gw/1/pc/3/none/ref=pd_gw_qpt_4/103-3111065-2579065>Why?</a>) -</span> -<br> -<img src="http://g-images.amazon.com/images/G/01/icons/small-blue-books-icon.gif" width=18 height=18 border=0 alt=Icon > -<a href=/exec/obidos/ASIN/059600186X/ref=pd_gw_qpt_5/103-3111065-2579065><i>Network Troubleshooting Tools (O'Reilly System Administration)</i></a> by Joseph D. Sloan -<span class=tiny> -(<a href=/exec/obidos/tg/recs/ir-why/-/books/0/regular/none/059600186X/gw/1/pc/3/none/ref=pd_gw_qpt_5/103-3111065-2579065>Why?</a>) -</span> -<br> -<p> -<font face=verdana,arial,helvetica size=-1><a href=/exec/obidos/tg/stores/your/favorites/-/music/ref=pd_fr_gw_nr_h/103-3111065-2579065><b>Your Music Store</b></a></font><br> -<font face=verdana,arial,helvetica color=#CC6600><b> -Isaac Freeman, et al, -<i>Beautiful Stars</i> -</b></font> -<br> -<a href=/exec/obidos/ASIN/B000063TQV/ref=pd_fr_qw_nr_1/103-3111065-2579065><img src="http://images.amazon.com/images/P/B000063TQV.01.26TLZZZZ.jpg" width=73 height=71 vspace=3 hspace=7 align=left border=0></a> -Great African American gospel music has an indisputable power, rooted in the audible faith of its performers and the beauty of their voices. As the bass singer of the <a href="/exec/obidos/tg/stores/artist/glance/-/73920/103-3111065-2579065">Fairfield Four</a>, an a cappella group that started more than a half century ago,... -<a href=/exec/obidos/ASIN/B000063TQV/ref=pd_fr_qw_nr_1/103-3111065-2579065><font size=-1>Read more</font></a> -<br> -<br clear=left> -<br> -<table border=0 cellpadding=2 cellspacing=0><tr><td colspan=2> -<p><b class="small">More Stores:</b> -</td></tr> -<tr valign=top><td width=1%><a href=/exec/obidos/tg/stores/your/favorites/-/electronics/ref=pd_fr_qw_nr_2/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/icons/small-blue-electronics-icon.gif" width=18 height=18 alt=Icon border=0 align=absmiddle></a></td><td><b class="small"><a href=/exec/obidos/tg/stores/your/favorites/-/electronics/ref=pd_fr_gw_nr_2_p/103-3111065-2579065>Your Electronics Store</a>:</b> <a href=/exec/obidos/ASIN/B000063574/ref=pd_fr_gw_nr_2/103-3111065-2579065>iRiver SlimX iMP-350 CD/MP3 Player with 8 minutes ASP and Upgradeable Firmware</a> -by iRiver -</td></tr> -<tr valign=top><td width=1%><a href=/exec/obidos/tg/stores/your/favorites/-/video/ref=pd_fr_qw_nr_3/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/icons/small-blue-video-icon.gif" width=18 height=18 alt=Icon border=0 align=absmiddle></a></td><td><b class="small"><a href=/exec/obidos/tg/stores/your/favorites/-/video/ref=pd_fr_gw_nr_3_p/103-3111065-2579065>Your Video Store</a>:</b> <a href=/exec/obidos/ASIN/B000062XNA/ref=pd_fr_gw_nr_3/103-3111065-2579065><i>Ocean's Eleven</i></a> -<b>VHS</b> ~ George Clooney -</td></tr> -</table> -<p> -<b><font face=verdana,arial,helvetica color=#CC6600>Listmania!</font></b><br> -<font face=verdana,arial,helvetica size=-2> -(<a href=/exec/obidos/tg/browse/-/542566/103-3111065-2579065>What is this?</a>) -</font><br> -<table width=100% border=0 cellpadding=5 cellspacing=0> -<tr valign=top> -<td width=50% class=small> -<a href=/exec/obidos/tg/listmania/list-browse/-/2RKS17C9X4D3F/ref=pd_gw_lmq_1/103-3111065-2579065><img src="http://images.amazon.com/images/P/0072127732.01.__PIm.arrow,TopLeft,-2,-19_SCTZZZZZZZ_.jpg" width=76 height=109 border=0 vspace=4 hspace=5></a> -<p> -<font face=verdana,arial,helvetica size=-1> -<a href=/exec/obidos/tg/listmania/list-browse/-/2RKS17C9X4D3F/ref=pd_gw_lmq_1/103-3111065-2579065><b>Best Linux Security books</b></a>: A list by <a href=/exec/obidos/tg/cm/member-fil/-/A3362WVVMJ3LE9/ref=pd_gw_lmq_n1/103-3111065-2579065>J. Parker</a>, Administrator, hacker.<br> -(7 item list)</font> -</td> -<td width=50% class=small> -<a href=/exec/obidos/tg/listmania/list-browse/-/2B0DIAPG2D3RT/ref=pd_gw_lmq_2/103-3111065-2579065><img src="http://images.amazon.com/images/P/0070419531.01.__PIm.arrow,TopLeft,-2,-19_SCTZZZZZZZ_.jpg" width=76 height=109 border=0 vspace=4 hspace=5></a> -<p> -<font face=verdana,arial,helvetica size=-1> -<a href=/exec/obidos/tg/listmania/list-browse/-/2B0DIAPG2D3RT/ref=pd_gw_lmq_2/103-3111065-2579065><b>Networking</b></a>: A list by <a href=/exec/obidos/tg/cm/member-fil/-/AJINE650CAMUQ/ref=pd_gw_lmq_n2/103-3111065-2579065>gakis</a>, Engineer<br> -(13 item list)</font> -</td> -</tr> -<tr> -<td colspan=2 class=small><ul> -<li><a href=/exec/obidos/tg/listmania/list-browse/-/IEF1DNVKZO8B/ref=pd_gw_lmq_3/103-3111065-2579065>My Coder Library</a>: A list by <a href=/exec/obidos/tg/cm/member-fil/-/A3RK9LZQKL2YIN/ref=pd_gw_lmq_n3/103-3111065-2579065>John Washam</a><br> <li><a href=/exec/obidos/tg/listmania/list-browse/-/LE6A7H4L7VZK/ref=pd_gw_lmq_4/103-3111065-2579065>ALL THE FANTASY YOU'LL EVER NE</a>: A list by <a href=/exec/obidos/tg/cm/member-fil/-/A3628L43ZVEMP5/ref=pd_gw_lmq_n4/103-3111065-2579065>aramis</a><br> <li><a href=/exec/obidos/tg/listmania/list-browse/-/1MD5H6RUOIMIU/ref=pd_gw_lmq_5/103-3111065-2579065>Mythopoeic Fantasy</a>: A list by <a href=/exec/obidos/tg/cm/member-fil/-/A7CSNW9E46NR5/ref=pd_gw_lmq_n5/103-3111065-2579065>Vera Nazarian</a><br> </ul></td></tr></table> -<p> -<b class=small><A href="/exec/obidos/tg/browse/-/605012/ref=gw_hp_cb_1_1/103-3111065-2579065">In Travel</A></b><br> -<A href="/exec/obidos/tg/browse/-/605012/ref=gw_hp_cb_2_1/103-3111065-2579065"><img src="http://g-images.amazon.com/images/G/01/travel/promotions/travel-gateway1.gif" width=100 height=95 border=0 align=left hspace=4></A> -<b><font face=verdana,arial,helvetica color=#CC6600>Your Next Vacation Starts -Here</font></b><br> -Save up to 70% on hotels from Vegas to New York -and everywhere in between on <A href="/exec/obidos/acn-redirect-to-partner/103-3111065-2579065?partner-name=expedia&partner-url=pubspec/scripts/eap.asp%3FEAPID%3D11420-1%26GOTO%3DDAILY%26Page%3D/deals/hoteldeals.asp%3Frfrr%3D-2980">Expedia.com</A>. -Book a flight during Hotwire's <A href="/exec/obidos/acn-redirect-to-partner/103-3111065-2579065?partner-name=hotwire&partner-url=index.jsp%3Fsid%3D39151%26bid%3DB627">major-airline Spring Sale</A> through May 2 and fly the -big-name airlines at no-name airline prices. <A href="/exec/obidos/acn-redirect-to-partner/103-3111065-2579065?partner-name=thevacationstore&partner-url=cruises/show_cruise.asp%3Fd%3D%26i%3D743065%26c%3D24%26v%3D110">The -Vacation Store</A> is offering seven-day Holland America -Caribbean cruises from just $599. <BR> - <br clear=left> -<td width=174> -<table width=100% cellpadding=3 cellspacing=0 border=0> -<tr> -<td> -<a href=/exec/obidos/subst/xs/hotpicks.html/ref=xs_ie_13_gw/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/marketing/cross-shop/web-labs/lp_gate_roto_t._ZCStuart%5c,,3,5,300,300,verdenab,14,204,0,0_SCLZZZZZZZ_.gif" width=174 height=34 border=0></a><br> -<a href=/exec/obidos/subst/xs/hotpicks.html/ref=xs_ie_13_gw/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/marketing/cross-shop/web-labs/lp_gate_roto_m.gif" width=174 height=200 border=0></a><br> -<a href=/exec/obidos/subst/xs/hotpicks.html/ref=xs_ie_13_gw/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/marketing/cross-shop/web-labs/lp_gate_roto_b.gif" width=174 height=231 border=0></a><br> -<a href=/exec/obidos/tg/new-for-you/new-for-you/-/main/ref=pd_nfy_gw_n/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/banners/n4u/n4u-header-recognized-01.gif" width=174 height=41 hspace=0 vspace=0 align=right border=0 alt="New For You"></a><br clear=all> -<table border=0 bgcolor=#708090 cellpadding=1 cellspacing=0 width=174 align=right valign=top vspace=0 hspace=0><tr><td> -<table border=0 cellpadding=3 cellspacing=0 width=100% bgcolor=#ffffff> -<tr><td bgcolor=#ffffff align=middle> -<span class=small><font color=#CC6600><b>Stuart,</b></font> check out what's<b> <a href=/exec/obidos/tg/new-for-you/new-for-you/-/main/ref=pd_nfy_gw_n/103-3111065-2579065>New for You</a></b>:<br></span> -</td></tr> -<tr><td bgcolor=#ffffff align=middle> -<span class=tiny>(If you're not Stuart D. Gathman, <a href=/exec/obidos/flex-sign-in/ref=pd_nfy_gw_n/103-3111065-2579065?opt=o&page=misc/login/flex-sign-in-secure.html&response=tg/new-for-you/new-for-you/-/main>click here</a>.)</span> -<br><br> -</td></tr> -<tr bgcolor=#eeeecc><td> -<a href=/exec/obidos/tg/new-for-you/inbox/inbox/-/main/ref=pd_nfy_gw_ibx/103-3111065-2579065><b class=small>Your Message Center</b></a> -</td></tr> -<tr bgcolor=#ffffee><td> -<table><tr bgcolor=#ffffee> -<td valign=top><a href=/exec/obidos/tg/new-for-you/inbox/inbox/-/main/ref=pd_nfy_gw_ibx/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/icons/exclamation-clear.gif" width=20 height=20 border=0 alt=!></a></td> -<td class=small> You have <a href=/exec/obidos/tg/new-for-you/inbox/inbox/-/main/ref=pd_nfy_gw_ibx/103-3111065-2579065>5 new messages</a>. -<br><br> -</td> -</tr></table> -</td></tr> -<tr bgcolor=#eeeecc><td> -<font face=verdana,arial,helvetica size=-1><a href=/exec/obidos/shopping-basket/ref=pd_nfy_gw_sc/103-3111065-2579065><b>Your Shopping Cart</b></a></font> -</td></tr> -<tr><td> -<table><tr> -<td valign=top><a href=/exec/obidos/shopping-basket/ref=pd_nfy_gw_sc/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/icons/shopping-cart-small.gif" width=25 height=25 border=0 alt="Shopping Cart" align=left></a></td> -<td valign=top><font face=verdana,arial,helvetica size=-1>You have 0 items in <a href=/exec/obidos/shopping-basket/ref=pd_nfy_gw_sc/103-3111065-2579065>your Shopping Cart</a>.</font><br><br></td> -</tr></table> -</td></tr></table> -<table border=0 cellpadding=3 cellspacing=0 width=100% bgcolor=#ffffff vspace=0> -<tr bgcolor=#eeeecc><td class=small> -<a href=/exec/obidos/tg/new-for-you/new-releases/-/main/ref=pd_nfy_gw_n/103-3111065-2579065><b>Your New Releases</b></a> -</td></tr></table> -<table border=0 cellpadding=3 cellspacing=0 width=100% bgcolor=#ffffff vspace=0> -<tr valign=top><td> -<a href=/exec/obidos/tg/new-for-you/new-releases/-/music/37/ref=pd_nfy_gw_n1/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/icons/small-blue-music-icon.gif" width=18 height=18 border=0 alt=Icon ></a></td><td> -<a href=/exec/obidos/tg/new-for-you/new-releases/-/music/37/ref=pd_nfy_gw_n1/103-3111065-2579065><font face=verdana,arial,helvetica size=-1>Pop</font></a> -</td></tr> -<tr valign=top><td> -<a href=/exec/obidos/tg/new-for-you/new-releases/-/music/173429/ref=pd_nfy_gw_n2/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/icons/small-blue-music-icon.gif" width=18 height=18 border=0 alt=Icon ></a></td><td> -<a href=/exec/obidos/tg/new-for-you/new-releases/-/music/173429/ref=pd_nfy_gw_n2/103-3111065-2579065><font face=verdana,arial,helvetica size=-1>Christian & Gospel</font></a> -</td></tr> -<tr valign=top><td> -<a href=/exec/obidos/tg/new-for-you/new-releases/-/books/5/ref=pd_nfy_gw_n3/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/icons/small-blue-books-icon.gif" width=18 height=18 border=0 alt=Icon ></a></td><td> -<a href=/exec/obidos/tg/new-for-you/new-releases/-/books/5/ref=pd_nfy_gw_n3/103-3111065-2579065><font face=verdana,arial,helvetica size=-1>Computers & Internet</font></a> -</td></tr> -<tr valign=top><td> -<a href=/exec/obidos/tg/new-for-you/new-releases/-/kitchen/289814/ref=pd_nfy_gw_n4/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/icons/icon-kitchen-blue.gif" width=18 height=18 border=0 alt=Icon ></a></td><td> -<a href=/exec/obidos/tg/new-for-you/new-releases/-/kitchen/289814/ref=pd_nfy_gw_n4/103-3111065-2579065><font face=verdana,arial,helvetica size=-1>Cookware</font></a> -</td></tr> -<tr valign=top><td> -<a href=/exec/obidos/tg/new-for-you/new-releases/-/video/141/ref=pd_nfy_gw_n5/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/icons/small-blue-vhs-icon.gif" width=18 height=18 border=0 alt=Icon ></a></td><td> -<a href=/exec/obidos/tg/new-for-you/new-releases/-/video/141/ref=pd_nfy_gw_n5/103-3111065-2579065><font face=verdana,arial,helvetica size=-1>Action & Adventure</font></a> -</td></tr> -</td></tr> -<tr><td colspan=2 align=left> <img src="http://g-images.amazon.com/images/G/01/icons/orange-arrow.gif" width=10 height=9 border=0> <a href=/exec/obidos/tg/new-for-you/new-releases/-/main/ref=pd_nfy_gw_n/103-3111065-2579065><font face=verdana,arial,helvetica size=-1><b>More New Releases</b></font></a><p> -</td></tr></table> -<table border=0 cellpadding=3 cellspacing=0 width=100% bgcolor=#ffffff> -<tr bgcolor=#eeeecc><td class=small> -<a href=/exec/obidos/tg/new-for-you/movers-and-shakers/-/books/ref=pd_gw_msgr/103-3111065-2579065><b>Movers & Shakers</b></a> -</td></tr></table> -<table border=0 cellpadding=2 cellspacing=0 width=100% bgcolor=#ffffff vspace=0> -<tr><td valign=top align=center> -<img src="http://g-images.amazon.com/images/G/01/icons/uparrow_green2.gif" width=13 height=11 alt="Up"> -</td> -<td valign=top> -<font color=#339900 face=verdana,arial,helvetica size=-1><b>974%</b></font> </td></tr> -<tr><td valign=top align=left> -<img src="http://g-images.amazon.com/images/G/01/icons/small-blue-dvd-icon.gif" width=18 height=18 border=0 alt=Icon > -</td> -<td valign=top> -<font face=verdana,arial,helvetica size=-1><a href=/exec/obidos/tg/new-for-you/movers-and-shakers/-/dvd/ref=pd_gw_msd2/103-3111065-2579065>Dorothy L. Sayers Mysteries (Strong Poison / Have His Carcass / Gaudy Night)</a> -<font face=verdana,arial,helvetica size=-1> -<b>DVD</b> -<br>~ Dorothy L. Sayers -</font> -</font> -</td></tr> -<tr><td valign=top align=center> -<img src="http://g-images.amazon.com/images/G/01/icons/uparrow_green2.gif" width=13 height=11 alt="Up"> -</td> -<td valign=top> -<font color=#339900 face=verdana,arial,helvetica size=-1><b>2,415%</b></font> </td></tr> -<tr><td valign=top align=left> -<img src="http://g-images.amazon.com/images/G/01/icons/small-blue-books-icon.gif" width=18 height=18 border=0 alt=Icon > -</td> -<td valign=top> -<font face=verdana,arial,helvetica size=-1><a href=/exec/obidos/tg/new-for-you/movers-and-shakers/-/books/ref=pd_gw_msb2/103-3111065-2579065>Artemis Fowl</a> -<br><font face=verdana,arial,helvetica size=-1>by Eoin Colfer</font> -</font> -</td></tr> -<tr><td colspan=2> -<img src="http://g-images.amazon.com/images/G/01/icons/orange-arrow.gif" width=10 height=9 border=0> <font face=verdana,arial,helvetica size=-1><b><a href=/exec/obidos/tg/new-for-you/movers-and-shakers/-/books/ref=pd_gw_msgr/103-3111065-2579065>More Movers & Shakers</a></b> -<br> -</td></tr></table> -</td></tr></table> -</td></tr></table> -</td></tr></table> -<br clear="all"> -<center> -<form method="post" action="/exec/obidos/search-handle-form/103-3111065-2579065"> -<table border=0 width=100% cellpadding=1 cellspacing=0 bgcolor=#999999> -<tr><td> -<table border=0 width=100% bgcolor=#ffffff cellspacing=0 cellpadding=5 class="small"> -<tr valign=top><td width=33% class="small"> -<b>Where's My Stuff?</b><br> -• Track your <a href="/exec/obidos/flex-sign-in/ref=hy_f_1/103-3111065-2579065?opt=ab&page=help/ya-sign-in-secure.html&response=order-history-filtered&method=POST&ss-order-filter=wheres-my-stuff&return-url=order-history-filtered">recent orders</a>.<br> -• View or change your orders in <a href="/exec/obidos/account-access-login/ref=hy_f_2/103-3111065-2579065">Your Account</a>. -<script language="JavaScript1.1" type="text/javascript"> -<!-- -var agt=navigator.userAgent.toLowerCase(); -var is_major = parseInt(navigator.appVersion); -var is_nav = ((agt.indexOf('mozilla')!=-1) && (agt.indexOf('spoofer')==-1) -&& (agt.indexOf('compatible') == -1) && (agt.indexOf('opera')==-1) -&& (agt.indexOf('webtv')==-1) && (agt.indexOf('hotjava')==-1)); -var is_gecko = (agt.indexOf('gecko') != -1); -var is_ie = ((agt.indexOf("msie") != -1) && (agt.indexOf("opera") == -1)); -var is_aol = (agt.indexOf("aol") != -1); -var is_opera = (agt.indexOf("opera") != -1); -var is_win = ( (agt.indexOf("win")!=-1) || (agt.indexOf("16bit")!=-1) ); -//--> -</script> -<script language="JavaScript1.1" type="text/javascript"> -<!-- -var OpenedWin; -function openWin (URL, width, height) { -OpenedWin = window.open(URL, "demo_window", "width="+width+",height="+height+",status=no,menubar=no,location=no,toolbar=no,directories=no,scrollbars=no"); -if (! is_aol) { -var NewX = (screen.availWidth/2)-(width/2); -var NewY = (screen.availHeight/2)-(height/2); -OpenedWin.moveTo(NewX, NewY); -NewX = null; -NewY = null; -} -} -function launch (URL, width, height) { -if (!URL || !width || !height) { -alert("Error"); -} else if (width>screen.availWidth || height>screen.availHeight) { -var message; -message = "Your screen resolution is too low to display the demo.\nClick 'OK' if you wish to continue anyway.\n"; -message += '\n Your screen resolution: '+screen.width+' x '+screen.height; -message += ' | Viewable: '+screen.availWidth+' x '+screen.availHeight; -message += '\n Required: '+width+' x '+height; -if (confirm(message)) { -message = "If you can not find the close buttons, use your keyboard:\n"; -message += 'Windows: ALT+F4\n'; -message += 'Macintosh: CONTROL+W'; -alert(message); -openWin(URL, width, height); -} -} else { -openWin(URL, width, height); -} -} -function displayLink(text){ -if ( is_major >= 4 && is_win && ( is_nav || is_ie || is_opera || is_gecko ) ) { -document.write(text); -}; -} -//--> -</script> -<script language="JavaScript1.1" type="text/javascript"> -<!-- -displayLink('<br>• See our <b><a href=javascript:launch(\'/exec/obidos/subst/help/demo-wms/display-demo.html/ref=hy_f_demo/103-3111065-2579065\',788,444)>animated demo</a></b>!'); -//--> -</script> -</td> -<td width=33% class="small"> -<b>Shipping & Returns</b><br> -• See our <a href="/exec/obidos/tg/browse/-/468520/ref=hy_f_3/103-3111065-2579065">shipping rates & policies</a>.<br> -• <a href="/exec/obidos/subst/help/self-service-returns.html/ref=hy_f_4/103-3111065-2579065">Return</a> an item (here's our <a href="/exec/obidos/tg/browse/-/468532/103-3111065-2579065">Returns Policy</a>). -</td> -<td width=33% class="small"> -<b>Need Help?</b><br> -• Forgot your password? <a href="/exec/obidos/self-service-forgot-password-get-email/ref=hy_f_6/103-3111065-2579065">Click here</a>. -<br> -• <a href="/exec/obidos/subst/gifts/gift-certificates/gc-redeeming.html/ref=hy_f_7/103-3111065-2579065">Redeem</a> or <a href="/exec/obidos/subst/gifts/gift-services/gift-certificates.html/ref=hy_f_8/103-3111065-2579065">buy</a> a gift certificate.<br> -• <a href="/exec/obidos/tg/browse/-/508510/ref=hy_f_9/103-3111065-2579065">Visit our Help department</a>. <br> -</td></tr> -</table> -</td></tr> -<tr><td> -<table border=0 width=100% bgcolor=#FFCC66 cellspacing=0 cellpadding=5> -<tr><td align=center class="small"> -<b>Search </b> -<select name=index> -<option value=blended selected>All Products -<option value=books>Books -<option value=music>Popular Music -<option value=music-dd>Music Downloads -<option value=classical>Classical Music -<option value="dvd">DVD -<option value="vhs">VHS -<option value=theatrical>Movie Showtimes -<option value=toys>Toys -<option value=baby>Baby -<option value=pc-hardware>Computers -<option value=videogames>Video Games -<option value=electronics>Electronics -<option value=photo>Camera & Photo -<option value=software>Software -<option value=tools>Tools & Hardware -<option value=magazines>Magazines -<option value=garden>Outdoor Living -<option value=kitchen>Kitchen -<option value=travel>Travel -<option value=wireless-phones>Cell Phones & Service -<option value=outlet>Outlet -<option value=auction-redirect>Auctions -<option value=fixed-price-redirect>zShops -</select> -<b> for </b> -<input type="text" name="field-keywords" size="15"> -<input type=image name="Go" value="Go!" border=0 alt="Go!" src=http://g-images.amazon.com/images/G/01/v9/search-browse/go-button-gateway.gif width=21 height=21 border=0 align=absmiddle > </td></tr></table> -</td></tr> -</table> -</form> -<p align=center> -<b class=h1>Stuart D. Gathman, make </b><font color=#990000><b class=sans>$</b><b class=sans>310.61</b></font><br /> -<b class=sans>Sell <a href="/exec/obidos/flex-sign-in/ref=sdp_bbump_gw/103-3111065-2579065?opt=an&page=misc/login/flex-sign-in-secure.html&response=tg/stores/static/-/used/sell-your-collection/1/">your past purchases</a> at Amazon.com today!</b> -</p> -<table width="100%"> -<tr> -<td width="50%" valign="top" align="left"> -<span class="small"><a href=/exec/obidos/change-style/subst/home/redirect.html/103-3111065-2579065>Text Only</a></span> -</td> -<td width="50%" valign="top" align="right" class="small"> -<a href="#top">Top of Page</a> -</td> -</tr> -</table> -<center> -<p> -<a href=/exec/obidos/subst/home/all-stores.html/ref=gw_bt_st/103-3111065-2579065>Directory of All Stores</a><p> -Our International Sites: -<a href="/exec/obidos/redirect-to-external-url/ref=gw_bt_uk/103-3111065-2579065?path=http%3A//www.amazon.co.uk/exec/obidos/redirect-home%3Ftag%3Dintl-usgt-ukhome-21%26site%3Damazon">United Kingdom</a> - | -<a href="/exec/obidos/redirect-to-external-url/ref=gw_bt_de/103-3111065-2579065?path=http%3A//www.amazon.de/exec/obidos/redirect-home%3Ftag%3Dintl-usgt-dehome-21%26site%3Dhome">Germany</a> - | -<a href="/exec/obidos/redirect-to-external-url/ref=gw_bt_jp/103-3111065-2579065?path=http%3A//www.amazon.co.jp/exec/obidos/redirect-home%3Ftag%3Dintl-usgatew-jphome-22%26site%3Damazon">Japan</a> -  | -<a href="/exec/obidos/redirect-to-external-url/ref=gw_bt_fr/103-3111065-2579065?path=http%3A//www.amazon.fr/exec/obidos/redirect-home%3Fsite%3Damazon%26tag%3Dusfr-gatew-footer-21">France</a> -<p> -<a href=/exec/obidos/tg/browse/-/508510/ref=gw_bt_he/103-3111065-2579065>Help</a> | -<a href=/exec/obidos/shopping-basket/ref=gw_bt_sc/103-3111065-2579065>Shopping Cart</a> | -<a href=/exec/obidos/account-access-login/ref=gw_bt_ya/103-3111065-2579065>Your Account</a> | -<a href="http://s1.amazon.com/exec/varzea/ts/announcement-list-zshops/slp/ref=gw_bt_si/103-3111065-2579065">Sell Items</a> | -<a href="/exec/obidos/flex-sign-in/ref=gw_bt_oc/103-3111065-2579065?opt=a&page=ordering/one-click-address-sign-in-secure.html&response=one-click-main&method=GET&return-url=one-click-main">1-Click Settings</a> -<p> -<a href=/exec/obidos/subst/misc/company-info.html/ref=gw_bt_aa/103-3111065-2579065>About Amazon.com</a> | -<a href=/exec/obidos/tg/stores/job-listings/-/generic/home/103-3111065-2579065>Join Our Staff</a> | -<a href="/exec/obidos/subst/associates/join/associates.html/ref=gw_bt_as/103-3111065-2579065">Join Associates</a> | -<a href=/exec/obidos/subst/partners/direct/direct-application.html/ref=gw_bt_ad/103-3111065-2579065>Join Advantage</a> | -<a href="http://s1.amazon.com/exec/varzea/subst/fx/home.html/ref=gw_bt_hs/103-3111065-2579065">Join Honor System</a> -</center> -<center> -<p> -<div class="tiny" align=center> -<A HREF="/exec/obidos/subst/misc/policy/conditions-of-use.html/103-3111065-2579065">Conditions of Use</A> | <A HREF="/exec/obidos/tg/browse/-/468496/103-3111065-2579065">Privacy Notice</A> © 1996-2002, Amazon.com, Inc. or its affiliates -</div> -</center> -<!-- whfhYn47qD1fv3PW2R8XWAkFcMwteHFKxorD --> -</body> -</html> - ---------------59A46341C90BA737DD47867B-- - diff --git a/test/big5 b/test/big5 deleted file mode 100644 index 34df02a..0000000 --- a/test/big5 +++ /dev/null @@ -1,44 +0,0 @@ -Received: from www.bmsi.com (bmsweb.bmsi.com [219.109.11.130]) - by bmsaix.bmsi.com (8.12.1/8.12.1) with ESMTP id g218JVhw028058 - for <stuart@bmsi.com>; Fri, 1 Mar 2002 03:19:31 -0500 -Received: from apol ([210.201.89.183]) - by www.bmsi.com (8.12.1/8.12.1) with SMTP id g218JQkY030600 - for <stuart@bmsi.com>; Fri, 1 Mar 2002 03:19:27 -0500 -Date: Fri, 1 Mar 2002 03:19:26 -0500 -Received: from tcts1 - by yahoo.com with SMTP id KAqmIGSKwGQHv6LYDEOUUS; - Fri, 01 Mar 2002 16:18:13 +0800 -Message-ID: <VPvce@seed.net.tw> -From: �j���ذ�گd�DZШ|����@www.bmsi.com -To: -Subject: 8PxZzvJbH8VtozQ3rC01SOwm =?big5?Q?=A6p=AAG=A7A=B7Q=AFd=BE=C7=AA=BA=B8=DC=A1K?= BwnqwcNylfNuCIM3RG0mCx -MIME-Version: 1.0 -Content-Type: multipart/related; - type="multipart/alternative"; - boundary="----=_NextPart_kpWBTLcCozjeV8sH5gRbJoOo3aJ" -X-Mailer: foOkz11rguOMzavzZaDTw -X-Priority: 3 -X-MSMail-Priority: Normal - -This is a multi-part message in MIME format. - -------=_NextPart_kpWBTLcCozjeV8sH5gRbJoOo3aJ -Content-Type: multipart/alternative; - boundary="----=_NextPart_kpWBTLcCozjeV8sH5gRbJoOo3aJAA" - - -------=_NextPart_kpWBTLcCozjeV8sH5gRbJoOo3aJAA -Content-Type: text/html; - charset="big5" -Content-Transfer-Encoding: base64 - -PGh0bWwgeG1sbnM6dj0idXJuOnNjaGVtYXMtbWljcm9zb2Z0LWNvbTp2bWwiDQp4bWxuczpvPSJ1 -DQoNCjwvYm9keT4NCg0KPC9odG1sPg== - - -------=_NextPart_kpWBTLcCozjeV8sH5gRbJoOo3aJAA-- -------=_NextPart_kpWBTLcCozjeV8sH5gRbJoOo3aJ-- - - - - diff --git a/test/bounce b/test/bounce deleted file mode 100644 index c405887..0000000 --- a/test/bounce +++ /dev/null @@ -1,86 +0,0 @@ -Received: from localhost (localhost) - by bmsaix.bmsi.com (8.12.9/8.12.6) id h62JqW5p030912; - Wed, 2 Jul 2003 15:52:32 -0400 -Date: Wed, 2 Jul 2003 15:52:32 -0400 -From: Mail Delivery Subsystem <MAILER-DAEMON@bmsaix.bmsi.com> -Message-Id: <200307021952.h62JqW5p030912@bmsaix.bmsi.com> -To: <annagh000@bellsouth.net> -MIME-Version: 1.0 -Content-Type: multipart/report; report-type=delivery-status; - boundary="h62JqW5p030912.1057175552/bmsaix.bmsi.com" -Subject: Returned mail: see transcript for details -Auto-Submitted: auto-generated (failure) - -This is a MIME-encapsulated message - ---h62JqW5p030912.1057175552/bmsaix.bmsi.com - -The original message was received at Fri, 27 Jun 2003 15:28:03 -0400 -from IDENT:ndcHoBWTR9Bf/rEFYJRejRoPTaRDgSCl@bmsweb.bmsi.com [192.168.9.81] - - ----- The following addresses had permanent fatal errors ----- -makurat@erols.com - (reason: 452 4.3.0 Filter failure) - (expanded from: <makurat@bmsi.com>) - - ----- Transcript of session follows ----- -... while talking to [192.168.9.81]: ->>> DATA -<<< 452 4.3.0 Filter failure -makurat@erols.com... Deferred: 452 4.3.0 Filter failure -Message could not be delivered for 5 days -Message will be deleted from queue - ---h62JqW5p030912.1057175552/bmsaix.bmsi.com -Content-Type: message/delivery-status - -Reporting-MTA: dns; bmsaix.bmsi.com -Arrival-Date: Fri, 27 Jun 2003 15:28:03 -0400 - -Final-Recipient: RFC822; makurat@bmsi.com -X-Actual-Recipient: RFC822; makurat@erols.com -Action: failed -Status: 4.4.7 -Remote-MTA: DNS; [192.168.9.81] -Diagnostic-Code: SMTP; 452 4.3.0 Filter failure -Last-Attempt-Date: Wed, 2 Jul 2003 15:52:32 -0400 - ---h62JqW5p030912.1057175552/bmsaix.bmsi.com -Content-Type: message/rfc822 - -Return-Path: <annagh000@bellsouth.net> -Received: from spidey.bmsi.com (IDENT:ndcHoBWTR9Bf/rEFYJRejRoPTaRDgSCl@bmsweb.bmsi.com [192.168.9.81]) - by bmsaix.bmsi.com (8.12.9/8.12.6) with ESMTP id h5RJS3Vi042394 - for <makurat@bmsi.com>; Fri, 27 Jun 2003 15:28:03 -0400 -Received: from sunlong.com ([202.105.130.54]) - by spidey.bmsi.com (8.11.6/8.11.6) with SMTP id h5RJS2o03547 - for <makurat@bmsi.com>; Fri, 27 Jun 2003 15:28:02 -0400 -Message-Id: <200306271928.h5RJS2o03547@spidey.bmsi.com> -Received: from mx06.mail.bellsouth.net([218.104.6.10]) by sunlong.com(JetMail 2.5.3.0) - with SMTP id jma73efca64b; Fri, 27 Jun 2003 19:23:44 -0000 -To: <Undisclosed.Recipients@spidey.bmsi.com> -From: "Stacy McClain" <annagh000@bellsouth.net> -Subject: Defy Gravity in 15 minutes -Date: Sat, 28 Jun 2003 03:34:15 -1600 -MIME-Version: 1.0 -Content-Type: multipart/mixed; - boundary="----=_NextPart_000_646C_00001D33.00000BE1" -Reply-To: annagh000@bellsouth.net -X-AntiAbuse: : This header was added to track abuse, please include it with any abuse report -X-AntiAbuse: Primary Hostname - 210.222.2.13 -X-Originating-Host: : 210.188.201.159 - -------=_NextPart_000_646C_00001D33.00000BE1 -Content-Type: text/html; - charset="iso-8859-1" -Content-Transfer-Encoding: base64 - -PGh0bWw+DQoNCjxoZWFkPg0KPHRpdGxlPjwvdGl0bGU+DQo8L2hlYWQ+DQoNCjxib2R5Pg0KDQo8cD4NCjxhIGhyZWY9Imh0dHA6Ly9zcmQueWFob28uY29tL2Ryc3QvNzQxMjQzMjM1LypodHRwOi93d3cuZnJ5YmVlLmNvbS8iPg0KPGltZyBzcmM9Imh0dHA6Ly8yMTAuMTUuNTEuOTUvcGljX3dlbGwvZ3YyLmdpZiIgYm9yZGVyPSIwIiB3aWR0aD0iNDA1IiBoZWlnaHQ9IjI3MCI+PC9hPjwvcD4NCg0KPHA+DQo8YSBocmVmPSJodHRwOi8vc3JkLnlhaG9vLmNvbS9kcnN0Lzc0MTQxNjg4Mjc3NzcvKmh0dHA6L3d3dy5mcnliZWUuY29tL3BhZ2UvYS5odG1sIj4NCjxpbWcgc3JjPSJodHRwOi8vY2xpY2suanVzdGZvcnlvdS1tYWlsLmNvbS9pbWFnZXMvRjEuZ2lmIiB3aWR0aD0iNDEwIiBoZWlnaHQ9IjE0IiBib3JkZXI9IjAiPjwvYT48L3A+DQoNCjxwIGFsaWduPSJsZWZ0Ij4NCiZuYnNwOzwvcD4NCg0KPHAgc3R5bGU9Im1hcmdpbi10b3A6IDA7IG1hcmdpbi1ib3R0b206IDAiPg0KJm5ic3A7PC9wPg0KDQo8cCBzdHlsZT0ibWFyZ2luLXRvcDogMDsgbWFyZ2luLWJvdHRvbTogMCI+DQombmJzcDs8L3A+DQoNCjxwIHN0eWxlPSJtYXJnaW4tdG9wOiAwOyBtYXJnaW4tYm90dG9tOiAwIj4NCiZuYnNwOzwvcD4NCg0KPHAgc3R5bGU9Im1hcmdpbi10b3A6IDA7IG1hcmdpbi1ib3R0b206IDAiPjxmb250IHNpemU9IjEiPnFhd3NteXp0ciBxYXdzYW9lZHRhZ2ZwdiANCnFhd3N5ZmRhb3FqIHFhd3NjaSBxYXdzY! -212Z3ZrIHFhd3NvaW55d3pkbyBxYXdzbXVxYXdza29jIA0KcWF3c2hobmVkZCBxYXdzZWllbiBxYXdzemlnZ3hucGN2cyBxYXdzd3lkZSBxYXdzeWFwIHFhd3NxamVkeWhxYXdzZmt1bSANCnFhd3NmbSBxYXdzdW11Ym1mYmR3IHFhd3Nkc29ka2xvIHFhd3Nhc2VtayBxYXdzZXdzIHFhd3NxdWRneGVvcWF3c3J6IA0KcWF3c290dSBxYXdzcHplbnJoZW1xYSBxYXdzdXplcmpqcWZxIHFhd3NydWFucyBxYXdzbnBjcGFoZ2pwIHFhd3NxYXdoZHJxYXdzYmFscXNxaiANCnFhd3N5bmggcWF3c2VrIHFhd3N0YmNndGd0IHFhd3N0ZnhzeHd4ICBxYXdzandlcHFhd3NsYmN6ZWRuIHFhd3NzcW1nb3YgDQpxYXdzZ3phdiBxYXdzZ2N2aCBxYXdzd21sYWt1bW5sbiBxYXdzZHpqcW9yeCBxYXdzdGhvbHRmaWxmeHFhd3NpcGJneSANCnFhd3NpbHp5Znd2dnMgIHFhd3NpdmJwdmNiIHFhd3NrZXRpYmtocGRhIHFhd3N6ZmJqYm1yayBxYXdzbWZvZ29ucWF3c2FvIA0KcWF3c21vcXggcWF3c3FkeWVuaCBxYXdzYnMgcWF3c2l5aXBkYWx4IHFhd3N6aXlpbyBxYXdzaWZ6dXFyamltcSANCnFhd3NuayBxYXdza3dhciBxYXdzanNleHNmc2IgcWF3c3RxaWlhY2cgcWF3c2p0YnFobnFlIHFhd3Niam1pcGpxYXdzaHl4anNwbXhuIA0KIHFhd3NqcmJlbnIgcWF3c3p6b3p0ZndydyBxYXdzZ25uaHdjIHFhd3NrdXkgcWF3c3ZwcWF3c25qbmd5eHl1eCBxYXdzd3lvc2EgDQpxYXdzb2lnIHFhd3Nub25rcm5pbWcgcWF3c2NtcGdxemtwcm! -U8L2ZvbnQ+PC9wPg0KDQo8L2JvZHk+DQoNCjwvaHRtbD48L3RpdGxlPg0K - -------=_NextPart_000_646C_00001D33.00000BE1-- - - ---h62JqW5p030912.1057175552/bmsaix.bmsi.com-- - diff --git a/test/bounce1 b/test/bounce1 deleted file mode 100644 index a9d2841..0000000 --- a/test/bounce1 +++ /dev/null @@ -1,85 +0,0 @@ -Received: from zuul.kastle.com (root@localhost) - by zuul.kastle.com with ESMTP id h7JGdwn27534 - for <amy@koger.bmsi.com>; Tue, 19 Aug 2003 12:39:58 -0400 (EDT) -Received: from kastle.com (netgate.kastle.com [172.17.2.8]) - by zuul.kastle.com with ESMTP id h7JGdwV27530 - for <amy@koger.bmsi.com>; Tue, 19 Aug 2003 12:39:58 -0400 (EDT) -Received: by kastle.com - with XWall v3.27 ; - Tue, 19 Aug 2003 12:45:41 -0400 -From: System Administrator <postmaster@kastle.com> -To: "amy@koger.bmsi.com" <amy@koger.bmsi.com> -Subject: Non delivery report: 5.9.5 (Blocked attachment) -Date: Tue, 19 Aug 2003 12:45:41 -0400 -X-Mailer: XWall v3.27 -Mime-Version: 1.0 -Content-Type: multipart/report; report-type=delivery-status; - boundary="_NextPart_1_qmZrHLajoetbkwlTZTViemHPfyb" - -This is a multi part message in MIME format. - ---_NextPart_1_qmZrHLajoetbkwlTZTViemHPfyb -Content-Type: text/plain; charset="us-ascii" -Content-Transfer-Encoding: 7bit - -Your message - - From: amy@koger.bmsi.com - - To: lwilliams@kastle.com - - Subj: Thank you! - Sent: 2003-08-19 08:51 - -has encountered a delivery problem. - - -Reason: Blocked attachment -One of the attachment(s) in the message is blocked. -For security reasons the message was not or not completely delivered to -the recipient. - -Additional info: -The blocked attachment is: thank_you.pif - ---_NextPart_1_qmZrHLajoetbkwlTZTViemHPfyb -Content-Type: message/xdelivery-status ; name="delivery-status.txt" - -Reporting-MTA: dns; kastle.com -Received-From-MTA: dns; zuul.kastle.com -Arrival-Date: Tue, 19 Aug 2003 12:45:41 -0400 - -Final-Recipient: rfc822; lwilliams@kastle.com -Action: failed -Status: 5.9.5 - ---_NextPart_1_qmZrHLajoetbkwlTZTViemHPfyb -Content-Type: message/rfc822 - -Received: from zuul.kastle.com [172.17.2.100] - by kastle.com - with XWall v3.27 ; - Tue, 19 Aug 2003 12:45:41 -0400 -Received: from zuul.kastle.com (root@localhost) - by zuul.kastle.com with ESMTP id h7JGduo27526 - for <lwilliams@kastle.com>; Tue, 19 Aug 2003 12:39:56 -0400 (EDT) -Received: from 1333AVE2 (wan-vc8f35e.norva3.biz.mindspring.com [216.135.140.174]) - by zuul.kastle.com with ESMTP id h7JGdqS27522 - for <lwilliams@kastle.com>; Tue, 19 Aug 2003 12:39:53 -0400 (EDT) -Message-Id: <200308191639.h7JGdqS27522@zuul.kastle.com> -From: <amy@koger.bmsi.com> -To: <lwilliams@kastle.com> -Subject: Thank you! -Date: Tue, 19 Aug 2003 12:51:38 --0400 -X-MailScanner: Found to be clean -Importance: Normal -X-Mailer: Microsoft Outlook Express 6.00.2600.0000 -X-MSMail-Priority: Normal -X-Priority: 3 (Normal) -MIME-Version: 1.0 -Content-Type: multipart/mixed; - boundary="_NextPart_000_062C48F7" - ---_NextPart_1_qmZrHLajoetbkwlTZTViemHPfyb-- - - diff --git a/test/bound b/test/bound deleted file mode 100644 index 774fbd1..0000000 --- a/test/bound +++ /dev/null @@ -1,84 +0,0 @@ -From dspam Mon Sep 29 16:36:23 2003 -Received: from orcon.net.nz (port-219-88-129-82.orcon.net.nz [219.88.129.82]) - by spidey.planet.com (8.11.6/8.11.6) with SMTP id h8Q85c414321 - for <postmaster@bugle.com>; Fri, 26 Sep 2003 04:05:39 -0400 -Date: Fri, 26 Sep 2003 20:05:56 +1200 -From: Mail Delivery Subsystem <MAILER-DAEMON@orcon.net.nz> -Message-Id: <200309262005.IEI23104@mx1.orcon.net.nz> -To: <postmaster@bugle.com> -MIME-Version: 1.0 -Content-Type: multipart/report; report-type=delivery-status; - boundary="IEI23104.1064534400/mx1.orcon.net.nz" -Subject: Returned mail: User unknown -Auto-Submitted: auto-generated (failure) -X-DSpam-HeaderScore: 0.007433 - -This is a MIME-encapsulated message - ---IEI23104.1064534400/mx1.orcon.net.nz - -The original message was received at Fri, 26 Sep 2003 20:05:56 +1200 -from - - ----- The following addresses had permanent fatal errors ----- -<mike-liz@orcon.net.nz> - (expanded from: <mike-liz@orcon.net.nz>) - - ----- Transcript of session follows ----- -mail.local: unknown name: mike-liz -550 <mike-liz@orcon.net.nz>... User unknown - ---IEI23104.1064534400/mx1.orcon.net.nz -Content-Type: message/delivery-status - -Reporting-MTA: dns; mx1.orcon.net.nz -Received-From-MTA: DNS; -Arrival-Date: Fri, 26 Sep 2003 20:05:56 +1200 - -Final-Recipient: RFC822; <mike-liz@orcon.net.nz> -X-Actual-Recipient: RFC822; mike-liz@orcon.net.nz -Action: failed -Status: 5.1.1 -Last-Attempt-Date: Fri, 26 Sep 2003 20:05:56 +1200 - ---IEI23104.1064534400/mx1.orcon.net.nz -Content-Type: message/rfc822 - -Return-Path: <MAILER-DAEMON> -Received: from global_1.bugle.com ([12.4.120.82]) - by dbmail-mx3.orcon.co.nz (8.12.6/8.12.6/Debian-7) with ESMTP id h8O6CRJ8015038 - for <mike-liz@orcon.net.nz>; Wed, 24 Sep 2003 18:12:28 +1200 -From: postmaster@bugle.com -To: mike-liz@orcon.net.nz -Date: Wed, 24 Sep 2003 02:13:53 -0400 -MIME-Version: 1.0 -Content-Type: multipart/report; report-type=delivery-status; - boundary="9B095B5ADSN=_01C3664F7D2C23400000BC00global_1.bugle." -X-DSNContext: 335a7efd - 4457 - 00000001 - 80040546 -Message-ID: <YkMSnhRpy00001453@global_1.bugle.com> -Subject: Delivery Status Notification (Failure) -X-Spam-Score: 3.5 (***) BANG_MONEY,CASHCASHCASH,EXCUSE_10,EXCUSE_14,MAILTO_TO_SPAM_ADDR,NO_REAL_NAME,SENT_IN_COMPLIANCE -X-Scanned-By: MIMEDefang 2.32 (www . roaringpenguin . com / mimedefang) -This is a MIME-formatted message. -Portions of this message may be unreadable without a MIME-capable mail program. - ---9B095B5ADSN=_01C3664F7D2C23400000BC00global_1.bugle. -Content-Type: text/plain; charset=unicode-1-1-utf-7 - -This is an automatically generated Delivery Status Notification. - -Delivery to the following recipients failed. - - jholt@bugle.com - - - - ---9B095B5ADSN=_01C3664F7D2C23400000BC00global_1.bugle. -Content-Type: message/delivery-status - -Reporting-MTA: dns;global_1.bugle.com -Received-From-MTA: dns;gts.bugle.com ---IEI23104.1064534400/mx1.orcon.net.nz-- - - diff --git a/test/honey b/test/honey deleted file mode 100644 index 182727e..0000000 --- a/test/honey +++ /dev/null @@ -1,36 +0,0 @@ -From: downs <downs@elit.com> -To: luv@elit.com -Subject: Hello,luv,welcome to my hometown -MIME-Version: 1.0 -Content-Type: multipart/alternative; - boundary=Rer34xd7vC5E6b434MS3soP671RCD8 - ---Rer34xd7vC5E6b434MS3soP671RCD8 -Content-Type: text/html; -Content-Transfer-Encoding: quoted-printable - -<HTML><HEAD></HEAD><BODY> -<iframe src=3Dcid:Q2Xet76Sg02 height=3D0 width=3D0> -</iframe> -<FONT></FONT></BODY></HTML> - ---Rer34xd7vC5E6b434MS3soP671RCD8 -Content-Type: audio/x-wav; - name=story[1].scr -Content-Transfer-Encoding: base64 -Content-ID: <Q2Xet76Sg02> - -TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -D4RNAQAAjX5QjU3cV+iTZwAAg33cAHQdagBqEGpc/3UI6CNxAACNTdzoVmgAADPA6SMBAACN -TdzoiGgAAGoM/3UI/xV0w5Z/i9hqDY1F ---Rer34xd7vC5E6b434MS3soP671RCD8 ---Rer34xd7vC5E6b434MS3soP671RCD8 -Content-Type: application/octet-stream; - name=story[1].asp -Content-Transfer-Encoding: base64 -Content-ID: <Q2Xet76Sg02> - -H4sIAAAAAAAAA8Uca3ObSPJzXJX/0MttxU6t9bYdO7G0hxG22Oi1gOzz1VWlRmgksUagBWTF -6DZXKrcVuTeUWdAlKkRVJNmTg42MD2OJHsZjeLgZpcNEs95+ECFOEhecV9jffuEP7I+h4cP/ -AMwafOuETQAA ---Rer34xd7vC5E6b434MS3soP671RCD8-- diff --git a/test/missingboundary b/test/missingboundary deleted file mode 100644 index b59c3e1..0000000 --- a/test/missingboundary +++ /dev/null @@ -1,128 +0,0 @@ -From leec@windowsshop.com Fri Sep 10 11:48:25 2004 -Message-ID: <4141CDD4.7040305@windowsshop.com> -Date: Fri, 10 Sep 2004 11:52:52 -0400 -From: Lee Connor <leec@windowsshop.com> -User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.4) Gecko/20030624 Netscape/7.1 (ax) -X-Accept-Language: en-us, en -MIME-Version: 1.0 -To: Cleo Matthews-Conley <cleom@windowsshop.com>, - Tony Collini <tonyc@windowsshop.com>, - John Higinbothom <johnh@windowsshop.com> -CC: Rich Higgins <richh@windowsshop.com> -Subject: [Fwd: [Fwd: Customer Concerns]] -Content-Type: multipart/mixed; - boundary="------------020209070802060007090105" - -This is a multi-part message in MIME format. ---------------020209070802060007090105 -Content-Type: text/plain; charset=us-ascii; format=flowed -Content-Transfer-Encoding: 7bit - -Cleo - please review attached feedback from Sales team.......I recall at -an early meeting after we moved in you and Tony (and maybe 1 or 2 -others) were going to develop a voice mail procedure or instruction -sheet for all staff. It looks like we really need this to get what we -are looking for from the system. Please let me know when you can produce -this and give a draft to the managers here for review. -Thanks, -Lee - - ---------------020209070802060007090105 -Content-Type: message/rfc822; - name="[Fwd: Customer Concerns]" -Content-Transfer-Encoding: 7bit -Content-Disposition: inline; - filename="[Fwd: Customer Concerns]" - -Return-Path: <richh@windowsshop.com> -Received: from windowsshop.com (pc147.windowsshop.com [192.168.100.147] (may be forged)) - by lord.windowsshop.com (8.12.10/8.12.10) with ESMTP id i89KCClX003425 - for <leec@windowsshop.com>; Thu, 9 Sep 2004 16:12:12 -0400 -Message-ID: <4140B851.3020501@windowsshop.com> -Date: Thu, 09 Sep 2004 16:08:49 -0400 -From: Rich <richh@windowsshop.com> -User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.0.2) Gecko/20021120 Netscape/7.01 -X-Accept-Language: en-us, en -MIME-Version: 1.0 -To: Lee Connor <leec@windowsshop.com> -Subject: [Fwd: Customer Concerns] -Content-Type: multipart/mixed; - boundary="------------030301030706020401010801" -X-DSpam-Score: 0.000000 - -This is a multi-part message in MIME format. ---------------030301030706020401010801 -Content-Type: text/plain; charset=us-ascii; format=flowed -Content-Transfer-Encoding: 7bit - -Lee - do you want me to do anything else with this? - -Rich - -<!DSPAM:FEE4D3278234264874834386> - - ---------------030301030706020401010801 -Content-Type: message/rfc822; name="Customer Concerns"; - boundary="===============0045392615==" -Content-Transfer-Encoding: 7bit -Content-Disposition: inline; - filename="Customer Concerns" - - -Return-Path: <joes@windowsshop.com> -Received: from joes (pc148.windowsshop.com [192.168.100.148] (may be forged)) - by lord.windowsshop.com (8.12.10/8.12.10) with SMTP id i89K9BlX003262 - for <richh@windowsshop.com>; Thu, 9 Sep 2004 16:09:11 -0400 -From: "Joe Schmuck" <joes@windowsshop.com> -To: <richh@windowsshop.com> -Subject: Customer Concerns -Date: Thu, 9 Sep 2004 16:08:26 -0400 -Message-ID: <OFEPKHCCLPIECLFBLDHBAEAECAAA.joes@windowsshop.com> -MIME-Version: 1.0 -Content-Type: text/plain; - charset="iso-8859-1" -Content-Transfer-Encoding: 7bit -X-Priority: 3 (Normal) -X-MSMail-Priority: Normal -X-Mailer: Microsoft Outlook IMO, Build 9.0.2416 (9.0.2910.0) -Importance: Normal -X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2800.1106 -X-DSpam-Score: 0.000000 - -Rich: - -Following is a summary of concerns from customers regarding internal -communications within WS: - - - Not all employees have activated their voice mail - when this is the -case, the system will automatically cut you off - - When employees are out of the office, phones are not forwarded to a back -up, ie manager - - Reception has no record of employee attendance, and therefore will -forward call to individual requested - see point 2 - - Reception directs calls to incorrect individuals - - When entering voice mail, if you press '0', system does not default to -operator, but puts you back into individual voice mail - - Reception phone demeanor has no 'pep' - -Thanks -Joe - - - - ---- -Outgoing mail is certified Virus Free. -Checked by AVG anti-virus system (http://www.grisoft.com). -Version: 6.0.752 / Virus Database: 503 - Release Date: 9/3/2004 - - -<!DSPAM:FEE4D05F1332634871908793> - ---===============0045392615==-- ---------------030301030706020401010801-- - ---------------020209070802060007090105-- - diff --git a/test/samp1 b/test/samp1 deleted file mode 100644 index 17d416f..0000000 --- a/test/samp1 +++ /dev/null @@ -1,46 +0,0 @@ -Return-Path: <lauren@foobar.com> -Received: from foobar.com (localhost [127.0.0.1]) - by hemholt.foobar.com (8.9.3/8.8.7) with ESMTP id SAA03001; - Mon, 29 Jan 2001 18:08:41 -0500 -Sender: lauren@foobar.com -Message-ID: <3A75F7F6.CBF9E75@foobar.com> -Date: Mon, 29 Jan 2001 18:08:39 -0500 -From: Lauren Hemholz <lauren@foobar.com> -Organization: Hemholtz Family -X-Mailer: Mozilla 4.76 [en] (X11; U; Linux 2.2.16-3 i586) -X-Accept-Language: en -MIME-Version: 1.0 -To: Jriser13@aol.com -Subject: Re: P.B.S kids -References: <e4.1045e74c.27a7018b@aol.com> -Content-Type: multipart/alternative; - boundary="------------7EC2082FC4F651D73FCD6FE1" -Status: O - - ---------------7EC2082FC4F651D73FCD6FE1 -Content-Type: text/plain; charset=us-ascii -Content-Transfer-Encoding: 7bit - -Dear Agent 1 -I hope you can read this. Whenever you write label it P.B.S kids. - Eliza doesn't know a thing about P.B.S kids. got to go by -agent one. - ---------------7EC2082FC4F651D73FCD6FE1 -Content-Type: text/html; charset=us-ascii -Content-Transfer-Encoding: 7bit - -<!doctype html public "-//w3c//dtd html 4.0 transitional//en"> -<html> -<font color="#FFCCCC">Dear Agent 1</font> -<br><font color="#66FFFF">I hope you can read this. </font><font color="#FFCC33">Whenever -you write label it </font><font color="#993399">P.</font><font color="#000000">B.</font><font color="#66FFFF">S -</font><font color="#3366FF">kids.</font> -<br><font color="#3366FF"> Eliza doesn't know a thing about -</font><font color="#993399">P.</font><font color="#000000">B.</font><font color="#66FFFF">S -</font><font color="#3366FF">kids. got to go by</font> -<br>agent one.</html> - ---------------7EC2082FC4F651D73FCD6FE1-- - diff --git a/test/spam44 b/test/spam44 deleted file mode 100644 index b6bef31..0000000 --- a/test/spam44 +++ /dev/null @@ -1,497 +0,0 @@ -Received: from smtp01.mrf.mail.rcn.net (smtp01.mrf.mail.rcn.net [207.172.4.60]) - by www.bmsi.com (8.12.1/8.12.1) with ESMTP id g42A1XGQ014740 - for <makurat@bmsi.com>; Thu, 2 May 2002 06:01:33 -0400 -Received: from 66-44-42-109.s617.apx1.lnhdc.md.dialup.rcn.com ([66.44.42.109] helo=fjoneill) - by smtp01.mrf.mail.rcn.net with smtp (Exim 3.33 #10) - id 173DOu-0004vQ-00; Thu, 02 May 2002 06:01:26 -0400 -From: "Francis J. O'Neill" <fjoneill@erols.com> -To: "Atkinson, Steve" <scatkinson@ieee.org>, - "Blewett, John" <sixpackdad@aol.com>, - "Carroll, Matt & Jane" <janematt@ix.netcom.com>, - "Donovan, Kathleen" <rspatcelbr@aol.com>, - "Fitzpatrick, Vince" <vfitzpatrick@rjagroup.com>, - "Flannery, Jessica & Beth" <jeanmflan@aol.com>, - "Fontaine, Gene" <fontaineg@hotmail.com>, "Fox, Bob" <wvfoxmanva@aol.com>, - "Gerken, K." <kevin_gerken@faa.gov>, - "Gerken, Kevin \(Home\)" <gerken@msn.com>, - "Hagan, Carl & Jan" <hagan9600@aol.com>, - "Hardcastle, Joe & Carol" <ch4avon@aol.com>, - "Hardcastle, Joe" <jhardc8400@aol.com>, - "Hendrickson, Scott" <umuc_scott@yahoo.com>, - "Holl, Mike" <matbxholl@aol.com>, - "Jaworski, Francis J" <sevengatefarm@aol.com>, "JC" <jgcannon@aol.com>, - "Joe & Kathy Martin" <joe.martin@focuspoint.com>, - "Joe & Kathy Martin" <kjams5@aol.com>, "Kendle, Greg" <gkendle@erols.com>, - <Moptop1998@aol.com>, "pquell" <pquell@aol.com>, - "Quinan, Phil" <philq@fgm.com>, "Quintana, G" <glquintana@aol.com>, - "Rannazzisi, Jim" <jimrazz@aol.com>, "Reed, Kathi" <Mrsreedyreed@aol.com>, - "Serini, Pete" <serinip1@aol.com>, "Sherry, Ed" <gses56@earthlink.net>, - "Smith, T.J." <tsmit40@aol.com>, - "Southard, Jack & Ann" <Jacksout111@cs.com>, - "Terza, Rick" <srterza@hotmail.com>, "White, Diane" <dwhite703@aol.com>, - "Tisdale, David" <djtisdale@earthlink.net>, - "Zilka, Skip & Adella Mae" <skp406@cs.com>, - "Worrick, Matt & Dyanne" <worrickgrim@comcast.net>, - "Worrick, Matt" <worrickm@nima.mil>, - "Weaver Bob & Carol" <Bingobobby@aol.com>, - "Villa, Al & Jennifer" <avilla@sysplan.com>, - "Van Doren, Frank & Joan" <jfvandoren@aol.com>, - "Trudeau, Tom & Jeri" <trudeau7369@yahoo.com>, - "Trowbridge, Paul" <paltrow@starpower.net>, - "Trotter, Robert R." <robiedo@juno.com>, - "Tracy, Mike & Patty" <ptracy161@comcast.net>, - "Tonnessen, Jim & Maria" <Bosn1@aol.com>, - "Templeton, Pat" <pat.templeton@bcinow.com>, - "Taylor, Michelle" <Michelle_Taylor@datatel.com>, - "Taylor, Fran & Janet" <FranJanMom@aol.com>, - "Summit, Adelaide" <adandarn@aol.com>, - "Stalker, Nicole" <jmstalker@comcast.net>, - "Snidal, Brian" <sniwal@yahoo.com>, "Smith Danielle" <tsmith7746@aol.com>, - "Shorten, Jim & Marcia" <shor10@juno.com>, - "Scoffone, Dave" <dscoff1@hotmail.com>, - "Ryder, Tom & Kim" <threeryders@erols.com>, - "Ryder, Larry & Kate" <lkaryder@aol.com>, "Rossi, Ralph" <rr2520@aol.com>, - "Ross, Scott" <knighted@msn.com>, "Riley, Francis" <fxrdem@aol.com>, - "Riley, Dave & Susan" <mtatlas@aol.com>, - "Riley Tom & Marie" <triley0574@comcast.net>, - "Reynolds, Tommy" <treynolds009@hotmail.com>, - "Reynolds, Jim & Noreen" <reynolds-tribe@msn.com>, - "Quintana Dick" <richard.p.quintana@cpmx.mail.saic.com>, - "Purdy, Larry & Anne" <Purdy7@juno.com>, "Post, Harold" <hpost@vt.edu>, - "Podledsak, Tom" <tpodlesak@arl.mil>, - "Pino, Ernie & Gloria" <emanpino@juno.com>, - "Pasieka, Tony & Katy" <Pasiekat@yahoo.com>, - "Partsch, Jerry & Monica" <gpartsch@comcast.net>, - "Ong, Ken" <kennethong78@hotmail.com>, "O'Neill, Mike" <moneill@gmu.edu>, - "O'Neill, Frank" <fjoneill@erols.com>, - "Oliver, John & Juanita" <jmloliver@aol.com>, - "O'Hanlon, Peter \(Work\)" <pohanlon@uspsoig.gov>, - "O'Hanlon Peter & Anne" <aohanlon@manassas.k12.va.us>, - "Noonan, Tim & Bettie" <bbnoonan@juno.com>, - "Newton Bill" <golfnbill15@msn.com>, "Nannery, Phil" <pnannery@tla.com>, - "Nannery, Alison" <alisonnannery@yahoo.com>, - "Myrum, Marc" <myrumma@hotmail.com>, - "Murphy, John & Karen" <JCM2nd@msn.com>, - "Mullen,OSB, Father Godfrey" <GodfreyOSB@erols.com>, - "McCusker, JP & Maggie" <mccusker@af.pentagon.mil>, - "McCusker, J.P. & Maggie" <jpandmaggie@aol.com>, - "Mathers, David & Kathy" <davidandkathy@compuserve.com>, - "Makurat, Dennis" <makurat@bmsi.com>, - "Lord, Kevin & Gail" <Lordhaus@netzero.net>, - "Linehan, Pat" <prpjtdkl@aol.com>, "Linehan, Kellie" <kekalee427@aol.com>, - "linehan, Joe" <cadetbrat@aol.com>, - "Lewandowski, Matt & Mary" <matt@chipware.com>, - "Lester Doug" <Lester_doug@bah.com>, "Kurz, Al & Sandra" <ARKurz@erols.com>, - "Koeppel Bruce & Carolyn" <Koeppelb@oceusa.com>, - "Kindergan Bob & Dee" <bka2@att.net>, - "Kerzner, Ken & Maureen" <auzguyz1@comcast.net>, - "Keating, Russ & Julexy" <russty@juno.com>, - "Johnson, Laura" <davidjohnsonrealtor@yahoo.com>, - "Johns, Milt & Shellie" <miltesq@aol.com>, - "Jacobeen, Dave & Maria" <jacobeen@erols.com>, - "Hilchey, Paul" <paulhilchey@juno.com>, - "Head, Rich & Judy" <rghead@aol.com>, - "Hart Bob & Lorraine" <hartstv@aol.com>, - "Harrington, Thom" <t.j.harrington@ieee.org>, - "Harrington Cathy" <cathyH@atcc.org>, - "Hammersley, Ron & Ladavadee" <RHammer849@aol.com>, - "Grimes, Li nda & Frank" <lnf67@erols.com>, - "Gregory, Glen" <ikhnaton@geek.com>, - "Gregory Bob & Peggy" <pegory1@netzero.net>, - "Greco, Joe & Ann" <jgreco104@aol.com>, - "Goodman, Bill & Marcia" <bmgoodman@aol.com>, - "Goble, Theresa" <tagoman@juno.com>, - "Goble Dick & Theresa" <tagoman@aol.com>, - "Glennon John" <John.Glennon@fepoc.carefirst.com>, - "Gendron, Ray & Barbara" <gendronb1@erols.com>, - "Gendron, Jerry" <jbgendron@webtv.net>, - "Gaynord, Bill & Linda" <lbgaynord@aol.com>, - "Gareis Charlie" <gareiscj@aol.com>, - "Gagat, Ron & Judy" <RGagat6314@aol.com>, - "Ford, Bobby & Mauren" <bobf@erols.com>, - "Fontaine, George & Jo" <fontneg@comcast.net>, - "Flannery Bill" <wflannery@anteon.com>, "Fini Bob & Beth" <rfini@erols.com>, - "Ferraro, Sonia & Jack" <soniaferraro@earthlink.com>, - "Ferraro, Jack & Sonia" <jpferraro@earthlink.net>, - "Farquhar Butch & Rosa" <afarquhar8@comcast.net>, - "Egitto, John & Ann" <egittos@yahoo.com>, - "Economou, Tina" <annenick@erols.com>, - "Drummond, Scott" <drummond.scott@verizon.net>, - "Drummond, Cheryl" <cheryl.drummond@verizon.net>, - "Dennin Bob & Mary Jane" <rdennin@aol.com>, - "Daudet, Darryl & Jean" <dkdaudet@aol.com>, - "Dale Charles" <Cdale@erols.com>, - "Conde, Norman & Josephine" <nconde@comcast.net>, - "Colgan, Charles" <charlescolgan@colganair.com>, - "Clarke Russ & Pat" <clarkert@comcast.net>, - "Charters, Nikki" <fitzfam@starpower.net>, - "Carta, Mike & Sallie" <mcrt8@cs.com>, - "Carroll, Pat & Debbie" <dpcarroll981@aol.com>, - "Capozoli, Tom" <GoogCapo@aol.com>, "Capozoli, Patty" <pbcapo@aol.com>, - "Campbell Michael" <campbells.manassas@comcast.net>, - "Callahan, Bob & Marge" <yankeeinva@juno.com>, - "Byrne, Paul" <byrnemed@home.com>, "Byrne Kevin" <kevin.byrne@eds.com>, - "Broad, Brian & Brenda" <pimpchoir@yahoo.com>, - "Brien, Hugh & Ann" <hbrien@aol.com>, - "Breault, Mike & Katy" <dopeyoo1914@cs.com>, - "Branigan Chris & Trish" <branig9000@cs.com>, - "Bland, John & Kerry" <theblands_2000@yahoo.com>, - "Berczek, Sr., John & Virginia" <yorksr@cs.com>, - "Barta, Lee" <leebarta@erols.com>, "Ball, Ken" <cannon-ball@juno.com>, - "Aveni, Marc & Martha" <maveni@vt.edu>, - "Aveni, Fred & Judy" <jaaveni@aol.com>, - "Arseneault, Joe & Jane" <arseneault_joe@msn.com>, - "Alzona, Conrad" <rocon@juno.com>, "Aleksy, Rich & Agnes" <rswa@att.net>, - "Sebranek, Lyle & Donna" <sebrenek_lyle@hotmail.com>, - "Thompson, Dan & Jan" <DST@tgccpa.com>, "Shipko, Dan" <tasdjs@get.net>, - "Robbins, Cecil" <bgj4981@netzero.net>, - "Pogash, John" <gotfins2lft@aol.com>, "Mcormack, Pat" <pampam@erols.com>, - "Mayorga, Sergio" <m2rau@aol.com>, "Marrin, Bill" <marrin123@aol.com>, - "Jacobeen, David" <jacobeen@ieee.org>, "Italion" <italstalon@aol.com>, - "Grieshaber, Jim" <jrgrieshaber@fcps.edu>, - "Corbo, Tony" <tony_corbo@yahoo.com>, "Blank, Bryan" <BEBonYoder@msn.com>, - "Blank, Alaina" <LannieRae@msn.com>, - "Webb, Scott & Jenine" <thecashstore@hotmail.com>, - "Webb, Scott & Jenine" <jeninewebb@hotmail.com>, - "Gillespie, Erik" <bigdaddyebg@yahoo.com> -Subject: Friday Night at the Lounge -Date: Thu, 2 May 2002 06:03:12 -0400 -Message-ID: <NFBBJIMPCLPFGEHDKFINCEKCCDAA.fjoneill@erols.com> -MIME-Version: 1.0 -Content-Type: multipart/alternative; - boundary="----=_NextPart_000_0002_01C1F19F.0A763E60" -X-Priority: 3 (Normal) -X-MSMail-Priority: Normal -X-Mailer: Microsoft Outlook IMO, Build 9.0.2416 (9.0.2911.0) -Importance: Normal -X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2600.0000 - -This is a multi-part message in MIME format. - -------=_NextPart_000_0002_01C1F19F.0A763E60 -Content-Type: text/plain; - charset="iso-8859-1" -Content-Transfer-Encoding: 8bit - -�FRIDAY NIGHT AT THE GEORGE BRENT LOUNGE� -The Lounge will be open this Friday, May 3rd. -From 5 till 11 PM -It will be staffed by the George Brent Squires -and the George Brent Squire Roses - -Dave Riley will be doing the bar honors -Mary O�Neill working her magic in the kitchen -MENU: -Polish Sausage w/Sauerkraut on a bun -with Potato Salad -or -Hot Wings (6) w/ Celery Sticks & Blue Cheese Dressing -Also available: Home made Pickled Eggs - -For Kids -Chicken Nuggets & Tater Tots - -There will be a raffle for a Relay-For-Life -TV and Folding Chair - - - - - -------=_NextPart_000_0002_01C1F19F.0A763E60 -Content-Type: text/html; - charset="iso-8859-1" -Content-Transfer-Encoding: quoted-printable - -<html xmlns:o=3D"urn:schemas-microsoft-com:office:office" = -xmlns:w=3D"urn:schemas-microsoft-com:office:word" = -xmlns=3D"http://www.w3.org/TR/REC-html40"> - -<head> -<meta http-equiv=3DContent-Type content=3D"text/html; = -charset=3Diso-8859-1"> -<meta name=3DProgId content=3DWord.Document> -<meta name=3DGenerator content=3D"Microsoft Word 9"> -<meta name=3DOriginator content=3D"Microsoft Word 9"> -<link rel=3DFile-List href=3D"cid:filelist.xml@01C1F19F.0958E780"> -<!--[if gte mso 9]><xml> - <o:OfficeDocumentSettings> - <o:DoNotRelyOnCSS/> - </o:OfficeDocumentSettings> -</xml><![endif]--><!--[if gte mso 9]><xml> - <w:WordDocument> - <w:View>Normal</w:View> - <w:Zoom>0</w:Zoom> - <w:DocumentKind>DocumentEmail</w:DocumentKind> - <w:EnvelopeVis/> - </w:WordDocument> -</xml><![endif]--> -<style> -<!-- - /* Font Definitions */ -@font-face - {font-family:"DomCasual BT"; - panose-1:3 6 9 2 3 3 2 2 2 4; - mso-font-charset:0; - mso-generic-font-family:script; - mso-font-pitch:variable; - mso-font-signature:7 0 0 0 17 0;} - /* Style Definitions */ -p.MsoNormal, li.MsoNormal, div.MsoNormal - {mso-style-parent:""; - margin:0in; - margin-bottom:.0001pt; - mso-pagination:widow-orphan; - font-size:12.0pt; - font-family:"Times New Roman"; - mso-fareast-font-family:"Times New Roman";} -p.MsoAutoSig, li.MsoAutoSig, div.MsoAutoSig - {margin:0in; - margin-bottom:.0001pt; - mso-pagination:widow-orphan; - font-size:12.0pt; - font-family:"Times New Roman"; - mso-fareast-font-family:"Times New Roman";} -span.EmailStyle15 - {mso-style-type:personal-compose; - mso-ansi-font-size:10.0pt; - mso-ascii-font-family:Arial; - mso-hansi-font-family:Arial; - mso-bidi-font-family:Arial; - color:black;} -span.EmailStyle17 - {mso-style-type:personal; - mso-ansi-font-size:10.0pt; - mso-ascii-font-family:Arial; - mso-hansi-font-family:Arial; - mso-bidi-font-family:Arial; - color:black;} -@page Section1 - {size:8.5in 11.0in; - margin:1.0in 1.25in 1.0in 1.25in; - mso-header-margin:.5in; - mso-footer-margin:.5in; - mso-paper-source:0;} -div.Section1 - {page:Section1;} ---> -</style> -</head> - -<body lang=3DEN-US style=3D'tab-interval:.5in'> - -<div class=3DSection1> - -<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span -class=3DEmailStyle17><b><font size=3D5 color=3Dred face=3D"DomCasual = -BT"><span -style=3D'font-size:18.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua= -l BT"; -color:red;font-weight:bold'>“FRIDAY NIGHT AT THE GEORGE BRENT = -LOUNGE”<o:p></o:p></span></font></b></span></p> - -<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span -class=3DEmailStyle17><b><font size=3D5 color=3Dred face=3D"DomCasual = -BT"><span -style=3D'font-size:18.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua= -l BT"; -color:red;font-weight:bold'>The Lounge will be open this Friday, May = -3<sup>rd</sup>.<o:p></o:p></span></font></b></span></p> - -<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span -class=3DEmailStyle17><b><font size=3D5 color=3Dred face=3D"DomCasual = -BT"><span -style=3D'font-size:18.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua= -l BT"; -color:red;font-weight:bold'>From 5 till 11 = -PM<o:p></o:p></span></font></b></span></p> - -<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span -class=3DEmailStyle17><b><font size=3D5 color=3Dred face=3D"DomCasual = -BT"><span -style=3D'font-size:18.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua= -l BT"; -color:red;font-weight:bold'>It will be staffed by the George Brent = -Squires<o:p></o:p></span></font></b></span></p> - -<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span -class=3DEmailStyle17><b><font size=3D5 color=3Dred face=3D"DomCasual = -BT"><span -style=3D'font-size:18.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua= -l BT"; -color:red;font-weight:bold'>and the George Brent Squire = -Roses<o:p></o:p></span></font></b></span></p> - -<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span -class=3DEmailStyle17><b><font size=3D5 color=3Dred face=3D"DomCasual = -BT"><span -style=3D'font-size:18.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua= -l BT"; -color:red;font-weight:bold'> <o:p></o:p></span></font></b></span></p= -> - -<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span -class=3DEmailStyle17><b><font size=3D5 color=3Dred face=3D"DomCasual = -BT"><span -style=3D'font-size:18.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua= -l BT"; -color:red;font-weight:bold'>Dave Riley will be doing the bar = -honors<o:p></o:p></span></font></b></span></p> - -<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span -class=3DEmailStyle17><b><font size=3D5 color=3Dred face=3D"DomCasual = -BT"><span -style=3D'font-size:18.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua= -l BT"; -color:red;font-weight:bold'>Mary O’Neill working her magic in the = -kitchen<o:p></o:p></span></font></b></span></p> - -<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span -class=3DEmailStyle17><b><u><font size=3D5 color=3Dblue face=3D"DomCasual = -BT"><span -style=3D'font-size:18.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua= -l BT"; -color:blue;font-weight:bold'>MENU:<o:p></o:p></span></font></u></b></span= -></p> - -<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span -class=3DEmailStyle17><b><font size=3D5 color=3Dblue face=3D"DomCasual = -BT"><span -style=3D'font-size:16.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua= -l BT"; -color:blue;font-weight:bold'>Polish Sausage w/Sauerkraut on a = -bun<o:p></o:p></span></font></b></span></p> - -<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span -class=3DEmailStyle17><b><font size=3D5 color=3Dblue face=3D"DomCasual = -BT"><span -style=3D'font-size:16.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua= -l BT"; -color:blue;font-weight:bold'>with Potato Salad<span = -style=3D"mso-spacerun: -yes"> </span><o:p></o:p></span></font></b></span></p> - -<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span -class=3DEmailStyle17><b><font size=3D5 color=3Dred face=3D"DomCasual = -BT"><span -style=3D'font-size:16.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua= -l BT"; -color:red;font-weight:bold'>or<o:p></o:p></span></font></b></span></p> - -<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span -class=3DEmailStyle17><b><font size=3D5 color=3Dblue face=3D"DomCasual = -BT"><span -style=3D'font-size:16.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua= -l BT"; -color:blue;font-weight:bold'>Hot Wings (6) w/ Celery Sticks & Blue = -Cheese -Dressing<o:p></o:p></span></font></b></span></p> - -<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span -class=3DEmailStyle17><b><font size=3D5 color=3Dblue face=3D"DomCasual = -BT"><span -style=3D'font-size:16.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua= -l BT"; -color:blue;font-weight:bold'>Also available: Home made Pickled = -Eggs<o:p></o:p></span></font></b></span></p> - -<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span -class=3DEmailStyle17><b><font size=3D5 color=3Dred face=3D"DomCasual = -BT"><span -style=3D'font-size:16.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua= -l BT"; -color:red;font-weight:bold'><![if = -!supportEmptyParas]> <![endif]><o:p></o:p></span></font></b></span><= -/p> - -<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span -class=3DEmailStyle17><b><font size=3D5 color=3Dred face=3D"DomCasual = -BT"><span -style=3D'font-size:16.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua= -l BT"; -color:red;font-weight:bold'>For = -Kids<o:p></o:p></span></font></b></span></p> - -<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span -class=3DEmailStyle17><b><font size=3D5 color=3Dblue face=3D"DomCasual = -BT"><span -style=3D'font-size:16.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua= -l BT"; -color:blue;font-weight:bold'>Chicken Nuggets & Tater = -Tots<o:p></o:p></span></font></b></span></p> - -<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span -class=3DEmailStyle17><b><font size=3D5 color=3Dblue face=3D"DomCasual = -BT"><span -style=3D'font-size:16.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua= -l BT"; -color:blue;font-weight:bold'><![if = -!supportEmptyParas]> <![endif]><o:p></o:p></span></font></b></span><= -/p> - -<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span -class=3DEmailStyle17><b><font size=3D5 color=3Dblue face=3D"DomCasual = -BT"><span -style=3D'font-size:16.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua= -l BT"; -color:blue;font-weight:bold'>There will be a raffle for a Relay-For-Life = -<o:p></o:p></span></font></b></span></p> - -<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span -class=3DEmailStyle17><b><font size=3D5 color=3Dblue face=3D"DomCasual = -BT"><span -style=3D'font-size:16.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua= -l BT"; -color:blue;font-weight:bold'>TV and Folding = -Chair</span></font></b></span><font -size=3D2 color=3Dred face=3D"Courier New"><span = -style=3D'font-size:10.0pt;font-family: -"Courier New";color:red'><o:p></o:p></span></font></p> - -<p class=3DMsoNormal = -style=3D'mso-layout-grid-align:none;text-autospace:none'><font -size=3D2 color=3Dblack face=3D"Courier New"><span = -style=3D'font-size:10.0pt;font-family: -"Courier New";color:black'><![if = -!supportEmptyParas]> <![endif]></span></font><font -size=3D2 color=3Dblack face=3D"Courier New"><span = -style=3D'font-size:10.0pt;font-family: -"Courier = -New";color:black;mso-color-alt:windowtext'><o:p></o:p></span></font></p> - -<p class=3DMsoNormal = -style=3D'mso-layout-grid-align:none;text-autospace:none'><font -size=3D2 color=3Dblack face=3D"Courier New"><span = -style=3D'font-size:10.0pt;font-family: -"Courier New";color:black'><![if = -!supportEmptyParas]> <![endif]></span></font><font -size=3D2 color=3Dblack face=3D"Courier New"><span = -style=3D'font-size:10.0pt;font-family: -"Courier = -New";color:black;mso-color-alt:windowtext'><o:p></o:p></span></font></p> - -<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span -class=3DEmailStyle17><b><font size=3D5 color=3Dblue face=3D"DomCasual = -BT"><span -style=3D'font-size:16.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua= -l BT"; -color:blue;font-weight:bold'><![if = -!supportEmptyParas]> <![endif]><o:p></o:p></span></font></b></span><= -/p> - -<p class=3DMsoNormal><span class=3DEmailStyle15><font size=3D2 = -color=3Dblack -face=3DArial><span = -style=3D'font-size:10.0pt;mso-bidi-font-size:12.0pt;font-family: -Arial'><![if = -!supportEmptyParas]> <![endif]><o:p></o:p></span></font></span></p> - -</div> - -</body> - -</html> - -------=_NextPart_000_0002_01C1F19F.0A763E60-- - - diff --git a/test/spam7 b/test/spam7 deleted file mode 100644 index ef6cb02..0000000 --- a/test/spam7 +++ /dev/null @@ -1,30 +0,0 @@ -Received: from mail pickup service by hotmail.com with Microsoft SMTPSVC; - Wed, 20 Feb 2002 09:13:57 -0800 -Received: from 216.144.70.231 by lw7fd.law7.hotmail.msn.com with HTTP; - Wed, 20 Feb 2002 17:13:44 GMT -X-Originating-IP: [216.144.70.231] -From: "jim simmons" <jimabides@hotmail.com> -Bcc: -Subject: Just another "Crappy Day in Paradise" here @ the Ranch -Date: Wed, 20 Feb 2002 10:13:44 -0700 -Mime-Version: 1.0 -Content-Type: multipart/mixed; boundary="----=_NextPart_000_4e56_490d_48e3" -Message-ID: <F251n1gLtuUtVSMp2uu0000a344@hotmail.com> -X-OriginalArrivalTime: 20 Feb 2002 17:13:57.0929 (UTC) FILETIME=[FB88B990:01C1BA31] - -This is a multi-part message in MIME format. - -------=_NextPart_000_4e56_490d_48e3 -Content-Type: text/html - -<html> <body> Test </body> </html> -------=_NextPart_000_4e56_490d_48e3 -Content-Type: image/pjpeg; name="Jim&amp;Girlz.jpg" -Content-Transfer-Encoding: base64 -Content-Disposition: attachment; filename="Jim&amp;Girlz.jpg" - -/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAoHBwgHBgoICAgLCgoLDhgQDg0N -UUUAFFFFABRRRQB//9k= - - -------=_NextPart_000_4e56_490d_48e3-- diff --git a/test/spam8 b/test/spam8 deleted file mode 100644 index 2b2df52..0000000 --- a/test/spam8 +++ /dev/null @@ -1,221 +0,0 @@ -Received: from mail.pro-send.com (smtp12.pro-send.com [65.124.197.229]) - by www.bmsi.com (8.12.3/8.12.3) with ESMTP id g927mSVA017008 - for <lindsays@dflinc.com>; Wed, 2 Oct 2002 03:48:29 -0400 -Received: from pro-send.com [65.124.197.226] by mail.pro-send.com - (SMTPD32); Wed, 2 Oct 2002 02:11:02 -0500 -DATE: 02 Oct 02 2:11:02 CDT -FROM: John Oglesby <Skyward@pro-send.com> -Reply-To: John Oglesby <skyward@concordebuddy.com> -TO: Lindsay Shrader <lindsays@dflinc.com> -SUBJECT: Lindsay Shrader -Message-Id: <2002100202.RS11@mail.pro-send.com> -MIME-Version: 1.0 -Content-Type: multipart/alternative; boundary=1002029 - ---1002029 -Content-Type: text/plain; charset=us-ascii - - - - - - -A SYSTEM for FREEDOM - - - - -Don't call in Sick... - - Call in WELL... Extremely Well! - - - -If -you want to see how, Click Here. - -Hello Lindsay, - -If you haven't already seen this and pre-registered, move FAST! -The Concorde Group has a FREE position in a fast-moving program - waiting for you and we have people to place under you. - -We'll notify you when you have a CHECK WAITING. - -This FREE position is waiting for Lindsay Shrader. - - - -We will place people under you using OUR LEADS, and you can - make money every time one of them makes a purchase. - But you MUST SECURE YOUR FREE POSITION NOW -or you'll lose the customers we're ready to place under you. -Click Here http://www.pro-send.com/proform_process.asp?code=Y474878338EC95487A11 - -By registering Lindsay Shrader today and taking a FREE TOUR, you - will secure your position with absolutely NO RISK. - -Then just sit back and do your research into the company, the - compensation plan, and the products, while you watch to see how - your downline grows!! - -Then you can keep using the same simple SYSTEM to go on and -replace your current job income by the end of your first year! - Take Your Free Tour Now: -Click Here http://www.pro-send.com/proform_process.asp?code=Y474878338EC95487A11 -Yours in Success, - -John Oglesby -joglesby2@msn.com -1+(877)-868-0143 -Home 972-878-2683 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -HOW DID WE LEARN ABOUT YOUR INTEREST IN A HOME-BASED BUSINESS? - -You responded to one of our ads. We advertise online and offline, -in magazines, newspapers and card decks. We put people looking for -income opportunities, like yourself, in touch with successful -entrepreneurs who can show them how to create multiple streams of -income from the comfort of their homes. Hopefully that answers your -question. - -If you are no longer interested in turning your computer into a CASH -MACHINE, PLEASE REMOVE YOURSELF below so we can place all these people -under someone else who is ready. - - - - -____________________________________________________________ -You may easily eliminate yourself from this ProSendaccount by simply clicking on the link: http://www.pro-send.com/x/?6C6938E41D1OR go to: http://www.pro-send.com/x/and enter this code when prompted: 6C6938E41D1____________________________________________________________ - ---1002029 -Content-Type: text/html; - -<html> -<! -Don't call in Sick... - -Call in WELL... Extremely Well! - -Lindsay, - -If you haven't already seen this and pre-registered, move FAST! - -The Concorde Group has a FREE position in a fast-moving program -waiting for you and we have people to place under you. - -We'll notify you when you have a CHECK WAITING. - -This FREE position is waiting for Lindsay Shrader. - -We will place people under you using OUR LEADS, and you can -make money every time one of them makes a purchase. -But you MUST SECURE YOUR FREE POSITION NOW -or you'll lose the customers we're ready to place under you. -> -<! <a href="http://www.pro-send.com/proform_process.asp?code=Y474878338EC95487A11"><!Click Here http://www.pro-send.com/proform_process.asp?code=Y474878338EC95487A11</A><! - -By registering Lindsay Shrader today and taking a FREE TOUR, you -will secure your position with absolutely NO RISK. - -Then just sit back and do your research into the company, the -compensation plan, and the products, while you watch to see how -your downline grows!! - -Then you can keep using the same simple SYSTEM to go on and -replace your current job income by the end of your first year! - -Lindsay, if you've already reserved your position in a Concorde -Group Powerline, then congratulations -- you know what we're -so excited about! - -If not, Click Here Now for Your Free Tour: -> -<! <a href="http://www.pro-send.com/proform_process.asp?code=Y474878338EC95487A11"><!Click Here http://www.pro-send.com/proform_process.asp?code=Y474878338EC95487A11</A><! - -Yours in Success, - -John Oglesby -joglesby2@msn.com -1+(877)-868-0143 -Home 972-878-2683 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -HOW DID WE LEARN ABOUT YOUR INTEREST IN A HOME-BASED BUSINESS? - -You responded to one of our ads. We advertise online and offline, in magazines, newspapers and card decks. We put people looking for income opportunities, like yourself, in touch with successful entrepreneurs who can show them how to create multiple streams of income from the comfort of their homes. Hopefully that answers your question. - -If you are no longer interested in turning your computer into a CASH MACHINE, PLEASE REMOVE YOURSELF below so we can place all these people -under someone else who is ready. - -> -<head> -<title>A SYSTEM for FREEDOM</title> -</head> -<body> - -<p align="left"><font face="Verdana"><b> -Don't call in Sick...<br> -<br> - Call in WELL... Extremely Well!</b></font></p> -<p><font face="Verdana" size="2"><a href="http://www.pro-send.com/proform_process.asp?code=Y474878338EC95487A11"><img border="0" src="http://www.breakfree2000.com/FreeManOnBeach.jpg" alt="Click Here" width="436" height="228"></a> <br> -<br> - -<b><a href="http://www.pro-send.com/proform_process.asp?code=Y474878338EC95487A11" target="_blank">If -you want to see how, Click Here.</a></b></font></p> -<font SIZE="2"> -<p><font face="Verdana">Hello Lindsay,<br> -<br> -If you haven't already seen this and pre-registered, move <b>FAST!</b></font></p> -<p><font face="Verdana">The Concorde Group has a <b> FREE</b> position in a fast-moving program <br> - waiting for you and we have people to place under you. <br> -<br> -We'll notify you when you have a <b> CHECK WAITING.</b> <br> -<br> -This <b> FREE</b> position is waiting for <b> Lindsay Shrader</b>. -</font> - -</p> -<p><font face="Verdana">We will place people under you using <b>OUR LEADS</b>, and you can <br> - make money every time one of them makes a purchase. <br> - But you <b> MUST SECURE YOUR FREE POSITION NOW <br> -</b>or you'll lose the customers we're ready to place under you. </p> -<p><a href="http://www.pro-send.com/proform_process.asp?code=Y474878338EC95487A11"><b>Click Here http://www.pro-send.com/proform_process.asp?code=Y474878338EC95487A11</b></a><br> -<br> -By registering <b> Lindsay Shrader</b> today and taking a <b> FREE TOUR</b>, you <br> - will secure your position with absolutely <b> NO RISK</b>.<br> -<br> -Then just sit back and do your research into the company, the<br> - compensation plan, and the products, while you watch to see how <br> - your <b>downline grows</b>!!</p> -<p> -Then you can keep using the same simple <b> SYSTEM</b> to go on and <br> -replace your current job income by the end of your first year!</p> -<p> Take <b>Your Free Tour</b> Now:<br> -<a href="http://www.pro-send.com/proform_process.asp?code=Y474878338EC95487A11"><b>Click Here http://www.pro-send.com/proform_process.asp?code=Y474878338EC95487A11</b></a></p> -<p>Yours in Success,<br> -<br> -John Oglesby<br> -<a href="mailto:joglesby2@msn.com">joglesby2@msn.com</a><br> -1+(877)-868-0143<br> -Home 972-878-2683<br> -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~<br> -<font face="Verdana" size="2">HOW DID WE LEARN ABOUT YOUR INTEREST IN A HOME-BASED BUSINESS?<br> -<br> -You responded to one of our ads. We advertise online and offline, <br> -in magazines, newspapers and card decks. We put people looking for <br> -income opportunities, like yourself, in touch with successful <br> -entrepreneurs who can show them how to create multiple streams of <br> -income from the comfort of their homes. Hopefully that answers your <br> -question.<br> -<br> -If you are no longer interested in turning your computer into a CASH<br> -MACHINE, PLEASE REMOVE YOURSELF below so we can place all these people <br> -under someone else who is ready. </font></p> - -</body> - -</html> -<br><br><font style=font-size:12px>____________________________________________________________<br> -<br>You may easily eliminate yourself from this ProSend<br>account by simply clicking on the link: <br><A href="http://www.pro-send.com/x/?6C6938E41D1">http://www.pro-send.com/x/?6C6938E41D1</A><br>OR go to: <br><A href="http://www.pro-send.com/x/">http://www.pro-send.com/x/</A><br>and enter this code when prompted: 6C6938E41D1<br>____________________________________________________________</font> ---1002029-- diff --git a/test/test1 b/test/test1 deleted file mode 100644 index ac55663..0000000 --- a/test/test1 +++ /dev/null @@ -1,61 +0,0 @@ -From kinga.huszka@wellsfargo.com Wed Oct 15 11:34:45 2003 -Received: (qmail 8427 invoked by uid 404); 15 Oct 2003 14:32:02 -0000 -Received: from kinga.huszka@aesfargo.com by coyote.nextra.hu by uid 401 with qmail-scanner-1.15 - (Clear:. - Processed in 3.378056 secs); 15 Oct 2003 14:32:02 -0000 -Received: from adsl9.adsl.nextra.hu (HELO marcus.movemany.info) (213.134.24.9) - by 0 with SMTP; 15 Oct 2003 14:31:58 -0000 -Received: from [192.168.1.12] (cargo2.movemany.info [192.168.1.12]) - by marcus.movemany.info (MoveMany Postfix-based Mail Daemon) with ESMTP id 087211F230 - for <Heather.Lammy@mulan.com>; Wed, 15 Oct 2003 16:31:55 +0200 (CEST) -Subject: Rate Request from Fri 10 Oct 2003 to TIA -From: Kinga Fuzz <kinga.huszka@wellsfargo.com> -To: World Transportation Systems / Heather Lammy <Heather.Lammy@mulan.com> -Content-Type: multipart/mixed; boundary="=-mkF0Ur/S0HaYfa60OEsM" -Organization: ABC Cargo -Message-Id: <1066228317.986.549.camel@cargo2> -Mime-Version: 1.0 -X-Mailer: Ximian Evolution 1.2.4 -Date: 15 Oct 2003 16:31:57 +0200 - - ---=-mkF0Ur/S0HaYfa60OEsM -Content-Type: multipart/alternative; boundary="=-VowfKaQaEHb81enMCUlR" - - ---=-VowfKaQaEHb81enMCUlR -Content-Type: text/plain -Content-Transfer-Encoding: 7bit - -Dear Heather, - - -First of all, I would like to ask you to send your emails to our general - email and its associated attachments is strictly prohibited. - ---=-VowfKaQaEHb81enMCUlR -Content-Type: text/html; charset=utf-8 -Content-Transfer-Encoding: 7bit - -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 TRANSITIONAL//EN"> -<HTML> -<HEAD> - <META HTTP-EQUIV="Content-Type" CONTENT="text/html; CHARSET=UTF-8"> - <META NAME="GENERATOR" CONTENT="GtkHTML/1.1.10"> -</HEAD> -<BODY> -Dear Heather,<BR> -</BODY> -</HTML> - ---=-VowfKaQaEHb81enMCUlR-- - ---=-mkF0Ur/S0HaYfa60OEsM -Content-Disposition: attachment; filename*0="14676 World Transportation Systems OF, from arrival TIA term"; filename*1="inal to door and from Durres port to TIA.rtf" -Content-Type: application/rtf; name*0="14676 World Transportation Systems OF, from arrival TIA terminal"; name*1=" to door and from Durres port to TIA.rtf" -Content-Transfer-Encoding: 7bit - -{\rtf1\ansi\deff1\adeflang1025 -\par } ---=-mkF0Ur/S0HaYfa60OEsM-- - diff --git a/test/test8 b/test/test8 deleted file mode 100644 index 83fed4b..0000000 --- a/test/test8 +++ /dev/null @@ -1,118 +0,0 @@ -Received: from mail pickup service by hotmail.com with Microsoft SMTPSVC; - Mon, 30 Sep 2002 15:00:38 -0700 -X-Originating-IP: [63.157.17.3] -From: "Debbie Morrison" <fmmorrison@msn.com> -To: "Ann & Richard Black" <AnnTBlack@aol.com>, - "Bill/Dorothy" <billcampbell2@attbi.com>, - "Cindy Kohr" <bosslady54@go.com>, - "Debbie Morrison" <debbiem@dflinc.com>, - "DONNA MORRISON" <DMORR42886@AOL.COM>, - "Glenda/Johnny Holmes" <glendaholmes@hotmail.com>, - "HAROLDMAXINE STROUD" <TO.THE.MAX@ATT.NET>, - "Janis & Bob Mathis" <teammathis@onebox.com>, - "Sherry Bigham" <Bighams@lisd.net>, - "Mark Bigham" <bigham11@swbell.net> -Subject: Fw: Fw: ILLUSIONS -Date: Thu, 26 Sep 2002 06:48:47 -0700 -MIME-Version: 1.0 -X-Mailer: MSN Explorer 7.02.0005.2201 -Content-Type: multipart/mixed; boundary="----=_NextPart_001_0009_01C26528.C39C68E0" -Message-ID: <OE142r27kicP3lh9uKw0000a7a1@hotmail.com> -X-OriginalArrivalTime: 30 Sep 2002 22:00:38.0335 (UTC) FILETIME=[CF7AE4F0:01C268CC] -X-IMAPbase: 1033583964 1 -Status: RO -X-Status: -X-Keywords: -X-UID: 1 - - -------=_NextPart_001_0009_01C26528.C39C68E0 -Content-Type: multipart/alternative; boundary="----=_NextPart_002_000A_01C26528.C39C68E0" - - -------=_NextPart_002_000A_01C26528.C39C68E0 -Content-Type: text/plain; charset="iso-8859-1" -Content-Transfer-Encoding: quoted-printable - -Keep opening on the forwards. Cool =20 - =20 ------ Original Message ----- -From: Got2Fish42@aol.com -Sent: Tuesday, September 24, 2002 3:16 PM -To: dugiew@cox-internet.com; txnrnt@yahoo.com; mbrock@tstar.net; DendyDl@= -swbell.net; sdickey@att.net; deasley@vzinet.com; fmmorrison@msn.com; mama= -jack4@juno.com; DMorr42886@aol.com; LStra415@aol.com; wrwebster@juno.com;= - GWIL@tjc.edu -Subject: Fwd: Fw: ILLUSIONS - Get more from the Web. FREE MSN Explorer download : http://explorer.msn= -.com - -------=_NextPart_002_000A_01C26528.C39C68E0 -Content-Type: text/html; charset="iso-8859-1" -Content-Transfer-Encoding: quoted-printable - -<HTML><BODY STYLE=3D"font:10pt verdana; border:none;"><DIV>Keep opening o= -n the forwards. Cool </DIV> <DIV> </DIV> <BLOCKQUOTE styl= -e=3D"PADDING-RIGHT: 0px; PADDING-LEFT: 5px; MARGIN-LEFT: 5px; BORDER-LEFT= -: #000000 2px solid; MARGIN-RIGHT: 0px"> <DIV style=3D"FONT: 10pt Arial">= ------ Original Message -----</DIV> <DIV style=3D"BACKGROUND: #e4e4e4; FON= -T: 10pt Arial; COLOR: black"><B>From:</B> Got2Fish42@aol.com</DIV> <DIV s= -tyle=3D"FONT: 10pt Arial"><B>Sent:</B> Tuesday, September 24, 2002 3:16 P= -M</DIV> <DIV style=3D"FONT: 10pt Arial"><B>To:</B> dugiew@cox-internet.co= -m; txnrnt@yahoo.com; mbrock@tstar.net; DendyDl@swbell.net; sdickey@att.ne= -t; deasley@vzinet.com; fmmorrison@msn.com; mamajack4@juno.com; DMorr42886= -@aol.com; LStra415@aol.com; wrwebster@juno.com; GWIL@tjc.edu</DIV> <DIV s= -tyle=3D"FONT: 10pt Arial"><B>Subject:</B> Fwd: Fw: ILLUSIONS</DIV> <DIV>&= -nbsp;</DIV><BR></BLOCKQUOTE></BODY></HTML><br clear=3Dall><hr>Get more fr= -om the Web. FREE MSN Explorer download : <a href=3D'http://explorer.msn.= -com'>http://explorer.msn.com</a><br></p> - -------=_NextPart_002_000A_01C26528.C39C68E0-- - - -------=_NextPart_001_0009_01C26528.C39C68E0 -Content-Type: message/rfc822; name="Fwd_ Fw_ ILLUSIONS.email" -Content-Disposition: attachment; filename="Fwd_ Fw_ ILLUSIONS.email" -Content-Transfer-Encoding: quoted-printable - -Return-path: <Bclc48@aol.com> -From: Bclc48@aol.com -Full-name: Bclc48 -Message-ID: <42.2de5cbf8.2ac10b39@aol.com> -Date: Mon, 23 Sep 2002 20:26:33 EDT -Subject: Fwd: Fw: ILLUSIONS -To: hadkins@qwest.net, Bardojm@aol.com, swa_tom@swbell.net, - eve@mixedmediaoutdoor.com, ArthurJaharris11@aol.com, - j.gual@worldnet.att.net, JOSEFUR@cs.com, AR2976@aol.com, CCcaro@aol.com, - Zgirlnan@aol.com, Got2Fish42@aol.com -MIME-Version: 1.0 -Content-Type: multipart/mixed; boundary=3D"part2_46.2e38b118.2ac10b39_bou= -ndary" -X-Mailer: AOL 7.0 for Windows US sub 10641 - - ---part2_46.2e38b118.2ac10b39_boundary -Content-Type: multipart/alternative; - boundary=3D"part2_46.2e38b118.2ac10b39_alt_boundary" - - ---part2_46.2e38b118.2ac10b39_alt_boundary -Content-Type: text/plain; charset=3D"US-ASCII" -Content-Transfer-Encoding: 7bit - -this is good - ---part2_46.2e38b118.2ac10b39_alt_boundary -Content-Type: text/html; charset=3D"US-ASCII" -Content-Transfer-Encoding: 7bit - -<HTML><FONT FACE=3Darial,helvetica><BODY BGCOLOR=3D"#ffffff"><SCRIPT style= -=3D"BACKGROUND-COLOR: #ffffff" SIZE=3D2 FAMILY=3D"SANSSERIF" FACE=3D"Aria= -l" LANG=3D"0">this is good</SCRIPT></HTML> - ---part2_46.2e38b118.2ac10b39_alt_boundary-- - ---part2_46.2e38b118.2ac10b39_boundary-- - -------=_NextPart_001_0009_01C26528.C39C68E0-- - diff --git a/test/virus1 b/test/virus1 deleted file mode 100644 index fae1949..0000000 --- a/test/virus1 +++ /dev/null @@ -1,72 +0,0 @@ -Received: from www.bmsi.com (bmsweb.bmsi.com [219.109.11.130]) - by bmsaix.bmsi.com (8.9.1/8.9.1) with ESMTP id FAA42304 - for <ed@bmsi.com>; Thu, 4 May 2000 05:22:03 -0400 -Received: from camco.celestial.com (root@dagney.celestial.com [192.136.111.7]) - by www.bmsi.com (8.9.1/8.9.1) with ESMTP id FAA21364 - for <ed@bmsi.com>; Thu, 4 May 2000 05:22:01 -0400 -Received: (12482 bytes) by camco.celestial.com - via sendmail with P:stdio/D:lists/R:inet_hosts/T:smtp - (sender: <owner-flexfax@celestial.com> owner: <owner-flexfax-outbound>) - id <m12nHjG-000eNHa@camco.celestial.com> - for flexfax-outbound; Thu, 4 May 2000 02:15:30 -0700 (PDT) - (Smail-3.2.0.111 2000-Feb-17 #1 built 2000-Apr-13) -Received: from sgi.com(sgi.SGI.COM[192.48.153.1]) (12116 bytes) by camco.celestial.com - via sendmail with P:esmtp/D:aliases/T:pipe - (sender: <owner-flexfax@sgi.com> owner: <owner-flexfax>) - id <m12nHh6-000eN7C@camco.celestial.com> - for <flexfax@celestial.com>; Thu, 4 May 2000 02:13:16 -0700 (PDT) - (Smail-3.2.0.111 2000-Feb-17 #1 built 2000-Apr-13) -Received: from proxy.internet ([195.184.42.82]) - by sgi.com (980327.SGI.8.8.8-aspam/980304.SGI-aspam: - SGI does not authorize the use of its proprietary - systems or networks for unsolicited or bulk email - from the Internet.) - via ESMTP id CAA02330 - for <flexfax@sgi.com>; Thu, 4 May 2000 02:13:10 -0700 (PDT) - mail_from (orum@ditas.dk) -Received: from [172.16.96.14] by proxy.daab.dkproxy.internet (NTMail 4.30.0013/NU4152.00.32401f35) with ESMTP id zmlyaaaa for <flexfax@sgi.com>; Thu, 4 May 2000 11:13:09 +0200 -Received: by mars with Internet Mail Service (5.5.2650.21) - id <KGM63KG3>; Thu, 4 May 2000 11:11:13 +0100 -Message-ID: <9704D2AA604ED311BF6D0008C79F0A990B57BE@mars> -From: =?iso-8859-1?Q?Peter_=D8rum?= <orum@ditas.dk> -To: "'flexfax@sgi.com'" <flexfax@sgi.com> -Subject: flexfax: ILOVEYOU -Date: Thu, 4 May 2000 11:11:11 +0100 -MIME-Version: 1.0 -X-Mailer: Internet Mail Service (5.5.2650.21) -Content-Type: multipart/mixed; - boundary="----_=_NextPart_000_01BFB5B1.13228432" -Sender: owner-flexfax@celestial.com -Precedence: bulk - -This message is in MIME format. Since your mail reader does not understand -this format, some or all of this message may not be legible. - -------_=_NextPart_000_01BFB5B1.13228432 -Content-Type: text/plain - - -kindly check the attached LOVELETTER coming from me. - - -------_=_NextPart_000_01BFB5B1.13228432 -Content-Type: application/octet-stream; - name="LOVE-LETTER-FOR-YOU.TXT.vbs" -Content-Transfer-Encoding: quoted-printable -Content-Disposition: attachment; - filename="LOVE-LETTER-FOR-YOU.TXT.vbs" - -rem barok -loveletter(vbe) <i hate go to school> -rem by: spyder / ispyder@mail.com / @GRAMMERSoft Group / = -Manila,Philippines -On Error Resume Next -set b=3Dfso.CreateTextFile(dirsystem+"\LOVE-LETTER-FOR-YOU.HTM") -b.close -set d=3Dfso.OpenTextFile(dirsystem+"\LOVE-LETTER-FOR-YOU.HTM",2) -d.write dt5 -d.write join(lines,vbcrlf) -d.write vbcrlf -d.write dt6 -d.close -end sub -------_=_NextPart_000_01BFB5B1.13228432-- diff --git a/test/virus13 b/test/virus13 deleted file mode 100644 index 2c86c43..0000000 --- a/test/virus13 +++ /dev/null @@ -1,127 +0,0 @@ -Received: from www.bmsi.com (bmsweb.bmsi.com [219.109.11.130]) - by bmsaix.bmsi.com (8.12.3/8.12.2) with ESMTP id g41MmROS014480 - for <stuart@bmsi.com>; Wed, 1 May 2002 18:48:27 -0400 -Received: from bmsred.bmsi.com (bmsred [219.109.11.50]) - by www.bmsi.com (8.12.1/8.12.1) with ESMTP id g41MmFGR017812 - for <stuart@bmsi.com>; Wed, 1 May 2002 18:48:15 -0400 -X-Received: from www.bmsi.com (bmsweb.bmsi.com [219.109.11.130]) - by bmsaix.bmsi.com (8.12.3/8.12.2) with ESMTP id g41M3hOS038584 - for <ed@bmsi.com>; Wed, 1 May 2002 18:03:43 -0400 -X-Received: from exp.dflinc.com (exppub [12.148.147.210]) - by www.bmsi.com (8.12.1/8.12.1) with ESMTP id g41M3LGQ017812 - for <ed@bmsi.com>; Wed, 1 May 2002 18:03:22 -0400 -X-Received: from exp.dflinc.com (exp.dflinc.com [219.109.14.1]) - by exp.dflinc.com (8.12.1/8.12.1) with ESMTP id g41M3JGT012258 - for <ed@bmsi.com>; Wed, 1 May 2002 17:03:19 -0500 -X-Received: from dns.intervip.psi.br (dns.intervip.psi.br [200.215.126.2]) - by exp.dflinc.com (8.12.1/8.12.1) with ESMTP id g3NHlhGS032960 - for <lorraine@dflinc.com>; Tue, 23 Apr 2002 12:47:44 -0500 -X-Received: from Sncpyf (adsl-fnsbnu-055-k.brt.telesc.net.br [200.180.75.55]) - by dns.intervip.psi.br (Postfix) with SMTP id 1FAEE24D18 - for <lorraine@dflinc.com>; Tue, 23 Apr 2002 14:50:41 -0300 (BRT) -From: enardelli <enardelli@karsten.com.br> -To: lorraine@dflinc.com -Subject: A special powful tool -MIME-Version: 1.0 -Content-Type: multipart/alternative; - boundary=XQ4T5Cj14m5h2vQ69IpO4mCG -Message-Id: <20020423175041.1FAEE24D18@dns.intervip.psi.br> -Date: Tue, 23 Apr 2002 14:50:41 -0300 (BRT) -X-ReSent-Date: Wed, 1 May 2002 17:03:03 -0500 (CDT) -X-ReSent-From: Gwen Bartelle <gwenb@dflinc.com> -X-ReSent-To: ed@bmsi.com -X-ReSent-Subject: A special powful tool -X-ReSent-Message-ID: <Pine.A41.4.10.10205011703030.30638@exp.dflinc.com> -ReSent-Date: Wed, 1 May 2002 18:47:52 -0400 (EDT) -ReSent-From: Ed Bond <ed@bmsi.com> -ReSent-To: Stuart Gathman <stuart@bmsi.com> -ReSent-Subject: A special powful tool -ReSent-Message-ID: <Pine.LNX.4.44.0205011847520.17454@bmsred.bmsi.com> - ---XQ4T5Cj14m5h2vQ69IpO4mCG -Content-Type: text/html; -Content-Transfer-Encoding: quoted-printable - -<HTML><HEAD></HEAD><BODY> -<iframe src=3Dcid:Ux7VyFy7bTS9q height=3D0 width=3D0> -</iframe> -<FONT>Hi,This is a special powful tool<br> -I wish you would enjoy it.</FONT></BODY></HTML> - ---XQ4T5Cj14m5h2vQ69IpO4mCG -Content-Type: audio/x-midi; - name=hom1;tile=1;ord=3354010700499224[1].scr -Content-Transfer-Encoding: base64 -Content-ID: <Ux7VyFy7bTS9q> - -TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -CMDDePe/RHj3v5IT+r+Pe/e/kHr3v9Fv97/1Gfq/93H3v1Yc+r/3dve/oGj3v8sK+r+sx/e/ -Nyz5v7Hu+b98HD== ---XQ4T5Cj14m5h2vQ69IpO4mCG ---XQ4T5Cj14m5h2vQ69IpO4mCG -Content-Type: application/octet-stream; - name=hom1;tile=1;ord=3354010700499224[1].htm -Content-Transfer-Encoding: base64 -Content-ID: <Ux7VyFy7bTS9q> - -PGh0bWw+PGhlYWQ+PHRpdGxlPkNsaWNrIGhlcmUgdG8gZmluZCBvdXQgbW9yZSE8L3RpdGxl -PjwvaGVhZD4NCjxib2R5PjxTQ1JJUFQgTEFOR1VBR0U9SmF2YVNjcmlwdD4KPCEtLQp2YXIg -U2hvY2tNb2RlID0gMDsKaWYgKG5hdmlnYXRvci5taW1lVHlwZXMgJiYgbmF2aWdhdG9yLm1p -bWVUeXBlc1siYXBwbGljYXRpb24veC1zaG9ja3dhdmUtZmxhc2giXSAmJiBuYXZpZ2F0b3Iu -bWltZVR5cGVzWyJhcHBsaWNhdGlvbi94LXNob2Nrd2F2ZS1mbGFzaCJdLmVuYWJsZWRQbHVn -aW4pIHsKaWYgKG5hdmlnYXRvci5wbHVnaW5zICYmIG5hdmlnYXRvci5wbHVnaW5zWyJTaG9j -a3dhdmUgRmxhc2giXSkKU2hvY2tNb2RlID0gMTsKfQplbHNlIGlmIChuYXZpZ2F0b3IudXNl -ckFnZW50ICYmIG5hdmlnYXRvci51c2VyQWdlbnQuaW5kZXhPZigiTVNJRSIpPj0wIAomJiAo -bmF2aWdhdG9yLnVzZXJBZ2VudC5pbmRleE9mKCJXaW5kb3dzIDkiKT49MCB8fCBuYXZpZ2F0 -b3IudXNlckFnZW50LmluZGV4T2YoIldpbmRvd3MgTlQiKT49MCkpIHsKZG9jdW1lbnQud3Jp -dGUoJzxTQ1JJUFQgTEFOR1VBR0U9VkJTY3JpcHRcPiBcbicpOwpkb2N1bWVudC53cml0ZSgn -b24gZXJyb3IgcmVzdW1lIG5leHQgXG4nKTsKZG9jdW1lbnQud3JpdGUoJ1Nob2NrTW9kZSA9 -IChJc09iamVjdChDcmVhdGVPYmplY3QoIlNob2Nrd2F2ZUZsYXNoLlNob2Nrd2F2ZUZsYXNo -LjMiKSkpICcpOwpkb2N1bWVudC53cml0ZSgnPFwvU0NSSVBUXD4gJyk7Cn0KaWYgKCBTaG9j -a01vZGUgKSB7CmRvY3VtZW50LndyaXRlKCc8T0JKRUNUIGNsYXNzaWQ9ImNsc2lkOkQyN0NE -QjZFLUFFNkQtMTFjZi05NkI4LTQ0NDU1MzU0MDAwMCInKTsKZG9jdW1lbnQud3JpdGUoJyBj -b2RlYmFzZT0iaHR0cDovL2FjdGl2ZS5tYWNyb21lZGlhLmNvbS9mbGFzaDIvY2Ficy9zd2Zs -YXNoLmNhYiN2ZXJzaW9uPTMsMCwwLDAiJyk7CmRvY3VtZW50LndyaXRlKCcgSUQ9YmFubmVy -IFdJRFRIPTIzMCBIRUlHSFQ9MjIwPicpOwpkb2N1bWVudC53cml0ZSgnIDxQQVJBTSBOQU1F -PW1vdmllIFZBTFVFPSJodHRwOi8vd3d3LnRlcnJhLmNvbS5ici9hZHMvcG9wXzIzMHgyMjBf -Z3Z0X3RlbGVmb25lLnN3Zj9jbGlja3RhZz1odHRwOi8vYWQuYnIuZG91YmxlY2xpY2submV0 -L2NsaWNrJTNCaD12MnwyZGRkfDN8MHwlfHAlM0IzOTI1ODU3JTNCMC0wJTNCMCUzQjY2NjEw -MDIlM0IxLTQ2OHw2MCUzQjUwOTkxN3w1MDkyNDR8MSUzQiUzQiUzZmh0dHAlM2ElMmYlMmZ3 -d3cuZ3Z0Lm5ldC5ici9taWRpYV9wb3B1cHRlcnJhX3Byb21vcG9ydGFsLmpzcCI+ICcpOwpk -b2N1bWVudC53cml0ZSgnIDxQQVJBTSBOQU1FPXF1YWxpdHkgVkFMVUU9YXV0b2hpZ2g+ICcp -Owpkb2N1bWVudC53cml0ZSgnPEVNQkVEIFNSQz0iaHR0cDovL3d3dy50ZXJyYS5jb20uYnIv -YWRzL3BvcF8yMzB4MjIwX2d2dF90ZWxlZm9uZS5zd2Y/Y2xpY2t0YWc9aHR0cDovL2FkLmJy -LmRvdWJsZWNsaWNrLm5ldC9jbGljayUzQmg9djJ8MmRkZHwzfDB8JXxwJTNCMzkyNTg1NyUz -QjAtMCUzQjAlM0I2NjYxMDAyJTNCMS00Njh8NjAlM0I1MDk5MTd8NTA5MjQ0fDElM0IlM0Il -M2ZodHRwJTNhJTJmJTJmd3d3Lmd2dC5uZXQuYnIvbWlkaWFfcG9wdXB0ZXJyYV9wcm9tb3Bv -cnRhbC5qc3AiJyk7CmRvY3VtZW50LndyaXRlKCcgc3dMaXZlQ29ubmVjdD1GQUxTRSBXSURU -SD0yMzAgSEVJR0hUPTIyMCcpOwpkb2N1bWVudC53cml0ZSgnIFFVQUxJVFk9YXV0b2hpZ2gn -KTsKZG9jdW1lbnQud3JpdGUoJyBUWVBFPSJhcHBsaWNhdGlvbi94LXNob2Nrd2F2ZS1mbGFz -aCIgUExVR0lOU1BBR0U9Imh0dHA6Ly93d3cubWFjcm9tZWRpYS5jb20vc2hvY2t3YXZlL2Rv -d25sb2FkL2luZGV4LmNnaT9QMV9Qcm9kX1ZlcnNpb249U2hvY2t3YXZlRmxhc2giPicpOwpk -b2N1bWVudC53cml0ZSgnPC9FTUJFRD4nKTsKZG9jdW1lbnQud3JpdGUoJzwvT0JKRUNUPicp -Owp9IGVsc2UgaWYgKCEobmF2aWdhdG9yLmFwcE5hbWUgJiYgbmF2aWdhdG9yLmFwcE5hbWUu -aW5kZXhPZigiTmV0c2NhcGUiKT49MCAmJiBuYXZpZ2F0b3IuYXBwVmVyc2lvbi5pbmRleE9m -KCIyLiIpPj0wKSl7CmRvY3VtZW50LndyaXRlKCc8QSBIUkVGPSJodHRwOi8vYWQuYnIuZG91 -YmxlY2xpY2submV0L2NsaWNrJTNCaD12MnwyZGRkfDN8MHwlfHAlM0IzOTI1ODU3JTNCMC0w -JTNCMCUzQjY2NjEwMDIlM0IxLTQ2OHw2MCUzQjUwOTkxN3w1MDkyNDR8MSUzQiUzQiUzZmh0 -dHAlM2ElMmYlMmZ3d3cuZ3Z0Lm5ldC5ici9taWRpYV9wb3B1cHRlcnJhX3Byb21vcG9ydGFs -LmpzcCIgVEFSR0VUPSJfYmxhbmsiPjxJTUcgU1JDPSJodHRwOi8vd3d3LnRlcnJhLmNvbS5i -ci9hZHMvcG9wXzIzMHgyMjBfZ3Z0X3RlbGVmb25lLmdpZiIgV0lEVEg9MjMwIEhFSUdIVD0y -MjAgQk9SREVSPTA+PC9BPicpOwp9Ci8vLS0+CjwvU0NSSVBUPgo8Tk9FTUJFRD48QSBIUkVG -PT0iaHR0cDovL2FkLmJyLmRvdWJsZWNsaWNrLm5ldC9jbGljayUzQmg9djJ8MmRkZHwzfDB8 -JXxwJTNCMzkyNTg1NyUzQjAtMCUzQjAlM0I2NjYxMDAyJTNCMS00Njh8NjAlM0I1MDk5MTd8 -NTA5MjQ0fDElM0IlM0IlM2ZodHRwJTNhJTJmJTJmd3d3Lmd2dC5uZXQuYnIvbWlkaWFfcG9w -dXB0ZXJyYV9wcm9tb3BvcnRhbC5qc3AiIFRBUkdFVD0iX2JsYW5rIj48SU1HIFNSQz0iaHR0 -cDovL3d3dy50ZXJyYS5jb20uYnIvYWRzL3BvcF8yMzB4MjIwX2d2dF90ZWxlZm9uZS5naWYi -IFdJRFRIPTIzMCBIRUlHSFQ9MjIwIEJPUkRFUj0wPjwvQT48L05PRU1CRUQ+CjxOT1NDUklQ -VD48QSBIUkVGPT0iaHR0cDovL2FkLmJyLmRvdWJsZWNsaWNrLm5ldC9jbGljayUzQmg9djJ8 -MmRkZHwzfDB8JXxwJTNCMzkyNTg1NyUzQjAtMCUzQjAlM0I2NjYxMDAyJTNCMS00Njh8NjAl -M0I1MDk5MTd8NTA5MjQ0fDElM0IlM0IlM2ZodHRwJTNhJTJmJTJmd3d3Lmd2dC5uZXQuYnIv -bWlkaWFfcG9wdXB0ZXJyYV9wcm9tb3BvcnRhbC5qc3AiIFRBUkdFVD0iX2JsYW5rIj48SU1H -IFNSQz0iaHR0cDovL3d3dy50ZXJyYS5jb20uYnIvYWRzL3BvcF8yMzB4MjIwX2d2dF90ZWxl -Zm9uZS5naWYiIFdJRFRIPTIzMCBIRUlHSFQ9MjIwIEJPUkRFUj0wPjwvQT48L05PU0NSSVBU -PjwvYm9keT4NCjwvaHRtbD ---XQ4T5Cj14m5h2vQ69IpO4mCG-- - - diff --git a/test/virus2 b/test/virus2 deleted file mode 100644 index 86816aa..0000000 --- a/test/virus2 +++ /dev/null @@ -1,90 +0,0 @@ -Received: from www.bmsi.com (bmsweb.bmsi.com [219.109.11.130]) - by bmsaix.bmsi.com (8.9.1/8.9.1) with ESMTP id QAA24094 - for <ed@bmsi.com>; Fri, 12 Jan 2001 16:30:00 -0500 -Received: from jscaix.jsconnor.com (jscaix [209.193.177.106]) - by www.bmsi.com (8.9.1/8.9.1) with ESMTP id QAA30044 - for <ed@bmsi.com>; Fri, 12 Jan 2001 16:29:54 -0500 -Received: from connor.jsconnor.com (connor.jsconnor.com [192.168.100.15]) - by jscaix.jsconnor.com (8.9.1/8.9.1) with ESMTP id QAA12022 - for <ed@bmsi.com>; Fri, 12 Jan 2001 16:31:51 -0500 -X-Received: from goodspeed2.apical.com (ns1.apical.com [209.150.15.130]) - by jscaix.jsconnor.com (8.9.1/8.9.1) with ESMTP id HAA36550 - for <carrollf@jsconnor.com>; Fri, 12 Jan 2001 07:19:10 -0500 -X-Received: from SalCanino (cz-cblk-150-16-32.cyberzone.net [209.150.16.32]) - by goodspeed2.apical.com (8.9.3/8.9.3) with SMTP id HAA14946 - for <carrollf@jsconnor.com>; Fri, 12 Jan 2001 07:16:37 -0500 -Reply-To: <sal.canino@innovativeconcepts.com> -From: "Sal Canino" <sal.canino@innovativeconcepts.com> -To: "Carroll Forehand" <carrollf@jsconnor.com> -Subject: AUTEAE -Date: Fri, 12 Jan 2001 04:16:36 -0800 -Message-ID: <NEBBKLEPKLBIEKBANDGCIEMOCGAA.sal.canino@innovativeconcepts.com> -MIME-Version: 1.0 -Content-Type: multipart/mixed; - boundary="----=_NextPart_000_0003_01C07C4E.74368FC0" -X-Priority: 3 (Normal) -X-MSMail-Priority: Normal -X-Mailer: Microsoft Outlook IMO, Build 9.0.2416 (9.0.2910.0) -Importance: Normal -X-MimeOLE: Produced By Microsoft MimeOLE V5.50.4133.2400 -Disposition-Notification-To: "Sal Canino" <sal.canino@innovativeconcepts.com> -ReSent-Date: Fri, 12 Jan 2001 16:29:03 -0500 (EST) -ReSent-From: Carroll Forehand <carrollf@jsconnor.com> -ReSent-To: ed@bmsi.com -ReSent-Subject: AUTEAE -ReSent-Message-ID: <Pine.A41.4.10.10101121629001.171826@connor.jsconnor.com> - -This is a multi-part message in MIME format. - -------=_NextPart_000_0003_01C07C4E.74368FC0 -Content-Type: text/plain; - charset="iso-8859-1" -Content-Transfer-Encoding: 7bit - - - -------=_NextPart_000_0003_01C07C4E.74368FC0 -Content-Type: application/octet-stream; - name="PEDI.JPG.vbs" -Content-Transfer-Encoding: quoted-printable -Content-Disposition: attachment; - filename="PEDI.JPG.vbs" - -rem = -=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= -=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= -=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= -=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=0A= -rem "Plan Colombia" virus v1.0=0A= -rem by Sand Ja9e Gr0w (www.colombia.com)=0A= -=0A= -rem Dedicated to all the people that want to be hackers or crackers, in = -Colombia =0A= -rem This program is also a protest act against the violence and = -corruption that Colombia lives...=0A= -rem I always wanting that all this finishes, I have said...=0A= -=0A= -=0A= -rem Santa fe de Bogot=E1 2000/09=0A= -rem I dedicate to all you the song "GoodBye" of Andreas Bochelli=0A= -rem = -=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= -=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= -=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= -=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=0A= -=0A= -=0A= -rem Thanks God..!=0A= -rem A greeting for "Lina Mar=EDa" from "Santa fe de Bogot=E1"=0A= -rem A greeting for "Tizo" from "Spain"=0A= -rem And One kicked of tail to my friends, "eL ChE" and "ThE SpY"=0A= -=0A= -rem okay, ok... =0A= -rem my baby start here...=0A= -=0A= - =0A= -On Error Resume Next=0A= - -------=_NextPart_000_0003_01C07C4E.74368FC0-- - - diff --git a/test/virus3 b/test/virus3 deleted file mode 100644 index 04f65e4..0000000 --- a/test/virus3 +++ /dev/null @@ -1,50 +0,0 @@ -Received: from www.bmsi.com (bmsweb.bmsi.com [219.109.11.130]) - by bmsaix.bmsi.com (8.11.5/8.11.3) with ESMTP id f8EMUxS24174 - for <stuart@bmsi.com>; Fri, 14 Sep 2001 18:30:59 -0400 -Received: from bmsred.bmsi.com (bmsred [219.109.11.50]) - by www.bmsi.com (8.9.1/8.9.1) with ESMTP id SAA12740 - for <stuart@bmsi.com>; Fri, 14 Sep 2001 18:30:58 -0400 -X-Received: from www.bmsi.com (bmsweb.bmsi.com [219.109.11.130]) - by bmsaix.bmsi.com (8.11.5/8.11.3) with ESMTP id f8EESNW28934 - for <ed@bmsi.com>; Fri, 14 Sep 2001 10:28:23 -0400 -X-Received: from bwi.bwicorp.com (bwi.bwicorp.com [209.116.254.106]) - by www.bmsi.com (8.9.1/8.9.1) with ESMTP id KAA34262 - for <ed@bmsi.com>; Fri, 14 Sep 2001 10:28:20 -0400 -X-Received: from bwicorp.com (bwi3 [192.168.3.22]) - by bwi.bwicorp.com (8.9.1/8.9.1) with ESMTP id KAA42970 - for <ed@bmsi.com>; Fri, 14 Sep 2001 10:33:54 -0400 -Date: Fri, 14 Sep 2001 10:33:54 -0400 -From: Mary Smith <mary@bwicorp.com> -Message-Id: <200109141433.KAA42970@bwi.bwicorp.com> -MIME-Version: 1.0 -Content-Type: multipart/mixed; boundary="==i3.9.0oisdboibsd((kncd" -ReSent-Date: Fri, 14 Sep 2001 18:30:47 -0400 (EDT) -ReSent-From: Ed Bond <ed@bmsi.com> -ReSent-To: Stuart Gathman <stuart@bmsi.com> -ReSent-Subject: Resent mail.... -ReSent-Message-ID: <Pine.LNX.4.33.0109141830470.13214@bmsred.bmsi.com> - ---==i3.9.0oisdboibsd((kncd -Content-Type: application/octet-stream; name="READER_DIGEST_LETTER.TXT.pif" -Content-Transfer-Encoding: base64 -Content-Disposition: attachment; filename="READER_DIGEST_LETTER.TXT.pif" - -TVpQAAIAAAAEAA8A//8AALgAAAAAAAAAQAAaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAEAALoQAA4ftAnNIbgBTM0hkJBUaGlzIHByb2dyYW0gbXVzdCBiZSBydW4gdW5kZXIgV2lu -MzINCiQ3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBFAABMAQQA5ijojgAAAAAAAAAA4ACOgQsBAhkA -FAAAAAYAAAAAAAAAEAAAABAAAAAwAAAAAEAAABAAAAACAAABAAAAAAAAAAMACgAAAAAAAMAAAAAE -AAAAAAAAAgAAAAAAEAAAIAAAAAAQAAAQAAAAAAAAEAAAAAAAAAAAAAAAAEAAAIoAAAAAUAAAAAYA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ09ERQAAAAAA -IAAAABAAAAAUAAAABgAAAAAAAAAAAAAAAAAAIAAA4ERBVEEAAAAAABAAAAAwAAAAAgAAABoAAAAA -AAAAAAAAAAAAAEAAAMAuaWRhdGEAAAAQAAAAQAAAAAIAAAAcAAAAAAAAAAAAAAAAAABAAADALnJz -cmMAAAAAgAAAAFAAAAAwAAAAHgAAAAAAAAAAAAAAAAAAQAAA0AAAAAAAAAAAAAAAAAAAAAAAAAAA -RDY5alLDAJCK/jLsU0G8R03PAwt5DjEcFVK3ICRNw5dh2gxwqg7aZ3VtO1ynbZr2zAD///////// -/////6IDEwBbAAggAAAA - - ---==i3.9.0oisdboibsd((kncd-- - - diff --git a/test/virus4 b/test/virus4 deleted file mode 100644 index b2e6e6c..0000000 --- a/test/virus4 +++ /dev/null @@ -1,60 +0,0 @@ -From mdb@go2net.com Tue Sep 18 10:31:34 2001 -Received: from www.bmsi.com (bmsweb.bmsi.com [219.109.11.130]) - by bmsaix.bmsi.com (8.11.5/8.11.3) with ESMTP id f8IEVXM42662 - for <stuart@bmsi.com>; Tue, 18 Sep 2001 10:31:34 -0400 -Received: from STOREULV2 (mail.indexas.no [195.70.182.114]) - by www.bmsi.com (8.9.1/8.9.1) with SMTP id KAA27604 - for <stuart@bmsi.com>; Tue, 18 Sep 2001 10:31:31 -0400 -Date: Tue, 18 Sep 2001 10:31:31 -0400 -From: mdb@go2net.com -Message-Id: <200109181431.KAA27604@www.bmsi.com> -Subject: udesktopdesktopeksempeleksempeldesktopeksempeldesktopeksempeldesktopdesktopdesktopeksempeleksempeleksempeleksempeldesktopeksempeleksempeleksempeleksempeleksempeleksempeldesktopeksempeleksempeleksempeldesktopeksempeleksempeldesktopdesktopdesktopeksempeldeskmail.bmsi.com.desktop -MIME-Version: 1.0 -Content-Type: multipart/related; - type="multipart/alternative"; - boundary="====_ABC1234567890DEF_====" -X-Priority: 3 -X-MSMail-Priority: Normal -X-Unsent: 1 -Status: RO -X-Status: -X-Keywords: - ---====_ABC1234567890DEF_==== -Content-Type: multipart/alternative; - boundary="====_ABC0987654321DEF_====" - ---====_ABC0987654321DEF_==== -Content-Type: text/html; - charset="iso-8859-1" -Content-Transfer-Encoding: quoted-printable - - -<HTML><HEAD></HEAD><BODY bgColor=3D#ffffff> -<iframe src=3Dcid:EA4DMGBP9p height=3D0 width=3D0> -</iframe></BODY></HTML> ---====_ABC0987654321DEF_====-- - ---====_ABC1234567890DEF_==== -Content-Type: audio/x-wav; - name="readme.exe" -Content-Transfer-Encoding: base64 -Content-ID: <EA4DMGBP9p> - -TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAA2AAAAA4fug4AtAnNIbgBTM0hVGhpcyBwcm9ncmFtIGNhbm5vdCBiZSBydW4gaW4gRE9TIG1v -ZGUuDQ0KJAAAAAAAAAA11CFvcbVPPHG1TzxxtU88E6pcPHW1TzyZqkU8dbVPPJmqSzxytU88cbVO -PBG1TzyZqkQ8fbVPPMmzSTxwtU88UmljaHG1TzwAAAAAAAAAAMBEAWMAAAB/UEUAAEwBBQB1Oqc7 -AAAAAAAAAADgAA4BCwEGAABwAAAAYAAAAAAAALN0AAAAEAAAAIAAAAAAFzYAEAAAABAAAAQAAAAA -AAAABAAAAAAAAAAAEAEAABAAAAAAAAACAAAAAAAQAAAQAAAAABAAABAAAAAAAAAQAAAAAAAAAAAA -AACEgQAAUAAAAADgAACIHgAAAAAAAAAAAAAAAAAAAAAAAAAAAQA4CgAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAIQBAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAudGV4dAAAAFZlAAAAEAAAAHAAAAAQAAAAAAAAAAAAAAAAAAAgAABgLnJkYXRhAAAq -CQAAAIAAAAAQAAAAgAAAAAAAAAAAAAAAAAAAQAAAQC5kYXRhAAAAKEcAAACQAAAAIAAAAJAAAAAA -AAAAAAAAAAAAAEAAAMAucnNyYwAAAAAgAAAA4AAAACAAAACwAAAAAAAAAAAAAAAAAABAAABALnJl -bG9jAABGCwAAAAABAAAQAAAA0AAAAAAAAAAAAAAAAAAAQAAAQgAAAAAAAAAAAAAAAAAAAAAAAAAA -AAA= - ---====_ABC1234567890DEF_==== - - diff --git a/test/virus5 b/test/virus5 deleted file mode 100644 index de9142c..0000000 --- a/test/virus5 +++ /dev/null @@ -1,38 +0,0 @@ -From mdb@go2net.com Tue Sep 18 10:31:34 2001 -Received: from localhost (varna148.pip.digsys.bg [193.68.1.148]) - by danbo.digsys.bg (8.10.1/8.10.1) with SMTP id fAM7FHk06734 - for butchc@trwonnor.com; Thu, 22 Nov 2001 09:15:18 +0200 (EET) -From: POP - interlogvar <interlogvar@mbox.digsys.bg> -Message-Id: <200111220715.fAM7FHk06734@danbo.digsys.bg> -To: butchc@trwonnor.com -Subject: Funny shit to see ?! -Date: Thu,22 Nov 2001 09:16:34 -0000 -MIME-Version: 1.0 -Content-Type: multipart/mixed; - boundary="bound" - X-Priority: 3 - X-MSMail-Priority: Normal - X-Mailer: Microsoft Outlook Express 5.50.4522.1300 - X-MimeOLE: Produced By Microsoft MimeOLE V5.50.4522.1300 - -This is a multi-part message in MIME format. - ---bound -Content-Type: text/html; - charset="iso-8859-1" -Content-Transfer-Encoding: quoted-printable - -<HTML><HEAD></HEAD><BODY><iframe src=3Dcid:SOMECID height=3D0 width=3D0></iframe> -<font>peace</font></BODY></HTML> - ---bound -Content-Type: audio/x-wav; - name="whatever.exe" -Content-Transfer-Encoding: base64 -Content-ID: <SOMECID> - -TVoAAAIAAAACAB4AHgAAAAACAAAAAAAAAAAAAMWnLuEOH7oOALQJ -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAA= - ---bound-- diff --git a/test/virus6 b/test/virus6 deleted file mode 100644 index 06afa66..0000000 --- a/test/virus6 +++ /dev/null @@ -1,27 +0,0 @@ -From mdb@go2net.com Tue Sep 18 10:31:34 2001 -Received: from aglnss01.grupoagrisal.net ([172.16.0.1]) - by agntss05 (Lotus Domino Release 5.07a) - with ESMTP id 2001120416164050:5294 ; - Tue, 4 Dec 2001 16:16:40 -0600 -Subject: MAEU XSS025786 - ORDER 1251 - CONTAINER MAEU 6053725 -To: kathyp@jsconnor.com -Cc: Blanca@ace-of-hearts.net -X-Mailer: Lotus Notes Release 5.07a May 14, 2001 -Message-ID: <OF28551015.C47BCC85-ON06256B18.0079DD92@grupoagrisal.net> -From: sherrera.dco.lc@agrisal.com -Date: Tue, 4 Dec 2001 16:11:48 -0600 -MIME-Version: 1.0 -X-MIMETrack: Serialize by Router on AGLNSS01/AGRISAL(Release 5.07a |May 14, 2001) at 04/12/2001 - 04:11:57 p.m., - Itemize by SMTP Server on aglnss03/Grupo_Agrisal(Release 5.07a |May 14, 2001) at - 12/04/2001 04:16:41 PM, - Serialize by Router on aglnss03/Grupo_Agrisal(Release 5.07a |May 14, 2001) at - 12/04/2001 04:16:51 PM -Content-type: application/octet-stream; - name="FAX20.exe" -Content-Disposition: attachment; filename="FAX20.exe" -Content-Transfer-Encoding: base64 - -TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAKJsVAAAACIAACIAACIBr6AQA - diff --git a/test/virus7 b/test/virus7 deleted file mode 100644 index c4c02c2..0000000 --- a/test/virus7 +++ /dev/null @@ -1,62 +0,0 @@ -From pandora.owner@pandora.cz Wed Mar 24 21:02:22 2004 -Received: from pandora.cz (localhost [127.0.0.1]) - by pandora3.mobil.cz (8.12.8/8.12.8) with ESMTP id i2O88iWu021270 - for <stuart@bmsi.com>; Wed, 24 Mar 2004 09:08:44 +0100 -Message-Id: <200403240808.i2O88iWu021270@pandora3.mobil.cz> -X-Sender: Pandora -MIME-Version: 1.0 -Date: Wed, 24 Mar 2004 09:08:44 +0100 -From: "administrator@pandora.cz" <administrator@pandora.cz> -To: "stuart@bmsi.com" <stuart@bmsi.com> -Subject: Konferenceneexistuje -Content-Type: multipart/mixed; boundary="Pandora3Bndry_1080115724426044878" - - ---Pandora3Bndry_1080115724426044878 -Content-Type: multipart/alternative; boundary="Pandora3Bndry_1080115724783315537" - - ---Pandora3Bndry_1080115724783315537 -Content-Type: text/plain; charset="ISO-8859-2" - -Konference '2003-07-46063' neexistuje. - ---Pandora3Bndry_1080115724783315537 -Content-Type: text/html; charset="ISO-8859-2" - -Konference '2003-07-46063' neexistuje. - ---Pandora3Bndry_1080115724783315537-- - ---Pandora3Bndry_1080115724426044878 -Content-Type: message/rfc822; boundary="----=_NextPart_000_0010_00000FFF.00007545" - -MIME-Version: 1.0 -Date: Wed, 24 Mar 2004 09:03:28 +0100 -From: "" <stuart@bmsi.com> -To: "" <2003-07-46063@pandora.cz> -Subject: =?ISO-8859-2?q?Re=3A_Your_software?= -Content-Type: multipart/mixed; boundary="Pandora3Bndry_10801157231587976770" - - ---Pandora3Bndry_10801157231587976770 -Content-Type: text/plain; charset="Windows-1252" -Content-Transfer-Encoding: 7bit - -See the attached file for details. - - ---Pandora3Bndry_10801157231587976770 -Content-Type: application/octet-stream; name="application.pif" -Content-Disposition: attachment; filename="application.pif" -Content-Transfer-Encoding: base64 - -TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAuAAAAKvnXsbvhjCV74Ywle+GMJVsmj6V44YwlQeZOpX2hjCV74YxlbiGMJVsjm2V -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - ---Pandora3Bndry_10801157231587976770-- - ---Pandora3Bndry_1080115724426044878-- - diff --git a/test/zip1 b/test/zip1 deleted file mode 100644 index 6dfcb75..0000000 --- a/test/zip1 +++ /dev/null @@ -1,51 +0,0 @@ -From paulp@go2net.com Wed Jun 1 22:35:12 2005 -Return-Path: <paulp@go2net.com> -Received: from mail.bmsi.com (spidey.bmsi.com [192.168.9.81]) - by bmsred.bmsi.com (8.13.1/8.12.10) with ESMTP id j522ZCQg014058 - for <stuart@bmsred.bmsi.com>; Wed, 1 Jun 2005 22:35:12 -0400 -Received: from 127.0.0.1 ([220.117.92.241]) - by mail.bmsi.com (8.13.1/8.13.1) with ESMTP id j522Ynjm028604 - for stuart@bmsi.com; Wed, 1 Jun 2005 22:34:51 -0400 -Message-Id: <200506020234.j522Ynjm028604@mail.bmsi.com> -SUBJECT: urgent -FROM: paulp@go2net.com -TO: stuart@bmsi.com -DATE: [[ ��, 02 6 2005 ���� 11:34:47 ]] -MIME-Version: 1.0 -Content-Type: multipart/mixed; boundary="--------bound--" -X-DSpam-Score: 0.081200 -Received-SPF: neutral (mail.bmsi.com: guessing: 220.117.92.241 is neither permitted nor denied by domain of go2net.com) -Status: RO -X-Status: -X-Keywords: NonJunk - -----------bound-- -Content-Type: text/plain; charset=us-ascii -Content-Transfer-Encoding: 7bit - -Hi - -Sorry, I forgot to send an important -document to you in that last email. I had an important phone call. -Please checkout attached doc file when you have a moment. - -Best Regards - -<!DSPAM:1043AE6B6492860536935410> - - -----------bound-- -Content-Type: application/x-msdownload; name="zip.zip" -Content-Transfer-Encoding: base64 -Content-Disposition: attachment; filename="zip.zip" - -UEsDBAoAAAAAADVVwjLaV2nEGgAAABoAAAAzABUAemlwLmRvYyAgICAgICAgICAgICAgICAg -ICAgICAgICAgICAgICAgICAgICAgICAuZXhlVVQJAAOmGp9CphqfQlV4BACGA2UAVGhpcyBw -cm9ncmFtIHdhcyBhIHZpcnVzLgpQSwECFwMKAAAAAAA1VcIy2ldpxBoAAAAaAAAAMwANAAAA -AAABAAAAtIEAAAAAemlwLmRvYyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg -ICAgICAuZXhlVVQFAAOmGp9CVXgAAFBLBQYAAAAAAQABAG4AAACAAAAAAAA= -----------bound-- - - -----------bound---- - diff --git a/testbms.py b/testbms.py deleted file mode 100644 index 56752bd..0000000 --- a/testbms.py +++ /dev/null @@ -1,299 +0,0 @@ -import unittest -import Milter -import bms -import mime -import rfc822 -import StringIO -import email -import sys -#import pdb - -class TestMilter(bms.bmsMilter): - - def __init__(self): - bms.bmsMilter.__init__(self) - self.logfp = open("test/milter.log","a") - self._delrcpt = [] # record deleted rcpts for testing - self._addrcpt = [] # record added rcpts for testing - - def log(self,*msg): - for i in msg: print >>self.logfp, i, - print >>self.logfp - - def getsymval(self,name): - if name == 'j': return 'test.milter.org' - return bms.bmsMilter.getsymval(self,name) - - def replacebody(self,chunk): - if self._body: - self._body.write(chunk) - self.bodyreplaced = True - else: - raise IOError,"replacebody not called from eom()" - - # FIXME: rfc822 indexing does not really reflect the way chg/add header - # work for a milter - def chgheader(self,field,idx,value): - if not self._body: - raise IOError,"chgheader not called from eom()" - self.log('chgheader: %s[%d]=%s' % (field,idx,value)) - if value == '': - del self._msg[field] - else: - self._msg[field] = value - self.headerschanged = True - - def addheader(self,field,value): - if not self._body: - raise IOError,"addheader not called from eom()" - self.log('addheader: %s=%s' % (field,value)) - self._msg[field] = value - self.headerschanged = True - - def delrcpt(self,rcpt): - if not self._body: - raise IOError,"delrcpt not called from eom()" - self._delrcpt.append(rcpt) - - def addrcpt(self,rcpt): - if not self._body: - raise IOError,"addrcpt not called from eom()" - self._addrcpt.append(rcpt) - - def setreply(self,rcode,xcode,msg): - self.reply = (rcode,xcode,msg) - - def feedFile(self,fp,sender="spam@adv.com",rcpt="victim@lamb.com"): - self._body = None - self.bodyreplaced = False - self.headerschanged = False - self.reply = None - msg = rfc822.Message(fp) - rc = self.envfrom('<%s>'%sender) - if rc != Milter.CONTINUE: return rc - rc = self.envrcpt('<%s>'%rcpt) - if rc != Milter.CONTINUE: return rc - line = None - for h in msg.headers: - if h[:1].isspace(): - line = line + h - continue - if not line: - line = h - continue - s = line.split(': ',1) - if len(s) > 1: val = s[1].strip() - else: val = '' - rc = self.header(s[0],val) - if rc != Milter.CONTINUE: return rc - line = h - if line: - s = line.split(': ',1) - rc = self.header(s[0],s[1]) - if rc != Milter.CONTINUE: return rc - rc = self.eoh() - if rc != Milter.CONTINUE: return rc - while 1: - buf = fp.read(8192) - if len(buf) == 0: break - rc = self.body(buf) - if rc != Milter.CONTINUE: return rc - self._msg = msg - self._body = StringIO.StringIO() - rc = self.eom() - if self.bodyreplaced: - body = self._body.getvalue() - else: - msg.rewindbody() - body = msg.fp.read() - self._body = StringIO.StringIO() - self._body.writelines(msg.headers) - self._body.write('\n') - self._body.write(body) - return rc - - def feedMsg(self,fname,sender="spam@adv.com",rcpt="victim@lamb.com"): - fp = open('test/'+fname,'r') - rc = self.feedFile(fp,sender,rcpt) - fp.close() - return rc - - def connect(self,host='localhost'): - self._body = None - self.bodyreplaced = False - rc = bms.bmsMilter.connect(self,host,1,('1.2.3.4',1234)) - if rc != Milter.CONTINUE and rc != Milter.ACCEPT: - self.close() - return rc - rc = self.hello('spamrelay') - if rc != Milter.CONTINUE: - self.close() - return rc - -class BMSMilterTestCase(unittest.TestCase): - - def testDefang(self,fname='virus1'): - milter = TestMilter() - rc = milter.connect('testDefang') - self.assertEqual(rc,Milter.CONTINUE) - rc = milter.feedMsg(fname) - self.assertEqual(rc,Milter.ACCEPT) - self.failUnless(milter.bodyreplaced,"Message body not replaced") - fp = milter._body - open('test/'+fname+".tstout","w").write(fp.getvalue()) - #self.failUnless(fp.getvalue() == open("test/virus1.out","r").read()) - fp.seek(0) - msg = mime.message_from_file(fp) - str = msg.get_payload(1).get_payload() - milter.log(str) - milter.close() - - # test some spams that crashed our parser - def testParse(self,fname='spam7'): - milter = TestMilter() - milter.connect('testParse') - rc = milter.feedMsg(fname) - self.assertEqual(rc,Milter.ACCEPT) - self.failIf(milter.bodyreplaced,"Milter needlessly replaced body.") - fp = milter._body - open('test/'+fname+".tstout","w").write(fp.getvalue()) - milter.connect('pro-send.com') - rc = milter.feedMsg('spam8') - self.assertEqual(rc,Milter.ACCEPT) - self.failIf(milter.bodyreplaced,"Milter needlessly replaced body.") - rc = milter.feedMsg('bounce') - self.assertEqual(rc,Milter.ACCEPT) - self.failIf(milter.bodyreplaced,"Milter needlessly replaced body.") - rc = milter.feedMsg('bounce1') - self.assertEqual(rc,Milter.ACCEPT) - self.failIf(milter.bodyreplaced,"Milter needlessly replaced body.") - milter.close() - - def testDefang2(self): - milter = TestMilter() - milter.connect('testDefang2') - rc = milter.feedMsg('samp1') - self.assertEqual(rc,Milter.ACCEPT) - self.failIf(milter.bodyreplaced,"Milter needlessly replaced body.") - rc = milter.feedMsg("virus3") - self.assertEqual(rc,Milter.ACCEPT) - self.failUnless(milter.bodyreplaced,"Message body not replaced") - fp = milter._body - open("test/virus3.tstout","w").write(fp.getvalue()) - #self.failUnless(fp.getvalue() == open("test/virus3.out","r").read()) - rc = milter.feedMsg("virus6") - self.assertEqual(rc,Milter.ACCEPT) - self.failUnless(milter.bodyreplaced,"Message body not replaced") - self.failUnless(milter.headerschanged,"Message headers not adjusted") - fp = milter._body - open("test/virus6.tstout","w").write(fp.getvalue()) - milter.close() - - def testDefang3(self): - milter = TestMilter() - milter.connect('testDefang3') - # test script removal on complex HTML attachment - rc = milter.feedMsg('amazon') - self.assertEqual(rc,Milter.ACCEPT) - self.failUnless(milter.bodyreplaced,"Message body not replaced") - fp = milter._body - open("test/amazon.tstout","w").write(fp.getvalue()) - # test defanging Klez virus - rc = milter.feedMsg("virus13") - self.assertEqual(rc,Milter.ACCEPT) - self.failUnless(milter.bodyreplaced,"Message body not replaced") - fp = milter._body - open("test/virus13.tstout","w").write(fp.getvalue()) - # test script removal on quoted-printable HTML attachment - # sgmllib can't handle the <![if cond]> syntax - rc = milter.feedMsg('spam44') - self.assertEqual(rc,Milter.ACCEPT) - self.failIf(milter.bodyreplaced,"Message body replaced") - fp = milter._body - open("test/spam44.tstout","w").write(fp.getvalue()) - milter.close() - - def testRFC822(self): - milter = TestMilter() - milter.connect('testRFC822') - # test encoded rfc822 attachment - #pdb.set_trace() - rc = milter.feedMsg('test8') - self.assertEqual(rc,Milter.ACCEPT) - # python2.4 doesn't scan encoded message attachments - if sys.hexversion < 0x02040000: - self.failUnless(milter.bodyreplaced,"Message body not replaced") - #self.failIf(milter.bodyreplaced,"Message body replaced") - fp = milter._body - open("test/test8.tstout","w").write(fp.getvalue()) - rc = milter.feedMsg('virus7') - self.assertEqual(rc,Milter.ACCEPT) - self.failUnless(milter.bodyreplaced,"Message body not replaced") - #self.failIf(milter.bodyreplaced,"Message body replaced") - fp = milter._body - open("test/virus7.tstout","w").write(fp.getvalue()) - - def testSmartAlias(self): - milter = TestMilter() - milter.connect('testSmartAlias') - # test smart alias feature - key = ('foo@bar.com','baz@bat.com') - bms.smart_alias[key] = ['ham@eggs.com'] - rc = milter.feedMsg('test8',key[0],key[1]) - self.assertEqual(rc,Milter.ACCEPT) - self.failUnless(milter._delrcpt == ['<baz@bat.com>']) - self.failUnless(milter._addrcpt == ['<ham@eggs.com>']) - # python2.4 email does not decode message attachments, so script - # is not replaced - if sys.hexversion < 0x02040000: - self.failUnless(milter.bodyreplaced,"Message body not replaced") - - def testBadBoundary(self): - milter = TestMilter() - milter.connect('testBadBoundary') - # test rfc822 attachment with invalid boundaries - #pdb.set_trace() - rc = milter.feedMsg('bound') - if sys.hexversion < 0x02040000: - # python2.4 adds invalid boundaries to decects list and makes - # payload a str - self.assertEqual(rc,Milter.REJECT) - self.assertEqual(milter.reply[0],'554') - #self.failUnless(milter.bodyreplaced,"Message body not replaced") - self.failIf(milter.bodyreplaced,"Message body replaced") - fp = milter._body - open("test/bound.tstout","w").write(fp.getvalue()) - - def testCompoundFilename(self): - milter = TestMilter() - milter.connect('testCompoundFilename') - # test rfc822 attachment with invalid boundaries - #pdb.set_trace() - rc = milter.feedMsg('test1') - self.assertEqual(rc,Milter.ACCEPT) - #self.failUnless(milter.bodyreplaced,"Message body not replaced") - self.failIf(milter.bodyreplaced,"Message body replaced") - fp = milter._body - open("test/test1.tstout","w").write(fp.getvalue()) - -# def testReject(self): -# "Test content based spam rejection." -# milter = TestMilter() -# milter.connect('gogo-china.com') -# rc = milter.feedMsg('big5'); -# self.failUnless(rc == Milter.REJECT) -# milter.close(); - -def suite(): return unittest.makeSuite(BMSMilterTestCase,'test') - -if __name__ == '__main__': - if len(sys.argv) > 1: - for fname in sys.argv[1:]: - milter = TestMilter() - milter.connect('main') - fp = open(fname,'r') - rc = milter.feedFile(fp) - fp = milter._body - sys.stdout.write(fp.getvalue()) - else: - unittest.main() diff --git a/testmime.py b/testmime.py deleted file mode 100644 index 004f43e..0000000 --- a/testmime.py +++ /dev/null @@ -1,155 +0,0 @@ -# $Log$ -# Revision 1.1.1.2 2005/05/31 18:23:49 customdesigned -# Development changes since 0.7.2 -# -# Revision 1.23 2005/02/11 18:34:14 stuart -# Handle garbage after quote in boundary. -# -# Revision 1.22 2005/02/10 01:10:59 stuart -# Fixed MimeMessage.ismodified() -# -# Revision 1.21 2005/02/10 00:56:49 stuart -# Runs with python2.4. Defang not working correctly - more work needed. -# -# Revision 1.20 2004/11/20 16:38:17 stuart -# Add rcs log -# -import unittest -import mime -import socket -import StringIO -import email -import sys -from email import Errors - -samp1_txt1 = """Dear Agent 1 -I hope you can read this. Whenever you write label it P.B.S kids. - Eliza doesn't know a thing about P.B.S kids. got to go by -agent one.""" - -hostname = socket.gethostname() - -class MimeTestCase(unittest.TestCase): - - # test mime parameter parsing - def testParam(self): - plist = mime._parseparam( - '; boundary="----=_NextPart_000_4e56_490d_48e3"') - self.failUnless(len(plist)==1) - self.failUnless(plist[0] == 'boundary="----=_NextPart_000_4e56_490d_48e3"') - plist = mime._parseparam('; name="Jim&amp;Girlz.jpg"') - self.failUnless(len(plist)==1) - self.failUnless(plist[0] == 'name="Jim&amp;Girlz.jpg"') - - def testParse(self,fname='samp1'): - msg = mime.message_from_file(open('test/'+fname,"r")) - self.failUnless(msg.ismultipart()) - parts = msg.get_payload() - self.failUnless(len(parts) == 2) - txt1 = parts[0].get_payload() - self.failUnless(txt1.rstrip() == samp1_txt1,txt1) - msg = mime.message_from_file(open('test/missingboundary',"r")) - # should get no exception as long as we don't try to parse - # message attachments - mime.defang(msg,scan_rfc822=False) - msg.dump(open('test/missingboundary.out','w')) - msg = mime.message_from_file(open('test/missingboundary',"r")) - try: - mime.defang(msg) - # python 2.4 doesn't get exceptions on missing boundaries, and - # if message is modified, output is readable by mail clients - if sys.hexversion < 0x02040000: - self.fail('should get boundary error parsing bad rfc822 attachment') - except Errors.BoundaryError: - pass - - def testDefang(self,vname='virus1',part=1, - fname='LOVE-LETTER-FOR-YOU.TXT.vbs'): - msg = mime.message_from_file(open('test/'+vname,"r")) - mime.defang(msg,scan_zip=True) - self.failUnless(msg.ismodified(),"virus not removed") - oname = vname + '.out' - msg.dump(open('test/'+oname,"w")) - msg = mime.message_from_file(open('test/'+oname,"r")) - txt2 = msg.get_payload() - if type(txt2) == list: - txt2 = txt2[part].get_payload() - self.failUnless( - txt2.rstrip()+'\n' == mime.virus_msg % (fname,hostname,None),txt2) - - def testDefang3(self): - self.testDefang('virus3',0,'READER_DIGEST_LETTER.TXT.pif') - - # virus4 does not include proper end boundary - def testDefang4(self): - self.testDefang('virus4',1,'readme.exe') - - # virus5 is even more screwed up - def testDefang5(self): - self.testDefang('virus5',1,'whatever.exe') - - # virus6 has no parts - the virus is directly inline - def testDefang6(self,vname="virus6",fname='FAX20.exe'): - msg = mime.message_from_file(open('test/'+vname,"r")) - mime.defang(msg) - oname = vname + '.out' - msg.dump(open('test/'+oname,"w")) - msg = mime.message_from_file(open('test/'+oname,"r")) - self.failIf(msg.ismultipart()) - txt2 = msg.get_payload() - self.failUnless(txt2 == mime.virus_msg % \ - (fname,hostname,None),txt2) - - # honey virus has a sneaky ASP payload which is parsed correctly - # by email package in python-2.2.2, but not by mime.MimeMessage or 2.2.1 - def testDefang7(self,vname="honey",fname='story[1].scr'): - msg = mime.message_from_file(open('test/'+vname,"r")) - mime.defang(msg) - oname = vname + '.out' - msg.dump(open('test/'+oname,"w")) - msg = mime.message_from_file(open('test/'+oname,"r")) - parts = msg.get_payload() - txt2 = parts[1].get_payload() - txt3 = parts[2].get_payload() - self.failUnless(txt2.rstrip()+'\n' == mime.virus_msg % \ - (fname,hostname,None),txt2) - if txt3 != '': - self.failUnless(txt3.rstrip()+'\n' == mime.virus_msg % \ - ('story[1].asp',hostname,None),txt3) - - def testParse2(self,fname="spam7"): - msg = mime.message_from_file(open('test/'+fname,"r")) - self.failUnless(msg.ismultipart()) - parts = msg.get_payload() - self.failUnless(len(parts) == 2) - name = parts[1].getname() - self.failUnless(name == "Jim&amp;Girlz.jpg","name=%s"%name) - - def testZip(self,vname="zip1",fname='zip.zip'): - self.testDefang('zip1',1,'zip.zip') - msg = mime.message_from_file(open('test/'+vname,"r")) - mime.defang(msg,scan_zip=False) - self.failIf(msg.ismodified()) - - def testHTML(self,fname=""): - result = StringIO.StringIO() - filter = mime.HTMLScriptFilter(result) - msg = """<! Illegal declaration used as comment> - <![if conditional]> Optional SGML <![endif]> - <!-- Legal SGML comment --> - """ - script = "<script lang=javascript> Dangerous script </script>" - filter.feed(msg + script) - filter.close() - #print result.getvalue() - self.failUnless(result.getvalue() == msg + filter.msg) - -def suite(): return unittest.makeSuite(MimeTestCase,'test') - -if __name__ == '__main__': - if len(sys.argv) < 2: - unittest.main() - else: - for fname in sys.argv[1:]: - fp = open(fname,'r') - msg = mime.message_from_file(fp) diff --git a/testsample.py b/testsample.py deleted file mode 100644 index 74acdaa..0000000 --- a/testsample.py +++ /dev/null @@ -1,149 +0,0 @@ -import unittest -import Milter -import sample -import mime -import rfc822 -import StringIO - -class TestMilter(sample.sampleMilter): - - def __init__(self): - self.logfp = open("test/milter.log","a") - - def log(self,*msg): - for i in msg: print >>self.logfp, i, - print >>self.logfp - - def replacebody(self,chunk): - if self._body: - self._body.write(chunk) - self.bodyreplaced = True - else: - raise IOError,"replacebody not called from eom()" - - # FIXME: rfc822 indexing does not really reflect the way chg/add header - # work for a milter - def chgheader(self,field,idx,value): - self.log('chgheader: %s[%d]=%s' % (field,idx,value)) - if value == '': - del self._msg[field] - else: - self._msg[field] = value - self.headerschanged = True - - def addheader(self,field,value): - self.log('addheader: %s=%s' % (field,value)) - self._msg[field] = value - self.headerschanged = True - - def feedMsg(self,fname): - self._body = None - self.bodyreplaced = False - self.headerschanged = 0 - fp = open('test/'+fname,'r') - msg = rfc822.Message(fp) - rc = self.envfrom('<spam@advertisements.com>') - if rc != Milter.CONTINUE: return rc - rc = self.envrcpt('<victim@lamb.com>') - if rc != Milter.CONTINUE: return rc - line = None - for h in msg.headers: - if h[:1].isspace(): - line = line + h - continue - if not line: - line = h - continue - s = line.split(': ',1) - rc = self.header(s[0],s[1].strip()) - if rc != Milter.CONTINUE: return rc - line = h - if line: - s = line.split(': ',1) - rc = self.header(s[0],s[1]) - if rc != Milter.CONTINUE: return rc - rc = self.eoh() - if rc != Milter.CONTINUE: return rc - while 1: - buf = fp.read(8192) - if len(buf) == 0: break - rc = self.body(buf) - if rc != Milter.CONTINUE: return rc - self._msg = msg - self._body = StringIO.StringIO() - rc = self.eom() - if self.bodyreplaced: - body = self._body.getvalue() - else: - msg.rewindbody() - body = msg.fp.read() - self._body = StringIO.StringIO() - self._body.writelines(msg.headers) - self._body.write('\n') - self._body.write(body) - return rc - - def connect(self,host='localhost'): - self._body = None - self.bodyreplaced = False - rc = sample.sampleMilter.connect(self,host,1,0) - if rc != Milter.CONTINUE and rc != Milter.ACCEPT: - self.close() - return rc - rc = self.hello('spamrelay') - if rc != Milter.CONTINUE: - self.close() - return rc - -class BMSMilterTestCase(unittest.TestCase): - - def testDefang(self,fname='virus1'): - milter = TestMilter() - rc = milter.connect() - self.failUnless(rc == Milter.CONTINUE) - rc = milter.feedMsg(fname) - self.failUnless(rc == Milter.ACCEPT) - self.failUnless(milter.bodyreplaced,"Message body not replaced") - fp = milter._body - open('test/'+fname+".tstout","w").write(fp.getvalue()) - #self.failUnless(fp.getvalue() == open("test/virus1.out","r").read()) - fp.seek(0) - msg = mime.message_from_file(fp) - s = msg.get_payload(1).get_payload() - milter.log(s) - milter.close() - - def testParse(self,fname='spam7'): - milter = TestMilter() - milter.connect('somehost') - rc = milter.feedMsg(fname) - self.failUnless(rc == Milter.ACCEPT) - self.failIf(milter.bodyreplaced,"Milter needlessly replaced body.") - fp = milter._body - open('test/'+fname+".tstout","w").write(fp.getvalue()) - milter.close() - - def testDefang2(self): - milter = TestMilter() - milter.connect('somehost') - rc = milter.feedMsg('samp1') - self.failUnless(rc == Milter.ACCEPT) - self.failIf(milter.bodyreplaced,"Milter needlessly replaced body.") - rc = milter.feedMsg("virus3") - self.failUnless(rc == Milter.ACCEPT) - self.failUnless(milter.bodyreplaced,"Message body not replaced") - fp = milter._body - open("test/virus3.tstout","w").write(fp.getvalue()) - #self.failUnless(fp.getvalue() == open("test/virus3.out","r").read()) - rc = milter.feedMsg("virus6") - self.failUnless(rc == Milter.ACCEPT) - self.failUnless(milter.bodyreplaced,"Message body not replaced") - self.failUnless(milter.headerschanged,"Message headers not adjusted") - fp = milter._body - open("test/virus6.tstout","w").write(fp.getvalue()) - milter.close() - -def suite(): return unittest.makeSuite(BMSMilterTestCase,'test') - -if __name__ == '__main__': - unittest.main() -- GitLab