Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3cef83f59b |
@@ -0,0 +1,340 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 2, June 1991
|
||||||
|
|
||||||
|
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||||
|
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The licenses for most software are designed to take away your
|
||||||
|
freedom to share and change it. By contrast, the GNU General Public
|
||||||
|
License is intended to guarantee your freedom to share and change free
|
||||||
|
software--to make sure the software is free for all its users. This
|
||||||
|
General Public License applies to most of the Free Software
|
||||||
|
Foundation's software and to any other program whose authors commit to
|
||||||
|
using it. (Some other Free Software Foundation software is covered by
|
||||||
|
the GNU Library General Public License instead.) You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
this service if you wish), that you receive source code or can get it
|
||||||
|
if you want it, that you can change the software or use pieces of it
|
||||||
|
in new free programs; and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to make restrictions that forbid
|
||||||
|
anyone to deny you these rights or to ask you to surrender the rights.
|
||||||
|
These restrictions translate to certain responsibilities for you if you
|
||||||
|
distribute copies of the software, or if you modify it.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must give the recipients all the rights that
|
||||||
|
you have. You must make sure that they, too, receive or can get the
|
||||||
|
source code. And you must show them these terms so they know their
|
||||||
|
rights.
|
||||||
|
|
||||||
|
We protect your rights with two steps: (1) copyright the software, and
|
||||||
|
(2) offer you this license which gives you legal permission to copy,
|
||||||
|
distribute and/or modify the software.
|
||||||
|
|
||||||
|
Also, for each author's protection and ours, we want to make certain
|
||||||
|
that everyone understands that there is no warranty for this free
|
||||||
|
software. If the software is modified by someone else and passed on, we
|
||||||
|
want its recipients to know that what they have is not the original, so
|
||||||
|
that any problems introduced by others will not reflect on the original
|
||||||
|
authors' reputations.
|
||||||
|
|
||||||
|
Finally, any free program is threatened constantly by software
|
||||||
|
patents. We wish to avoid the danger that redistributors of a free
|
||||||
|
program will individually obtain patent licenses, in effect making the
|
||||||
|
program proprietary. To prevent this, we have made it clear that any
|
||||||
|
patent must be licensed for everyone's free use or not licensed at all.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
|
0. This License applies to any program or other work which contains
|
||||||
|
a notice placed by the copyright holder saying it may be distributed
|
||||||
|
under the terms of this General Public License. The "Program", below,
|
||||||
|
refers to any such program or work, and a "work based on the Program"
|
||||||
|
means either the Program or any derivative work under copyright law:
|
||||||
|
that is to say, a work containing the Program or a portion of it,
|
||||||
|
either verbatim or with modifications and/or translated into another
|
||||||
|
language. (Hereinafter, translation is included without limitation in
|
||||||
|
the term "modification".) Each licensee is addressed as "you".
|
||||||
|
|
||||||
|
Activities other than copying, distribution and modification are not
|
||||||
|
covered by this License; they are outside its scope. The act of
|
||||||
|
running the Program is not restricted, and the output from the Program
|
||||||
|
is covered only if its contents constitute a work based on the
|
||||||
|
Program (independent of having been made by running the Program).
|
||||||
|
Whether that is true depends on what the Program does.
|
||||||
|
|
||||||
|
1. You may copy and distribute verbatim copies of the Program's
|
||||||
|
source code as you receive it, in any medium, provided that you
|
||||||
|
conspicuously and appropriately publish on each copy an appropriate
|
||||||
|
copyright notice and disclaimer of warranty; keep intact all the
|
||||||
|
notices that refer to this License and to the absence of any warranty;
|
||||||
|
and give any other recipients of the Program a copy of this License
|
||||||
|
along with the Program.
|
||||||
|
|
||||||
|
You may charge a fee for the physical act of transferring a copy, and
|
||||||
|
you may at your option offer warranty protection in exchange for a fee.
|
||||||
|
|
||||||
|
2. You may modify your copy or copies of the Program or any portion
|
||||||
|
of it, thus forming a work based on the Program, and copy and
|
||||||
|
distribute such modifications or work under the terms of Section 1
|
||||||
|
above, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) You must cause the modified files to carry prominent notices
|
||||||
|
stating that you changed the files and the date of any change.
|
||||||
|
|
||||||
|
b) You must cause any work that you distribute or publish, that in
|
||||||
|
whole or in part contains or is derived from the Program or any
|
||||||
|
part thereof, to be licensed as a whole at no charge to all third
|
||||||
|
parties under the terms of this License.
|
||||||
|
|
||||||
|
c) If the modified program normally reads commands interactively
|
||||||
|
when run, you must cause it, when started running for such
|
||||||
|
interactive use in the most ordinary way, to print or display an
|
||||||
|
announcement including an appropriate copyright notice and a
|
||||||
|
notice that there is no warranty (or else, saying that you provide
|
||||||
|
a warranty) and that users may redistribute the program under
|
||||||
|
these conditions, and telling the user how to view a copy of this
|
||||||
|
License. (Exception: if the Program itself is interactive but
|
||||||
|
does not normally print such an announcement, your work based on
|
||||||
|
the Program is not required to print an announcement.)
|
||||||
|
|
||||||
|
These requirements apply to the modified work as a whole. If
|
||||||
|
identifiable sections of that work are not derived from the Program,
|
||||||
|
and can be reasonably considered independent and separate works in
|
||||||
|
themselves, then this License, and its terms, do not apply to those
|
||||||
|
sections when you distribute them as separate works. But when you
|
||||||
|
distribute the same sections as part of a whole which is a work based
|
||||||
|
on the Program, the distribution of the whole must be on the terms of
|
||||||
|
this License, whose permissions for other licensees extend to the
|
||||||
|
entire whole, and thus to each and every part regardless of who wrote it.
|
||||||
|
|
||||||
|
Thus, it is not the intent of this section to claim rights or contest
|
||||||
|
your rights to work written entirely by you; rather, the intent is to
|
||||||
|
exercise the right to control the distribution of derivative or
|
||||||
|
collective works based on the Program.
|
||||||
|
|
||||||
|
In addition, mere aggregation of another work not based on the Program
|
||||||
|
with the Program (or with a work based on the Program) on a volume of
|
||||||
|
a storage or distribution medium does not bring the other work under
|
||||||
|
the scope of this License.
|
||||||
|
|
||||||
|
3. You may copy and distribute the Program (or a work based on it,
|
||||||
|
under Section 2) in object code or executable form under the terms of
|
||||||
|
Sections 1 and 2 above provided that you also do one of the following:
|
||||||
|
|
||||||
|
a) Accompany it with the complete corresponding machine-readable
|
||||||
|
source code, which must be distributed under the terms of Sections
|
||||||
|
1 and 2 above on a medium customarily used for software interchange; or,
|
||||||
|
|
||||||
|
b) Accompany it with a written offer, valid for at least three
|
||||||
|
years, to give any third party, for a charge no more than your
|
||||||
|
cost of physically performing source distribution, a complete
|
||||||
|
machine-readable copy of the corresponding source code, to be
|
||||||
|
distributed under the terms of Sections 1 and 2 above on a medium
|
||||||
|
customarily used for software interchange; or,
|
||||||
|
|
||||||
|
c) Accompany it with the information you received as to the offer
|
||||||
|
to distribute corresponding source code. (This alternative is
|
||||||
|
allowed only for noncommercial distribution and only if you
|
||||||
|
received the program in object code or executable form with such
|
||||||
|
an offer, in accord with Subsection b above.)
|
||||||
|
|
||||||
|
The source code for a work means the preferred form of the work for
|
||||||
|
making modifications to it. For an executable work, complete source
|
||||||
|
code means all the source code for all modules it contains, plus any
|
||||||
|
associated interface definition files, plus the scripts used to
|
||||||
|
control compilation and installation of the executable. However, as a
|
||||||
|
special exception, the source code distributed need not include
|
||||||
|
anything that is normally distributed (in either source or binary
|
||||||
|
form) with the major components (compiler, kernel, and so on) of the
|
||||||
|
operating system on which the executable runs, unless that component
|
||||||
|
itself accompanies the executable.
|
||||||
|
|
||||||
|
If distribution of executable or object code is made by offering
|
||||||
|
access to copy from a designated place, then offering equivalent
|
||||||
|
access to copy the source code from the same place counts as
|
||||||
|
distribution of the source code, even though third parties are not
|
||||||
|
compelled to copy the source along with the object code.
|
||||||
|
|
||||||
|
4. You may not copy, modify, sublicense, or distribute the Program
|
||||||
|
except as expressly provided under this License. Any attempt
|
||||||
|
otherwise to copy, modify, sublicense or distribute the Program is
|
||||||
|
void, and will automatically terminate your rights under this License.
|
||||||
|
However, parties who have received copies, or rights, from you under
|
||||||
|
this License will not have their licenses terminated so long as such
|
||||||
|
parties remain in full compliance.
|
||||||
|
|
||||||
|
5. You are not required to accept this License, since you have not
|
||||||
|
signed it. However, nothing else grants you permission to modify or
|
||||||
|
distribute the Program or its derivative works. These actions are
|
||||||
|
prohibited by law if you do not accept this License. Therefore, by
|
||||||
|
modifying or distributing the Program (or any work based on the
|
||||||
|
Program), you indicate your acceptance of this License to do so, and
|
||||||
|
all its terms and conditions for copying, distributing or modifying
|
||||||
|
the Program or works based on it.
|
||||||
|
|
||||||
|
6. Each time you redistribute the Program (or any work based on the
|
||||||
|
Program), the recipient automatically receives a license from the
|
||||||
|
original licensor to copy, distribute or modify the Program subject to
|
||||||
|
these terms and conditions. You may not impose any further
|
||||||
|
restrictions on the recipients' exercise of the rights granted herein.
|
||||||
|
You are not responsible for enforcing compliance by third parties to
|
||||||
|
this License.
|
||||||
|
|
||||||
|
7. If, as a consequence of a court judgment or allegation of patent
|
||||||
|
infringement or for any other reason (not limited to patent issues),
|
||||||
|
conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot
|
||||||
|
distribute so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you
|
||||||
|
may not distribute the Program at all. For example, if a patent
|
||||||
|
license would not permit royalty-free redistribution of the Program by
|
||||||
|
all those who receive copies directly or indirectly through you, then
|
||||||
|
the only way you could satisfy both it and this License would be to
|
||||||
|
refrain entirely from distribution of the Program.
|
||||||
|
|
||||||
|
If any portion of this section is held invalid or unenforceable under
|
||||||
|
any particular circumstance, the balance of the section is intended to
|
||||||
|
apply and the section as a whole is intended to apply in other
|
||||||
|
circumstances.
|
||||||
|
|
||||||
|
It is not the purpose of this section to induce you to infringe any
|
||||||
|
patents or other property right claims or to contest validity of any
|
||||||
|
such claims; this section has the sole purpose of protecting the
|
||||||
|
integrity of the free software distribution system, which is
|
||||||
|
implemented by public license practices. Many people have made
|
||||||
|
generous contributions to the wide range of software distributed
|
||||||
|
through that system in reliance on consistent application of that
|
||||||
|
system; it is up to the author/donor to decide if he or she is willing
|
||||||
|
to distribute software through any other system and a licensee cannot
|
||||||
|
impose that choice.
|
||||||
|
|
||||||
|
This section is intended to make thoroughly clear what is believed to
|
||||||
|
be a consequence of the rest of this License.
|
||||||
|
|
||||||
|
8. If the distribution and/or use of the Program is restricted in
|
||||||
|
certain countries either by patents or by copyrighted interfaces, the
|
||||||
|
original copyright holder who places the Program under this License
|
||||||
|
may add an explicit geographical distribution limitation excluding
|
||||||
|
those countries, so that distribution is permitted only in or among
|
||||||
|
countries not thus excluded. In such case, this License incorporates
|
||||||
|
the limitation as if written in the body of this License.
|
||||||
|
|
||||||
|
9. The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the Program
|
||||||
|
specifies a version number of this License which applies to it and "any
|
||||||
|
later version", you have the option of following the terms and conditions
|
||||||
|
either of that version or of any later version published by the Free
|
||||||
|
Software Foundation. If the Program does not specify a version number of
|
||||||
|
this License, you may choose any version ever published by the Free Software
|
||||||
|
Foundation.
|
||||||
|
|
||||||
|
10. If you wish to incorporate parts of the Program into other free
|
||||||
|
programs whose distribution conditions are different, write to the author
|
||||||
|
to ask for permission. For software which is copyrighted by the Free
|
||||||
|
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||||
|
make exceptions for this. Our decision will be guided by the two goals
|
||||||
|
of preserving the free status of all derivatives of our free software and
|
||||||
|
of promoting the sharing and reuse of software generally.
|
||||||
|
|
||||||
|
NO WARRANTY
|
||||||
|
|
||||||
|
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||||
|
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||||
|
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||||
|
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||||
|
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||||
|
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||||
|
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||||
|
REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||||
|
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||||
|
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||||
|
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||||
|
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||||
|
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGES.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
convey the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program is interactive, make it output a short notice like this
|
||||||
|
when it starts in an interactive mode:
|
||||||
|
|
||||||
|
Gnomovision version 69, Copyright (C) year name of author
|
||||||
|
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, the commands you use may
|
||||||
|
be called something other than `show w' and `show c'; they could even be
|
||||||
|
mouse-clicks or menu items--whatever suits your program.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or your
|
||||||
|
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||||
|
necessary. Here is a sample; alter the names:
|
||||||
|
|
||||||
|
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||||
|
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||||
|
|
||||||
|
<signature of Ty Coon>, 1 April 1989
|
||||||
|
Ty Coon, President of Vice
|
||||||
|
|
||||||
|
This General Public License does not permit incorporating your program into
|
||||||
|
proprietary programs. If your program is a subroutine library, you may
|
||||||
|
consider it more useful to permit linking proprietary applications with the
|
||||||
|
library. If this is what you want to do, use the GNU Library General
|
||||||
|
Public License instead of this License.
|
||||||
@@ -22,3 +22,5 @@ include milter.rc
|
|||||||
include milter.rc7
|
include milter.rc7
|
||||||
include milter.cfg
|
include milter.cfg
|
||||||
include rhsbl.m4
|
include rhsbl.m4
|
||||||
|
include softfail.txt
|
||||||
|
include strike3.txt
|
||||||
|
|||||||
+25
-13
@@ -1,7 +1,8 @@
|
|||||||
|
|
||||||
# Author: Stuart D. Gathman <stuart@bmsi.com>
|
# Author: Stuart D. Gathman <stuart@bmsi.com>
|
||||||
# Copyright 2001 Business Management Systems, Inc.
|
# Copyright 2001 Business Management Systems, Inc.
|
||||||
# This code is under GPL. See COPYING for details.
|
# This code is under the GNU General Public License. See COPYING for details.
|
||||||
|
|
||||||
|
# A thin OO wrapper for the milter module
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import milter
|
import milter
|
||||||
@@ -140,16 +141,28 @@ def closecallback(ctx):
|
|||||||
m._setctx(None) # release milterContext
|
m._setctx(None) # release milterContext
|
||||||
return rc
|
return rc
|
||||||
|
|
||||||
|
def dictfromlist(args):
|
||||||
|
"Convert ESMTP parm list to keyword dictionary."
|
||||||
|
kw = {}
|
||||||
|
for s in args:
|
||||||
|
pos = s.find('=')
|
||||||
|
if pos > 0:
|
||||||
|
kw[s[:pos].upper()] = s[pos+1:]
|
||||||
|
return kw
|
||||||
|
|
||||||
def envcallback(c,args):
|
def envcallback(c,args):
|
||||||
"""Convert ESMTP parms to keyword parameters.
|
"""Call function c with ESMTP parms converted to keyword parameters.
|
||||||
Can be used in the envfrom and/or envrcpt callbacks to process
|
Can be used in the envfrom and/or envrcpt callbacks to process
|
||||||
ESMTP parameters as python keyword parameters."""
|
ESMTP parameters as python keyword parameters."""
|
||||||
kw = {}
|
kw = {}
|
||||||
|
pargs = [args[0]]
|
||||||
for s in args[1:]:
|
for s in args[1:]:
|
||||||
pos = s.find('=')
|
pos = s.find('=')
|
||||||
if pos > 0:
|
if pos > 0:
|
||||||
kw[s[:pos]] = s[pos+1:]
|
kw[s[:pos].upper()] = s[pos+1:]
|
||||||
return apply(c,args,kw)
|
else:
|
||||||
|
pargs.append(s)
|
||||||
|
return c(*pargs,**kw)
|
||||||
|
|
||||||
def runmilter(name,socketname,timeout = 0):
|
def runmilter(name,socketname,timeout = 0):
|
||||||
# This bit is here on the assumption that you will be starting this filter
|
# This bit is here on the assumption that you will be starting this filter
|
||||||
@@ -176,14 +189,13 @@ def runmilter(name,socketname,timeout = 0):
|
|||||||
# milter.set_flags(milter.ADDHDRS)
|
# milter.set_flags(milter.ADDHDRS)
|
||||||
milter.set_connect_callback(connectcallback)
|
milter.set_connect_callback(connectcallback)
|
||||||
milter.set_helo_callback(lambda ctx, host: ctx.getpriv().hello(host))
|
milter.set_helo_callback(lambda ctx, host: ctx.getpriv().hello(host))
|
||||||
milter.set_envfrom_callback(lambda ctx,*str:
|
# For envfrom and envrcpt, we would like to convert ESMTP parms to keyword
|
||||||
ctx.getpriv().envfrom(*str))
|
# parms, but then all existing users would have to include **kw to accept
|
||||||
# envcallback(ctx.getpriv().envfrom,str))
|
# arbitrary keywords without crashing. We do provide envcallback and
|
||||||
milter.set_envrcpt_callback(lambda ctx,*str:
|
# dictfromlist to make parsing the ESMTP args convenient.
|
||||||
ctx.getpriv().envrcpt(*str))
|
milter.set_envfrom_callback(lambda ctx,*str: ctx.getpriv().envfrom(*str))
|
||||||
# envcallback(ctx.getpriv().envrcpt,str))
|
milter.set_envrcpt_callback(lambda ctx,*str: ctx.getpriv().envrcpt(*str))
|
||||||
milter.set_header_callback(lambda ctx,fld,val:
|
milter.set_header_callback(lambda ctx,fld,val: ctx.getpriv().header(fld,val))
|
||||||
ctx.getpriv().header(fld,val))
|
|
||||||
milter.set_eoh_callback(lambda ctx: ctx.getpriv().eoh())
|
milter.set_eoh_callback(lambda ctx: ctx.getpriv().eoh())
|
||||||
milter.set_body_callback(lambda ctx,chunk: ctx.getpriv().body(chunk))
|
milter.set_body_callback(lambda ctx,chunk: ctx.getpriv().body(chunk))
|
||||||
milter.set_eom_callback(lambda ctx: ctx.getpriv().eom())
|
milter.set_eom_callback(lambda ctx: ctx.getpriv().eom())
|
||||||
|
|||||||
+37
-17
@@ -1,9 +1,18 @@
|
|||||||
|
# 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 smtplib
|
||||||
import spf
|
import spf
|
||||||
import socket
|
import socket
|
||||||
from email.Message import Message
|
from email.Message import Message
|
||||||
|
|
||||||
nospf_msg = """This is an automatically generated Delivery Status Notification.
|
nospf_msg = """Subject: Critical mail server configuration error
|
||||||
|
|
||||||
|
This is an automatically generated Delivery Status Notification.
|
||||||
|
|
||||||
THIS IS A WARNING MESSAGE ONLY.
|
THIS IS A WARNING MESSAGE ONLY.
|
||||||
|
|
||||||
@@ -65,11 +74,12 @@ If you need further assistance, please do not hesitate to
|
|||||||
contact me again.
|
contact me again.
|
||||||
|
|
||||||
Kind regards,
|
Kind regards,
|
||||||
Stuart D. Gathman
|
|
||||||
postmaster@%(receiver)s
|
postmaster@%(receiver)s
|
||||||
"""
|
"""
|
||||||
|
|
||||||
softfail_msg = """
|
softfail_msg = """Subject: SPF softfail (POSSIBLE FORGERY)
|
||||||
|
|
||||||
This is an automatically generated Delivery Status Notification.
|
This is an automatically generated Delivery Status Notification.
|
||||||
|
|
||||||
THIS IS A WARNING MESSAGE ONLY.
|
THIS IS A WARNING MESSAGE ONLY.
|
||||||
@@ -85,7 +95,10 @@ Received-SPF: %(spf_result)s
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def send_dsn(mailfrom,receiver,msg=None):
|
def send_dsn(mailfrom,receiver,msg=None):
|
||||||
"Send DSN. If msg is None, do callback verification."
|
"""Send DSN. If msg is None, do callback verification.
|
||||||
|
Mailfrom is original sender we are sending DSN or CBV to.
|
||||||
|
Receiver is the MTA sending the DSN.
|
||||||
|
Return None for success or (code,msg) for failure."""
|
||||||
user,domain = mailfrom.split('@')
|
user,domain = mailfrom.split('@')
|
||||||
q = spf.query(None,None,None)
|
q = spf.query(None,None,None)
|
||||||
mxlist = q.dns(domain,'MX')
|
mxlist = q.dns(domain,'MX')
|
||||||
@@ -102,7 +115,7 @@ def send_dsn(mailfrom,receiver,msg=None):
|
|||||||
if resp.split()[0] == receiver:
|
if resp.split()[0] == receiver:
|
||||||
return (553,'Fraudulent MX for %s' % domain)
|
return (553,'Fraudulent MX for %s' % domain)
|
||||||
if not (200 <= code <= 299):
|
if not (200 <= code <= 299):
|
||||||
raise SMTPHeloError(code, resp)
|
raise smtplib.SMTPHeloError(code, resp)
|
||||||
if msg:
|
if msg:
|
||||||
try:
|
try:
|
||||||
smtp.sendmail('<>',mailfrom,msg)
|
smtp.sendmail('<>',mailfrom,msg)
|
||||||
@@ -112,7 +125,7 @@ def send_dsn(mailfrom,receiver,msg=None):
|
|||||||
else: # CBV
|
else: # CBV
|
||||||
code,resp = smtp.docmd('MAIL FROM: <>')
|
code,resp = smtp.docmd('MAIL FROM: <>')
|
||||||
if code != 250:
|
if code != 250:
|
||||||
raise SMTPSenderRefused(code, resp, '<>')
|
raise smtplib.SMTPSenderRefused(code, resp, '<>')
|
||||||
code,resp = smtp.rcpt(mailfrom)
|
code,resp = smtp.rcpt(mailfrom)
|
||||||
if code not in (250,251):
|
if code not in (250,251):
|
||||||
return (code,resp) # permanent error
|
return (code,resp) # permanent error
|
||||||
@@ -121,9 +134,9 @@ def send_dsn(mailfrom,receiver,msg=None):
|
|||||||
except smtplib.SMTPRecipientsRefused,x:
|
except smtplib.SMTPRecipientsRefused,x:
|
||||||
return x.recipients[mailfrom] # permanent error
|
return x.recipients[mailfrom] # permanent error
|
||||||
except smtplib.SMTPSenderRefused,x:
|
except smtplib.SMTPSenderRefused,x:
|
||||||
return x # does not accept DSN
|
return x.args[:2] # does not accept DSN
|
||||||
except smtplib.SMTPDataError,x:
|
except smtplib.SMTPDataError,x:
|
||||||
return x # permanent error
|
return x.args # permanent error
|
||||||
except smtplib.SMTPException:
|
except smtplib.SMTPException:
|
||||||
pass # any other error, try next MX
|
pass # any other error, try next MX
|
||||||
except socket.error:
|
except socket.error:
|
||||||
@@ -131,7 +144,8 @@ def send_dsn(mailfrom,receiver,msg=None):
|
|||||||
smtp.close()
|
smtp.close()
|
||||||
return (450,'No MX servers available') # temp error
|
return (450,'No MX servers available') # temp error
|
||||||
|
|
||||||
def create_msg(q,rcptlist,origmsg):
|
def create_msg(q,rcptlist,origmsg=None,template=None):
|
||||||
|
"Create a DSN message from a template. Template must be '\n' separated."
|
||||||
heloname = q.h
|
heloname = q.h
|
||||||
sender = q.s
|
sender = q.s
|
||||||
connectip = q.i
|
connectip = q.i
|
||||||
@@ -145,24 +159,30 @@ def create_msg(q,rcptlist,origmsg):
|
|||||||
if not spf_result.startswith('softfail'):
|
if not spf_result.startswith('softfail'):
|
||||||
spf_result = None
|
spf_result = None
|
||||||
except: spf_result = None
|
except: spf_result = None
|
||||||
|
|
||||||
msg = Message()
|
msg = Message()
|
||||||
|
|
||||||
msg.add_header('To',sender)
|
msg.add_header('To',sender)
|
||||||
msg.add_header('From','postmaster@%s'%receiver)
|
msg.add_header('From','postmaster@%s'%receiver)
|
||||||
msg.add_header('Auto-Submitted','auto-generated (configuration error)')
|
msg.add_header('Auto-Submitted','auto-generated (configuration error)')
|
||||||
msg.set_type('text/plain')
|
msg.set_type('text/plain')
|
||||||
if spf_result:
|
|
||||||
msg.add_header('Subject','SPF softfail (POSSIBLE FORGERY)')
|
if not template:
|
||||||
msg.set_payload(softfail_msg % locals())
|
if spf_result: template = softfail_msg
|
||||||
else:
|
else: template = nospf_msg
|
||||||
msg.add_header('Subject','Critical mail server configuration error')
|
hdrs,body = template.split('\n',1)
|
||||||
msg.set_payload(nospf_msg % locals())
|
for ln in hdrs.splitlines():
|
||||||
|
name,val = ln.split(':',1)
|
||||||
|
msg.add_header(name,(val % locals()).strip())
|
||||||
|
msg.set_payload(body % locals())
|
||||||
|
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
q = spf.query('192.168.9.50',
|
q = spf.query('192.168.9.50',
|
||||||
'SRS0=pmeHL=RH=bmsi.com=stuart@bmsi.com',
|
'SRS0=pmeHL=RH=bmsi.com=stuart@bmsi.com',
|
||||||
'bmsred.bmsi.com',receiver='mail.bmsi.com')
|
'bmsred.bmsi.com',receiver='mail.bmsi.com')
|
||||||
msg = create_msg(q,'charlie@jsconnor.com')
|
msg = create_msg(q,['charlie@jsconnor.com'],None,None)
|
||||||
#print msg.as_string()
|
print msg.as_string()
|
||||||
# print send_dsn(f,msg.as_string())
|
# print send_dsn(f,msg.as_string())
|
||||||
print send_dsn(q.s,'mail.bmsi.com',msg.as_string())
|
print send_dsn(q.s,'mail.bmsi.com',msg.as_string())
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
# 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:
|
# examples we don't yet recognize:
|
||||||
#
|
#
|
||||||
# wiley-268-8196.roadrunner.nf.net at ('205.251.174.46', 4810)
|
# wiley-268-8196.roadrunner.nf.net at ('205.251.174.46', 4810)
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
Here is a history of user visible changes to Python milter.
|
Here is a history of user visible changes to Python milter.
|
||||||
|
|
||||||
|
0.8.1 Fix zip in zip loop in mime.py
|
||||||
|
Fix HeaderParseError in bms.py header callback
|
||||||
|
Check internal_domains for outgoing mail
|
||||||
|
Fix inconsistent results from send_dsn
|
||||||
|
0.8.0 Move Milter module to subpackage.
|
||||||
|
DSN support for Three strikes rule and SPF SOFTFAIL
|
||||||
|
Move /*mime*/ and dynip to Milter subpackage
|
||||||
|
Fix SPF unknown mechanism list not cleared
|
||||||
|
Make banned extensions configurable.
|
||||||
|
Option to scan zipfiles for bad extensions.
|
||||||
|
Properly log pydspam exceptions
|
||||||
|
0.7.3 Experimental release with python2.4 support
|
||||||
0.7.2 Return unknown for invalid ip address in mechanism
|
0.7.2 Return unknown for invalid ip address in mechanism
|
||||||
Recognize dynamic PTR names, and don't count them as authentication.
|
Recognize dynamic PTR names, and don't count them as authentication.
|
||||||
Three strikes and yer out rule.
|
Three strikes and yer out rule.
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
Defer TEMPERROR in SPF evaluation - give precedence to security
|
Defer TEMPERROR in SPF evaluation - give precedence to security
|
||||||
(only defer for PASS mechanisms).
|
(only defer for PASS mechanisms).
|
||||||
|
|
||||||
Allow multiple recipients for MAIL FROM: <> by default.
|
|
||||||
|
|
||||||
Option to add Received-SPF header, but never reject on SPF.
|
Option to add Received-SPF header, but never reject on SPF.
|
||||||
|
|
||||||
Option to configure banned extension list for mime.py. Default to empty.
|
|
||||||
|
|
||||||
Create null config that does nothing - except maybe add Received-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.
|
headers. Many admins would like to turn features on one at a time.
|
||||||
|
|
||||||
@@ -27,8 +23,6 @@ or recipient prefix.
|
|||||||
|
|
||||||
Can't output messages with malformed rfc822 attachments.
|
Can't output messages with malformed rfc822 attachments.
|
||||||
|
|
||||||
Use python exceptions in SPF to cleanly handle unknown and error results.
|
|
||||||
|
|
||||||
Example malformed SPF:
|
Example malformed SPF:
|
||||||
onvunvuvvx.usafisnews.org text "v=spf1 mx ptr ip4:207.44.199.970 -all"
|
onvunvuvvx.usafisnews.org text "v=spf1 mx ptr ip4:207.44.199.970 -all"
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,34 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# A simple milter.
|
# A simple milter that has grown quite a bit.
|
||||||
# $Log$
|
# $Log$
|
||||||
|
# Revision 1.10 2005/06/16 18:35:51 customdesigned
|
||||||
|
# Ignore HeaderParseError decoding header
|
||||||
|
#
|
||||||
|
# Revision 1.9 2005/06/14 21:55:29 customdesigned
|
||||||
|
# Check internal_domains for outgoing mail.
|
||||||
|
#
|
||||||
|
# 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
|
# Revision 1.134 2005/05/25 15:36:43 stuart
|
||||||
# Use dynip module.
|
# Use dynip module.
|
||||||
# Support smart aliasing of wiretap destination.
|
# Support smart aliasing of wiretap destination.
|
||||||
@@ -179,8 +207,8 @@
|
|||||||
# Release 0.6.4
|
# Release 0.6.4
|
||||||
#
|
#
|
||||||
# Author: Stuart D. Gathman <stuart@bmsi.com>
|
# Author: Stuart D. Gathman <stuart@bmsi.com>
|
||||||
# Copyright 2001 Business Management Systems, Inc.
|
# Copyright 2001,2002,2003,2004,2005 Business Management Systems, Inc.
|
||||||
# This code is under GPL. See COPYING for details.
|
# This code is under the GNU General Public License. See COPYING for details.
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
@@ -190,6 +218,7 @@ import mime
|
|||||||
import email.Errors
|
import email.Errors
|
||||||
import Milter
|
import Milter
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import traceback
|
||||||
import ConfigParser
|
import ConfigParser
|
||||||
import time
|
import time
|
||||||
import re
|
import re
|
||||||
@@ -229,6 +258,8 @@ log_headers = False
|
|||||||
block_chinese = False
|
block_chinese = False
|
||||||
spam_words = ()
|
spam_words = ()
|
||||||
porn_words = ()
|
porn_words = ()
|
||||||
|
banned_exts = mime.extlist.split(',')
|
||||||
|
scan_zip = False
|
||||||
scan_html = True
|
scan_html = True
|
||||||
scan_rfc822 = True
|
scan_rfc822 = True
|
||||||
internal_connect = ()
|
internal_connect = ()
|
||||||
@@ -252,11 +283,21 @@ spf_reject_neutral = ()
|
|||||||
spf_accept_softfail = ()
|
spf_accept_softfail = ()
|
||||||
spf_best_guess = False
|
spf_best_guess = False
|
||||||
spf_reject_noptr = False
|
spf_reject_noptr = False
|
||||||
|
multiple_bounce_recipients = True
|
||||||
|
time_format = '%Y%b%d %H:%M:%S %Z'
|
||||||
timeout = 600
|
timeout = 600
|
||||||
cbv_cache = {}
|
cbv_cache = {}
|
||||||
try:
|
try:
|
||||||
for rcpt in open('send_dsn.log'):
|
too_old = time.time() - 30*24*60*60 # 30 days
|
||||||
cbv_cache[rcpt.strip()] = None
|
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
|
except IOError: pass
|
||||||
|
|
||||||
class MilterConfigParser(ConfigParser.ConfigParser):
|
class MilterConfigParser(ConfigParser.ConfigParser):
|
||||||
@@ -264,7 +305,7 @@ class MilterConfigParser(ConfigParser.ConfigParser):
|
|||||||
def getlist(self,sect,opt):
|
def getlist(self,sect,opt):
|
||||||
if self.has_option(sect,opt):
|
if self.has_option(sect,opt):
|
||||||
return [q.strip() for q in self.get(sect,opt).split(',')]
|
return [q.strip() for q in self.get(sect,opt).split(',')]
|
||||||
return ()
|
return []
|
||||||
|
|
||||||
def getaddrset(self,sect,opt):
|
def getaddrset(self,sect,opt):
|
||||||
if not self.has_option(sect,opt):
|
if not self.has_option(sect,opt):
|
||||||
@@ -311,6 +352,7 @@ def read_config(list):
|
|||||||
'timeout': '600',
|
'timeout': '600',
|
||||||
'scan_html': 'no',
|
'scan_html': 'no',
|
||||||
'scan_rfc822': 'yes',
|
'scan_rfc822': 'yes',
|
||||||
|
'scan_zip': 'no',
|
||||||
'block_chinese': 'no',
|
'block_chinese': 'no',
|
||||||
'log_headers': 'no',
|
'log_headers': 'no',
|
||||||
'blind_wiretap': 'yes',
|
'blind_wiretap': 'yes',
|
||||||
@@ -322,19 +364,44 @@ def read_config(list):
|
|||||||
'dspam_internal': 'yes'
|
'dspam_internal': 'yes'
|
||||||
})
|
})
|
||||||
cp.read(list)
|
cp.read(list)
|
||||||
|
|
||||||
|
# milter section
|
||||||
tempfile.tempdir = cp.get('milter','tempdir')
|
tempfile.tempdir = cp.get('milter','tempdir')
|
||||||
global socketname, scan_rfc822, scan_html, block_chinese, timeout
|
global socketname, timeout, check_user, log_headers
|
||||||
|
global internal_connect, internal_domains, trusted_relay, hello_blacklist
|
||||||
socketname = cp.get('milter','socket')
|
socketname = cp.get('milter','socket')
|
||||||
timeout = cp.getint('milter','timeout')
|
timeout = cp.getint('milter','timeout')
|
||||||
scan_rfc822 = cp.getboolean('milter','scan_rfc822')
|
check_user = cp.getaddrset('milter','check_user')
|
||||||
scan_html = cp.getboolean('milter','scan_html')
|
|
||||||
block_chinese = cp.getboolean('milter','block_chinese')
|
|
||||||
|
|
||||||
global hide_path, block_forward, log_headers
|
|
||||||
hide_path = cp.getlist('scrub','hide_path')
|
|
||||||
block_forward = cp.getaddrset('milter','block_forward')
|
|
||||||
log_headers = cp.getboolean('milter','log_headers')
|
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
|
global blind_wiretap, wiretap_users, wiretap_dest, discard_users
|
||||||
blind_wiretap = cp.getboolean('wiretap','blind')
|
blind_wiretap = cp.getboolean('wiretap','blind')
|
||||||
wiretap_users = cp.getaddrset('wiretap','users')
|
wiretap_users = cp.getaddrset('wiretap','users')
|
||||||
@@ -342,17 +409,7 @@ def read_config(list):
|
|||||||
wiretap_dest = cp.getdefault('wiretap','dest')
|
wiretap_dest = cp.getdefault('wiretap','dest')
|
||||||
if wiretap_dest: wiretap_dest = '<%s>' % wiretap_dest
|
if wiretap_dest: wiretap_dest = '<%s>' % wiretap_dest
|
||||||
|
|
||||||
global check_user, reject_virus_from, internal_connect, internal_domains
|
global smart_alias
|
||||||
check_user = cp.getaddrset('milter','check_user')
|
|
||||||
reject_virus_from = cp.getlist('scrub','reject_virus_from')
|
|
||||||
internal_connect = cp.getlist('milter','internal_connect')
|
|
||||||
internal_domains = cp.getlist('milter','internal_domains')
|
|
||||||
|
|
||||||
global porn_words, spam_words, smart_alias, trusted_relay, hello_blacklist
|
|
||||||
trusted_relay = cp.getlist('milter','trusted_relay')
|
|
||||||
porn_words = cp.getlist('milter','porn_words')
|
|
||||||
spam_words = cp.getlist('milter','spam_words')
|
|
||||||
hello_blacklist = cp.getlist('milter','hello_blacklist')
|
|
||||||
for sa in cp.getlist('wiretap','smart_alias'):
|
for sa in cp.getlist('wiretap','smart_alias'):
|
||||||
sm = cp.getlist('wiretap',sa)
|
sm = cp.getlist('wiretap',sa)
|
||||||
if len(sm) < 2:
|
if len(sm) < 2:
|
||||||
@@ -362,10 +419,9 @@ def read_config(list):
|
|||||||
key = (sm[0],sm[1])
|
key = (sm[0],sm[1])
|
||||||
smart_alias[key] = sm[2:]
|
smart_alias[key] = sm[2:]
|
||||||
|
|
||||||
|
# dspam section
|
||||||
global dspam_dict, dspam_users, dspam_userdir, dspam_exempt, dspam_internal
|
global dspam_dict, dspam_users, dspam_userdir, dspam_exempt, dspam_internal
|
||||||
global dspam_screener,dspam_whitelist,dspam_reject,dspam_sizelimit
|
global dspam_screener,dspam_whitelist,dspam_reject,dspam_sizelimit
|
||||||
global spf_reject_neutral,spf_best_guess,SRS,spf_reject_noptr
|
|
||||||
global spf_accept_softfail
|
|
||||||
dspam_dict = cp.getdefault('dspam','dspam_dict')
|
dspam_dict = cp.getdefault('dspam','dspam_dict')
|
||||||
dspam_exempt = cp.getaddrset('dspam','dspam_exempt')
|
dspam_exempt = cp.getaddrset('dspam','dspam_exempt')
|
||||||
dspam_whitelist = cp.getaddrset('dspam','dspam_whitelist')
|
dspam_whitelist = cp.getaddrset('dspam','dspam_whitelist')
|
||||||
@@ -377,6 +433,9 @@ def read_config(list):
|
|||||||
if cp.has_option('dspam','dspam_sizelimit'):
|
if cp.has_option('dspam','dspam_sizelimit'):
|
||||||
dspam_sizelimit = cp.getint('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:
|
if spf:
|
||||||
spf.DELEGATE = cp.getdefault('spf','delegate')
|
spf.DELEGATE = cp.getdefault('spf','delegate')
|
||||||
spf_reject_neutral = cp.getlist('spf','reject_neutral')
|
spf_reject_neutral = cp.getlist('spf','reject_neutral')
|
||||||
@@ -407,9 +466,9 @@ def parse_addr(t):
|
|||||||
return t.split('@')
|
return t.split('@')
|
||||||
|
|
||||||
def parse_header(val):
|
def parse_header(val):
|
||||||
|
try:
|
||||||
h = decode_header(val)
|
h = decode_header(val)
|
||||||
if not len(h) or (not h[0][1] and len(h) == 1): return val
|
if not len(h) or (not h[0][1] and len(h) == 1): return val
|
||||||
try:
|
|
||||||
u = []
|
u = []
|
||||||
for s,enc in h:
|
for s,enc in h:
|
||||||
if enc:
|
if enc:
|
||||||
@@ -426,6 +485,7 @@ def parse_header(val):
|
|||||||
except UnicodeError: continue
|
except UnicodeError: continue
|
||||||
except UnicodeDecodeError: pass
|
except UnicodeDecodeError: pass
|
||||||
except LookupError: pass
|
except LookupError: pass
|
||||||
|
except email.errors.HeaderParseError: pass
|
||||||
return val
|
return val
|
||||||
|
|
||||||
class bmsMilter(Milter.Milter):
|
class bmsMilter(Milter.Milter):
|
||||||
@@ -570,6 +630,17 @@ class bmsMilter(Milter.Milter):
|
|||||||
self.log("REJECT: spam from self",pat)
|
self.log("REJECT: spam from self",pat)
|
||||||
self.setreply('550','5.7.1','I hate talking to myself.')
|
self.setreply('550','5.7.1','I hate talking to myself.')
|
||||||
return Milter.REJECT
|
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
|
self.rejectvirus = domain in reject_virus_from
|
||||||
if user in wiretap_users.get(domain,()):
|
if user in wiretap_users.get(domain,()):
|
||||||
self.add_recipient(wiretap_dest)
|
self.add_recipient(wiretap_dest)
|
||||||
@@ -637,6 +708,7 @@ class bmsMilter(Milter.Milter):
|
|||||||
)
|
)
|
||||||
return Milter.REJECT
|
return Milter.REJECT
|
||||||
if self.mailfrom != '<>':
|
if self.mailfrom != '<>':
|
||||||
|
q.result = res
|
||||||
self.cbv_needed = q
|
self.cbv_needed = q
|
||||||
if res in ('deny', 'fail'):
|
if res in ('deny', 'fail'):
|
||||||
self.log('REJECT: SPF %s %i %s' % (res,code,txt))
|
self.log('REJECT: SPF %s %i %s' % (res,code,txt))
|
||||||
@@ -658,6 +730,7 @@ class bmsMilter(Milter.Milter):
|
|||||||
)
|
)
|
||||||
return Milter.REJECT
|
return Milter.REJECT
|
||||||
if self.mailfrom != '<>':
|
if self.mailfrom != '<>':
|
||||||
|
q.result = res
|
||||||
self.cbv_needed = q
|
self.cbv_needed = q
|
||||||
if res == 'neutral' and q.o in spf_reject_neutral:
|
if res == 'neutral' and q.o in spf_reject_neutral:
|
||||||
self.log('REJECT: SPF neutral for',q.s)
|
self.log('REJECT: SPF neutral for',q.s)
|
||||||
@@ -673,7 +746,8 @@ class bmsMilter(Milter.Milter):
|
|||||||
if res == 'error':
|
if res == 'error':
|
||||||
if code >= 500:
|
if code >= 500:
|
||||||
self.log('REJECT: SPF %s %i %s' % (res,code,txt))
|
self.log('REJECT: SPF %s %i %s' % (res,code,txt))
|
||||||
self.setreply(str(code),'5.7.1',txt)
|
# latest SPF draft recommends 5.5.2 instead of 5.7.1
|
||||||
|
self.setreply(str(code),'5.5.2',txt)
|
||||||
return Milter.REJECT
|
return Milter.REJECT
|
||||||
self.log('TEMPFAIL: SPF %s %i %s' % (res,code,txt))
|
self.log('TEMPFAIL: SPF %s %i %s' % (res,code,txt))
|
||||||
self.setreply(str(code),'4.3.0',txt)
|
self.setreply(str(code),'4.3.0',txt)
|
||||||
@@ -695,7 +769,7 @@ class bmsMilter(Milter.Milter):
|
|||||||
user,domain = t
|
user,domain = t
|
||||||
if self.mailfrom == '<>' or self.canon_from.startswith('postmaster@') \
|
if self.mailfrom == '<>' or self.canon_from.startswith('postmaster@') \
|
||||||
or self.canon_from.startswith('mailer-daemon@'):
|
or self.canon_from.startswith('mailer-daemon@'):
|
||||||
if self.recipients:
|
if self.recipients and not multiple_bounce_recipients:
|
||||||
self.data_allowed = False
|
self.data_allowed = False
|
||||||
if srs and domain == srs_fwdomain:
|
if srs and domain == srs_fwdomain:
|
||||||
oldaddr = '@'.join(parse_addr(to))
|
oldaddr = '@'.join(parse_addr(to))
|
||||||
@@ -843,8 +917,9 @@ class bmsMilter(Milter.Milter):
|
|||||||
# copy headers to a temp file for scanning the body
|
# copy headers to a temp file for scanning the body
|
||||||
headers = self.fp.getvalue()
|
headers = self.fp.getvalue()
|
||||||
self.fp.close()
|
self.fp.close()
|
||||||
self.tempname = fname = tempfile.mktemp(".defang")
|
fd,fname = tempfile.mkstemp(".defang")
|
||||||
self.fp = open(fname,"w+b")
|
self.tempname = fname
|
||||||
|
self.fp = os.fdopen(fd,"w+b")
|
||||||
self.fp.write(headers) # IOError (e.g. disk full) causes TEMPFAIL
|
self.fp.write(headers) # IOError (e.g. disk full) causes TEMPFAIL
|
||||||
# check if headers are really spammy
|
# check if headers are really spammy
|
||||||
if dspam_dict and not self.internal_connection:
|
if dspam_dict and not self.internal_connection:
|
||||||
@@ -876,11 +951,22 @@ class bmsMilter(Milter.Milter):
|
|||||||
for i in range(len(h),0,-1):
|
for i in range(len(h),0,-1):
|
||||||
self.chgheader(name,i-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):
|
def _chk_attach(self,msg):
|
||||||
"Filter attachments by content."
|
"Filter attachments by content."
|
||||||
mime.check_name(msg,self.tempname) # check for bad extensions
|
# 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:
|
if scan_html:
|
||||||
mime.check_html(msg,self.tempname) # remove scripts from HTML
|
mime.check_html(msg,self.tempname)
|
||||||
# don't let a tricky virus slip one past us
|
# don't let a tricky virus slip one past us
|
||||||
if scan_rfc822:
|
if scan_rfc822:
|
||||||
msg = msg.get_submsg()
|
msg = msg.get_submsg()
|
||||||
@@ -953,7 +1039,8 @@ class bmsMilter(Milter.Milter):
|
|||||||
self.fp = StringIO.StringIO(txt)
|
self.fp = StringIO.StringIO(txt)
|
||||||
modified = True
|
modified = True
|
||||||
except Exception,x:
|
except Exception,x:
|
||||||
print x
|
self.log("check_spam:",x)
|
||||||
|
traceback.print_exc()
|
||||||
# screen if no recipients are dspam_users
|
# screen if no recipients are dspam_users
|
||||||
if not modified and dspam_screener and not self.internal_connection \
|
if not modified and dspam_screener and not self.internal_connection \
|
||||||
and self.dspam:
|
and self.dspam:
|
||||||
@@ -993,6 +1080,7 @@ class bmsMilter(Milter.Milter):
|
|||||||
|
|
||||||
# filter leaf attachments through _chk_attach
|
# filter leaf attachments through _chk_attach
|
||||||
assert not msg.ismodified()
|
assert not msg.ismodified()
|
||||||
|
self.bad_extensions = ['.' + x for x in banned_exts]
|
||||||
rc = mime.check_attachments(msg,self._chk_attach)
|
rc = mime.check_attachments(msg,self._chk_attach)
|
||||||
except: # milter crashed trying to analyze mail
|
except: # milter crashed trying to analyze mail
|
||||||
exc_type,exc_value = sys.exc_info()[0:2]
|
exc_type,exc_value = sys.exc_info()[0:2]
|
||||||
@@ -1048,14 +1136,21 @@ class bmsMilter(Milter.Milter):
|
|||||||
self.addheader(name,val)
|
self.addheader(name,val)
|
||||||
|
|
||||||
if self.cbv_needed:
|
if self.cbv_needed:
|
||||||
sender = self.cbv_needed.s
|
q = self.cbv_needed
|
||||||
|
sender = q.s
|
||||||
cached = cbv_cache.has_key(sender)
|
cached = cbv_cache.has_key(sender)
|
||||||
if cached:
|
if cached:
|
||||||
self.log('CBV:',sender,'(cached)')
|
self.log('CBV:',sender,'(cached)')
|
||||||
res = cbv_cache[sender]
|
res = cbv_cache[sender]
|
||||||
else:
|
else:
|
||||||
self.log('CBV:',sender)
|
self.log('CBV:',sender)
|
||||||
m = dsn.create_msg(self.cbv_needed,self.recipients,msg)
|
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()
|
m = m.as_string()
|
||||||
print >>open('last_dsn','w'),m
|
print >>open('last_dsn','w'),m
|
||||||
res = dsn.send_dsn(sender,self.receiver,m)
|
res = dsn.send_dsn(sender,self.receiver,m)
|
||||||
@@ -1065,13 +1160,15 @@ class bmsMilter(Milter.Milter):
|
|||||||
self.log('TEMPFAIL:',desc)
|
self.log('TEMPFAIL:',desc)
|
||||||
self.setreply('450','4.2.0',*desc.splitlines())
|
self.setreply('450','4.2.0',*desc.splitlines())
|
||||||
return Milter.TEMPFAIL
|
return Milter.TEMPFAIL
|
||||||
|
if len(res) < 3: res += time.time(),
|
||||||
cbv_cache[sender] = res
|
cbv_cache[sender] = res
|
||||||
self.log('REJECT:',desc)
|
self.log('REJECT:',desc)
|
||||||
self.setreply('550','5.7.1',*desc.splitlines())
|
self.setreply('550','5.7.1',*desc.splitlines())
|
||||||
return Milter.REJECT
|
return Milter.REJECT
|
||||||
cbv_cache[sender] = res
|
cbv_cache[sender] = res
|
||||||
if not cached:
|
if not cached:
|
||||||
print >>open('send_dsn.log','a'),sender # log who we sent DSNs to
|
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
|
self.cbv_needed = None
|
||||||
|
|
||||||
if not defanged and not spam_checked:
|
if not defanged and not spam_checked:
|
||||||
|
|||||||
@@ -72,6 +72,9 @@ milter-0.4.5 or later to remove this dependency.
|
|||||||
<code>set_flags()</code> before calling <code>runmilter()</code>. For
|
<code>set_flags()</code> before calling <code>runmilter()</code>. For
|
||||||
instance, <code>Milter.set_flags(Milter.ADDRCPT)</code>. You must add together
|
instance, <code>Milter.set_flags(Milter.ADDRCPT)</code>. You must add together
|
||||||
all of <code>ADDHDRS, CHGBODY, ADDRCPT, DELRCPT, CHGHDRS</code> that apply.
|
all of <code>ADDHDRS, CHGBODY, ADDRCPT, DELRCPT, CHGHDRS</code> that apply.
|
||||||
|
<p> NOTE - recent versions default flags to enabling all features. You
|
||||||
|
must now call <code>set_flags()</code> if you wish to disable features for
|
||||||
|
efficiency.
|
||||||
<p>
|
<p>
|
||||||
|
|
||||||
<li> Q. Why does sendmail sometimes print something like:
|
<li> Q. Why does sendmail sometimes print something like:
|
||||||
@@ -94,14 +97,19 @@ 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
|
code out of the sample as the project evolves. Think of sample.py as
|
||||||
an active config file.
|
an active config file.
|
||||||
<p>
|
<p>
|
||||||
|
If you are running bms.py, then the block_chinese option in
|
||||||
|
<code>/etc/mail/pymilter.cfg</code> controls this feature.
|
||||||
|
<p>
|
||||||
|
|
||||||
<li> Q. Why does sendmail coredump with milters on OpenBSD?
|
<li> Q. Why does sendmail coredump with milters on OpenBSD?
|
||||||
<p> A. Sendmail has a problem with unix sockets on OpenBSD. Use
|
<p> A. Sendmail has a problem with unix sockets on old versions of OpenBSD.
|
||||||
an internet domain socket instead. For example, in <code>sendmail.cf</code> use
|
Use an internet domain socket instead. For example, in
|
||||||
|
<code>sendmail.cf</code> use
|
||||||
<pre>
|
<pre>
|
||||||
Xpythonfilter, S=inet:1234@localhost
|
Xpythonfilter, S=inet:1234@localhost
|
||||||
</pre>
|
</pre>
|
||||||
and change sample.py accordingly.
|
and change sample.py accordingly.
|
||||||
|
<p> OpenBSD users report that this problem has been fixed.
|
||||||
<p>
|
<p>
|
||||||
|
|
||||||
<li> Q. How can I change the bounce message for an invalid recipient?
|
<li> Q. How can I change the bounce message for an invalid recipient?
|
||||||
@@ -143,8 +151,8 @@ everything up for you. For other systems:
|
|||||||
<li> Arrange to run bms.py in the background (as a service perhaps) and
|
<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
|
redirect output and errors to a logfile. For instance, on AIX you'll want
|
||||||
to use SRC (System Resource Controller).
|
to use SRC (System Resource Controller).
|
||||||
<li> Copy milter.cfg to the directory you run bms.py in, and edit it. The
|
<li> Copy pymilter.cfg to the /etc/mail or the directory you run bms.py in,
|
||||||
comments should explain the options.
|
and edit it. The comments should explain the options.
|
||||||
<li> Start bms.py in the background as arranged.
|
<li> Start bms.py in the background as arranged.
|
||||||
<li> Add Xpythonfilter to sendmail.cf or add an INPUT_MAIL_FILTER to
|
<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.mc. Regen sendmail.cf if you use sendmail.mc and restart
|
||||||
|
|||||||
+27
-11
@@ -1,50 +1,63 @@
|
|||||||
[milter]
|
[milter]
|
||||||
# the socket used to communicate with sendmail. Must match sendmail.cf
|
# the socket used to communicate with sendmail. Must match sendmail.cf
|
||||||
;socket=/var/run/milter/pythonsock
|
socket=/var/run/milter/pythonsock
|
||||||
# where to save original copies of defanged and failed messages
|
# where to save original copies of defanged and failed messages
|
||||||
tempdir = /var/log/milter/save
|
tempdir = /var/log/milter/save
|
||||||
# how long to wait for a response from sendmail before giving up
|
# how long to wait for a response from sendmail before giving up
|
||||||
;timeout=600
|
;timeout=600
|
||||||
log_headers = 0
|
log_headers = 0
|
||||||
# connection ips and hostnames are matched against this glob style list
|
# connection ips and hostnames are matched against this glob style list
|
||||||
# to recognize internal senders
|
# to recognize internal senders.
|
||||||
;internal_connect = 192.168.*.*
|
;internal_connect = 192.168.*.*
|
||||||
|
|
||||||
# mail that is not an internal_connect and claims to be from an
|
# mail that is not an internal_connect and claims to be from an
|
||||||
# internal domain is rejected. You should enable SPF instead if you can.
|
# internal domain is rejected. Furthermore, internal mail that
|
||||||
# SPF is much more comprehensive and flexible.
|
# 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
|
;internal_domains = mycorp.com
|
||||||
|
|
||||||
# connections from a trusted relay can trust the first Received header
|
# connections from a trusted relay can trust the first Received header
|
||||||
# SPF checks are bypassed for internal connections and trusted relays.
|
# SPF checks are bypassed for internal connections and trusted relays.
|
||||||
;trusted_relay = 1.2.3.4, 66.12.34.56
|
;trusted_relay = 1.2.3.4, 66.12.34.56
|
||||||
|
|
||||||
# reject external senders with hello names no legit external sender would use
|
# 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
|
# SPF will do this also, but listing your own domain and mailserver here
|
||||||
# will save some DNS lookups when rejecting certain viruses.
|
# will save some DNS lookups when rejecting certain viruses.
|
||||||
;hello_blacklist = mycorp.com, 66.12.34.56
|
;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
|
# features intended to filter or block incoming mail
|
||||||
;[defang]
|
[defang]
|
||||||
|
|
||||||
# do virus scanning on attached messages also
|
# do virus scanning on attached messages also
|
||||||
scan_rfc822 = 1
|
scan_rfc822 = 1
|
||||||
|
# do virus scanning on attached zipfiles also
|
||||||
|
scan_zip = 0
|
||||||
# Comment out scripts in HTML attachments. Can be CPU intensive.
|
# Comment out scripts in HTML attachments. Can be CPU intensive.
|
||||||
scan_html = 0
|
scan_html = 0
|
||||||
# reject messages with asian fonts because we can't read them
|
# reject messages with asian fonts because we can't read them
|
||||||
block_chinese = 1
|
block_chinese = 1
|
||||||
# list users who hate forwarded mail
|
# list users who hate forwarded mail
|
||||||
;block_forward = egghead@mycorp.com, busybee@mycorp.com
|
;block_forward = egghead@mycorp.com, busybee@mycorp.com
|
||||||
# Reject mail for domains mentioned unless user is mentioned here also
|
|
||||||
;check_user = joe@mycorp.com, mary@mycorp.com, file:bigcorp.com
|
|
||||||
# reject mail with these case insensitive strings in the subject
|
# reject mail with these case insensitive strings in the subject
|
||||||
porn_words = penis, breast, pussy, horse cock, porn, xenical, diet pill, d1ck,
|
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,
|
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,
|
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,
|
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,
|
x@n3x, vicod3n, penís, c0d1n, phentermine, en1arge, dip1oma, v1codin,
|
||||||
valium, rolex, sexual
|
valium, rolex, sexual, fuck
|
||||||
# reject mail with these case sensitive strings in the subject
|
# reject mail with these case sensitive strings in the subject
|
||||||
spam_words = $$$, !!!, XXX, FREE, HGH
|
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
|
# See http://bmsi.com/python/pysrs.html for details
|
||||||
[srs]
|
[srs]
|
||||||
@@ -67,9 +80,10 @@ reject_spoofed = 0
|
|||||||
;reject_neutral = aol.com
|
;reject_neutral = aol.com
|
||||||
# use a default (v=spf1 a/24 mx/24 ptr) when no SPF records are published
|
# use a default (v=spf1 a/24 mx/24 ptr) when no SPF records are published
|
||||||
;best_guess = 0
|
;best_guess = 0
|
||||||
# reject senders that have neither PTR nor SPF records
|
# Reject senders that have neither PTR nor valid HELO nor SPF records, or send
|
||||||
|
# DSN otherwise
|
||||||
;reject_noptr = 0
|
;reject_noptr = 0
|
||||||
# always accept softfail from these domains
|
# always accept softfail from these domains, or send DSN otherwise
|
||||||
;accept_softfail = bounces.amazon.com
|
;accept_softfail = bounces.amazon.com
|
||||||
|
|
||||||
# features intended to clean up outgoing mail
|
# features intended to clean up outgoing mail
|
||||||
@@ -104,6 +118,8 @@ blind = 1
|
|||||||
# additional copies can be added
|
# additional copies can be added
|
||||||
;walter1 = cust@othercorp.com,walter@bigcorp.com,boss@bigcorp.com,
|
;walter1 = cust@othercorp.com,walter@bigcorp.com,boss@bigcorp.com,
|
||||||
; walter@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
|
# See http://bmsi.com/python/dspam.html
|
||||||
[dspam]
|
[dspam]
|
||||||
|
|||||||
+18
-7
@@ -24,11 +24,13 @@ ALT="Viewable With Any Browser" BORDER="0"></A>
|
|||||||
Stuart D. Gathman</a><br>
|
Stuart D. Gathman</a><br>
|
||||||
This web page is written by Stuart D. Gathman<br>and<br>sponsored by
|
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>
|
<a href="http://www.bmsi.com">Business Management Systems, Inc.</a> <br>
|
||||||
Last updated May 31, 2005</h4>
|
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> |
|
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="/mailman/listinfo/pymilter">Subscribe to mailing list</a> |
|
||||||
<a href="#overview">Overview</a>
|
<a href="#overview">Overview</a> |
|
||||||
|
<a href="/python/dspam.html">pydspam</a> |
|
||||||
|
<a href="/libdspam/dspam.html">libdspam</a>
|
||||||
<p>
|
<p>
|
||||||
<a href="//www.python.org">
|
<a href="//www.python.org">
|
||||||
<img src="python55.gif" align=left alt="A Python"></a>
|
<img src="python55.gif" align=left alt="A Python"></a>
|
||||||
@@ -39,17 +41,26 @@ provides a python interface to libmilter that exploits all its features.
|
|||||||
<p>
|
<p>
|
||||||
Sendmail 8.12 officially releases libmilter.
|
Sendmail 8.12 officially releases libmilter.
|
||||||
Version 8.12 seems to be more robust, and includes new privilege
|
Version 8.12 seems to be more robust, and includes new privilege
|
||||||
separation features to enhance security.
|
separation features to enhance security. Even better, sendmail 8.13
|
||||||
I recommend upgrading.
|
supports socket maps, which makes <a href="pysrs.html">pysrs</a> much more
|
||||||
|
efficient and secure. I recommend upgrading.
|
||||||
|
|
||||||
<h2> Recent Changes </h2>
|
<h2> Recent Changes </h2>
|
||||||
|
|
||||||
Python milter is being moved to
|
Python milter is being moved to
|
||||||
<a href="http://sourceforge.net/projects/pymilter/">Sourceforge</a> for
|
<a href="http://sourceforge.net/projects/pymilter/">pymilter Sourceforge
|
||||||
development.
|
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>
|
<p>
|
||||||
Release 0.7.2 tightens the authentication screws with a "3 strikes and
|
Release 0.7.2 tightens the authentication screws with a "3 strikes and
|
||||||
your out" policy. A sender must have a valid PTR, HELO, or SPF record
|
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
|
to send email. Specific senders can be whitelisted using the
|
||||||
"delegate" option in the spf configuration section by adding a
|
"delegate" option in the spf configuration section by adding a
|
||||||
default SPF record for them. The PTR and HELO are required
|
default SPF record for them. The PTR and HELO are required
|
||||||
|
|||||||
+42
-9
@@ -1,10 +1,25 @@
|
|||||||
%define name milter
|
%define name milter
|
||||||
%define version 0.8.0
|
%define version 0.8.1
|
||||||
%define release 2.EL3
|
%define release 1.RH7
|
||||||
# Redhat 7.x and earlier (multiple ps lines per thread)
|
# what version of RH are we building for?
|
||||||
#define sysvinit milter.rc7
|
%define redhat9 0
|
||||||
# RH9, other systems (single ps line per process)
|
%define redhat7 1
|
||||||
|
%define redhat6 0
|
||||||
|
|
||||||
|
# Options for Redhat version 6.x:
|
||||||
|
# rpm -ba|--rebuild --define "rh6 1"
|
||||||
|
%{?rh6:%define redhat7 0}
|
||||||
|
%{?rh6:%define redhat6 1}
|
||||||
|
|
||||||
|
# some systems dont have initrddir defined
|
||||||
|
%{?_initrddir:%define _initrddir /etc/rc.d/init.d}
|
||||||
|
|
||||||
|
%if %{redhat9}
|
||||||
%define sysvinit milter.rc
|
%define sysvinit milter.rc
|
||||||
|
%else # Redhat 7.x and earlier (multiple ps lines per thread)
|
||||||
|
%define sysvinit milter.rc7
|
||||||
|
%endif
|
||||||
|
# RH9, other systems (single ps line per process)
|
||||||
%ifos Linux
|
%ifos Linux
|
||||||
%define python python2.4
|
%define python python2.4
|
||||||
%else
|
%else
|
||||||
@@ -25,10 +40,10 @@ Vendor: Stuart D. Gathman <stuart@bmsi.com>
|
|||||||
Packager: Stuart D. Gathman <stuart@bmsi.com>
|
Packager: Stuart D. Gathman <stuart@bmsi.com>
|
||||||
Url: http://www.bmsi.com/python/milter.html
|
Url: http://www.bmsi.com/python/milter.html
|
||||||
Requires: %{python} >= 2.4, sendmail >= 8.12.10
|
Requires: %{python} >= 2.4, sendmail >= 8.12.10
|
||||||
%ifnos aix4.1
|
%ifos Linux
|
||||||
Requires: chkconfig
|
Requires: chkconfig
|
||||||
%endif
|
%endif
|
||||||
BuildRequires: %{python}-devel >= 2.2.2, sendmail-devel >= 8.12.10
|
BuildRequires: %{python}-devel , sendmail-devel >= 8.12.10
|
||||||
|
|
||||||
%description
|
%description
|
||||||
This is a python extension module to enable python scripts to
|
This is a python extension module to enable python scripts to
|
||||||
@@ -48,7 +63,7 @@ rm -rf $RPM_BUILD_ROOT
|
|||||||
mkdir -p $RPM_BUILD_ROOT/var/log/milter
|
mkdir -p $RPM_BUILD_ROOT/var/log/milter
|
||||||
mkdir -p $RPM_BUILD_ROOT/etc/mail
|
mkdir -p $RPM_BUILD_ROOT/etc/mail
|
||||||
mkdir $RPM_BUILD_ROOT/var/log/milter/save
|
mkdir $RPM_BUILD_ROOT/var/log/milter/save
|
||||||
cp bms.py $RPM_BUILD_ROOT/var/log/milter
|
cp bms.py strike3.txt softfail.txt $RPM_BUILD_ROOT/var/log/milter
|
||||||
cp milter.cfg $RPM_BUILD_ROOT/etc/mail/pymilter.cfg
|
cp milter.cfg $RPM_BUILD_ROOT/etc/mail/pymilter.cfg
|
||||||
|
|
||||||
# logfile rotation
|
# logfile rotation
|
||||||
@@ -145,12 +160,30 @@ rm -rf $RPM_BUILD_ROOT
|
|||||||
%dir /var/log/milter/save
|
%dir /var/log/milter/save
|
||||||
%config /var/log/milter/start.sh
|
%config /var/log/milter/start.sh
|
||||||
%config /var/log/milter/bms.py
|
%config /var/log/milter/bms.py
|
||||||
|
%config /var/log/milter/strike3.txt
|
||||||
|
%config /var/log/milter/softfail.txt
|
||||||
%config(noreplace) /etc/mail/pymilter.cfg
|
%config(noreplace) /etc/mail/pymilter.cfg
|
||||||
/usr/share/sendmail-cf/hack/rhsbl.m4
|
/usr/share/sendmail-cf/hack/rhsbl.m4
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* Thu Jun 16 2005 Stuart Gathman <stuart@bmsi.com> 0.8.1-1
|
||||||
|
- Fix zip in zip loop in mime.py
|
||||||
|
- Fix HeaderParseError in bms.py header callback
|
||||||
|
- Check internal_domains for outgoing mail
|
||||||
|
- Fix inconsistent results from send_dsn
|
||||||
|
* Mon Jun 06 2005 Stuart Gathman <stuart@bmsi.com> 0.8.0-3
|
||||||
|
- properly log pydspam exceptions
|
||||||
|
* Sat Jun 04 2005 Stuart Gathman <stuart@bmsi.com> 0.8.0-2
|
||||||
|
- Include default softfail, strike3 templates
|
||||||
|
* Wed May 25 2005 Stuart Gathman <stuart@bmsi.com> 0.8.0-1
|
||||||
|
- Move Milter module to subpackage.
|
||||||
|
- DSN support for Three strikes rule and SPF SOFTFAIL
|
||||||
|
- Move /*mime*/ and dynip to Milter subpackage
|
||||||
|
- Fix SPF unknown mechanism list not cleared
|
||||||
|
- Make banned extensions configurable.
|
||||||
|
- Option to scan zipfiles for bad extensions.
|
||||||
* Tue Feb 08 2005 Stuart Gathman <stuart@bmsi.com> 0.7.3-1.EL3
|
* Tue Feb 08 2005 Stuart Gathman <stuart@bmsi.com> 0.7.3-1.EL3
|
||||||
- Compile for EL3 and Python4
|
- Support EL3 and Python2.4 (some scanning/defang support broken)
|
||||||
* Mon Aug 30 2004 Stuart Gathman <stuart@bmsi.com> 0.7.2-1
|
* Mon Aug 30 2004 Stuart Gathman <stuart@bmsi.com> 0.7.2-1
|
||||||
- Fix various SPF bugs
|
- Fix various SPF bugs
|
||||||
- Recognize dynamic PTR names, and don't count them as authentication.
|
- Recognize dynamic PTR names, and don't count them as authentication.
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
/* Copyright (C) 2001 James Niemira (niemira@colltech.com, urmane@urmane.org)
|
/* 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
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* modify it under the terms of the GNU General Public License
|
||||||
@@ -33,6 +34,9 @@ $ python setup.py help
|
|||||||
libraries=["milter","smutil","resolv"]
|
libraries=["milter","smutil","resolv"]
|
||||||
|
|
||||||
* $Log$
|
* $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
|
* Revision 2.31 2004/08/23 02:24:36 stuart
|
||||||
* Support setbacklog
|
* Support setbacklog
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,13 @@
|
|||||||
# $Log$
|
# $Log$
|
||||||
|
# Revision 1.3 2005/06/02 15:00:17 customdesigned
|
||||||
|
# Configure banned extensions. Scan zipfile option with test case.
|
||||||
|
#
|
||||||
|
# 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
|
# Revision 1.62 2005/02/14 22:31:17 stuart
|
||||||
# _parseparam replacement not needed for python2.4
|
# _parseparam replacement not needed for python2.4
|
||||||
#
|
#
|
||||||
@@ -62,12 +71,13 @@
|
|||||||
# with a warning message.
|
# with a warning message.
|
||||||
|
|
||||||
# Author: Stuart D. Gathman <stuart@bmsi.com>
|
# Author: Stuart D. Gathman <stuart@bmsi.com>
|
||||||
# Copyright 2001 Business Management Systems, Inc.
|
# Copyright 2001,2002,2003,2004,2005 Business Management Systems, Inc.
|
||||||
# This code is under GPL. See COPYING for details.
|
# This code is under the GNU General Public License. See COPYING for details.
|
||||||
|
|
||||||
import StringIO
|
import StringIO
|
||||||
import socket
|
import socket
|
||||||
import Milter
|
import Milter
|
||||||
|
import zipfile
|
||||||
|
|
||||||
import email
|
import email
|
||||||
import email.Message
|
import email.Message
|
||||||
@@ -80,6 +90,16 @@ from email import Errors
|
|||||||
|
|
||||||
from types import ListType,StringType
|
from types import ListType,StringType
|
||||||
|
|
||||||
|
def zipnames(txt):
|
||||||
|
fp = StringIO.StringIO(txt)
|
||||||
|
zipf = zipfile.ZipFile(fp,'r')
|
||||||
|
names = []
|
||||||
|
for nm in zipf.namelist():
|
||||||
|
names.append(('zipname',nm))
|
||||||
|
if nm.lower().endswith('.zip'):
|
||||||
|
names += zipnames(zipf.read(nm))
|
||||||
|
return names
|
||||||
|
|
||||||
class MimeGenerator(Generator):
|
class MimeGenerator(Generator):
|
||||||
def _dispatch(self, msg):
|
def _dispatch(self, msg):
|
||||||
# Get the Content-Type: for the message, then try to dispatch to
|
# Get the Content-Type: for the message, then try to dispatch to
|
||||||
@@ -153,7 +173,7 @@ class MimeMessage(Message):
|
|||||||
def getname(self):
|
def getname(self):
|
||||||
return self.get_param('name')
|
return self.get_param('name')
|
||||||
|
|
||||||
def getnames(self):
|
def getnames(self,scan_zip=False):
|
||||||
"""Return a list of (attr,name) pairs of attributes that IE might
|
"""Return a list of (attr,name) pairs of attributes that IE might
|
||||||
interpret as a name - and hence decide to execute this message."""
|
interpret as a name - and hence decide to execute this message."""
|
||||||
names = []
|
names = []
|
||||||
@@ -168,7 +188,13 @@ class MimeMessage(Message):
|
|||||||
else:
|
else:
|
||||||
val = _unquotevalue(val.strip())
|
val = _unquotevalue(val.strip())
|
||||||
names.append((attr,val))
|
names.append((attr,val))
|
||||||
return names + [("filename",self.get_filename())]
|
names += [("filename",self.get_filename())]
|
||||||
|
if scan_zip:
|
||||||
|
for key,name in tuple(names): # copy by converting to tuple
|
||||||
|
if name and name.lower().endswith('.zip'):
|
||||||
|
txt = self.get_payload(decode=True)
|
||||||
|
names += zipnames(txt)
|
||||||
|
return names
|
||||||
|
|
||||||
def ismodified(self):
|
def ismodified(self):
|
||||||
"True if this message or a subpart has been modified."
|
"True if this message or a subpart has been modified."
|
||||||
@@ -276,12 +302,14 @@ A copy of your original message was saved as '%s:%s'.
|
|||||||
See your administrator.
|
See your administrator.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def check_name(msg,savname=None,ckname=check_ext):
|
def check_name(msg,savname=None,ckname=check_ext,scan_zip=False):
|
||||||
"Replace attachment with a warning if its name is suspicious."
|
"Replace attachment with a warning if its name is suspicious."
|
||||||
for key,name in msg.getnames():
|
for key,name in msg.getnames(scan_zip):
|
||||||
badname = ckname(name)
|
badname = ckname(name)
|
||||||
if badname:
|
if badname:
|
||||||
hostname = socket.gethostname()
|
hostname = socket.gethostname()
|
||||||
|
if key == 'zipname':
|
||||||
|
badname = msg.get_filename()
|
||||||
msg.set_payload(virus_msg % (badname,hostname,savname))
|
msg.set_payload(virus_msg % (badname,hostname,savname))
|
||||||
del msg["content-type"]
|
del msg["content-type"]
|
||||||
del msg["content-disposition"]
|
del msg["content-disposition"]
|
||||||
@@ -309,11 +337,11 @@ check function(MimeMessage): int
|
|||||||
# save call context for Python without nested_scopes
|
# save call context for Python without nested_scopes
|
||||||
class _defang:
|
class _defang:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self,scan_html=True):
|
||||||
self.scan_html = True
|
self.scan_html = scan_html
|
||||||
|
|
||||||
def _chk_name(self,msg):
|
def _chk_name(self,msg):
|
||||||
rc = check_name(msg,self._savname,self._check)
|
rc = check_name(msg,self._savname,self._check,self.scan_zip)
|
||||||
if self.scan_html:
|
if self.scan_html:
|
||||||
check_html(msg,self._savname) # remove scripts from HTML
|
check_html(msg,self._savname) # remove scripts from HTML
|
||||||
if self.scan_rfc822:
|
if self.scan_rfc822:
|
||||||
@@ -322,12 +350,14 @@ class _defang:
|
|||||||
return check_attachments(msg,self._chk_name)
|
return check_attachments(msg,self._chk_name)
|
||||||
return rc
|
return rc
|
||||||
|
|
||||||
def __call__(self,msg,savname=None,check=check_ext,scan_rfc822=True):
|
def __call__(self,msg,savname=None,check=check_ext,scan_rfc822=True,
|
||||||
|
scan_zip=False):
|
||||||
"""Compatible entry point.
|
"""Compatible entry point.
|
||||||
Replace all attachments with dangerous names."""
|
Replace all attachments with dangerous names."""
|
||||||
self._savname = savname
|
self._savname = savname
|
||||||
self._check = check
|
self._check = check
|
||||||
self.scan_rfc822 = scan_rfc822
|
self.scan_rfc822 = scan_rfc822
|
||||||
|
self.scan_zip = scan_zip
|
||||||
check_attachments(msg,self._chk_name)
|
check_attachments(msg,self._chk_name)
|
||||||
if msg.ismodified():
|
if msg.ismodified():
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -2,3 +2,4 @@
|
|||||||
python=python2
|
python=python2
|
||||||
doc_files=README NEWS TODO
|
doc_files=README NEWS TODO
|
||||||
packager=Stuart D. Gathman <stuart@bmsi.com>
|
packager=Stuart D. Gathman <stuart@bmsi.com>
|
||||||
|
release=2.4
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ if sys.version < '2.2.3':
|
|||||||
DistributionMetadata.classifiers = None
|
DistributionMetadata.classifiers = None
|
||||||
DistributionMetadata.download_url = None
|
DistributionMetadata.download_url = None
|
||||||
|
|
||||||
setup(name = "milter", version = "0.8.0",
|
setup(name = "milter", version = "0.8.1",
|
||||||
description="Python interface to sendmail milter API",
|
description="Python interface to sendmail milter API",
|
||||||
long_description="""\
|
long_description="""\
|
||||||
This is a python extension module to enable python scripts to
|
This is a python extension module to enable python scripts to
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
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
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
"""SPF (Sender-Permitted From) implementation.
|
"""SPF (Sender-Permitted From) implementation.
|
||||||
|
|
||||||
Copyright (c) 2003, Terence Way
|
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
|
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
|
it under the same terms as Python itself, so long as this copyright message
|
||||||
and disclaimer are retained in their original form.
|
and disclaimer are retained in their original form.
|
||||||
@@ -45,6 +46,15 @@ For news, bugfixes, etc. visit the home page for this implementation at
|
|||||||
# Terrence is not responding to email.
|
# Terrence is not responding to email.
|
||||||
#
|
#
|
||||||
# $Log$
|
# $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
|
# Revision 1.24 2005/03/16 21:58:39 stuart
|
||||||
# Change Milter module to package.
|
# Change Milter module to package.
|
||||||
#
|
#
|
||||||
@@ -417,11 +427,12 @@ class query(object):
|
|||||||
except TempError,x:
|
except TempError,x:
|
||||||
return ('error', 450, 'SPF Temporary Error: ' + str(x))
|
return ('error', 450, 'SPF Temporary Error: ' + str(x))
|
||||||
except PermError,x:
|
except PermError,x:
|
||||||
# Pre-Lentczner draft treats this as an unknown result
|
|
||||||
# and equivalent to no SPF record.
|
|
||||||
self.prob = x.msg
|
self.prob = x.msg
|
||||||
self.mech.append(x.mech)
|
self.mech.append(x.mech)
|
||||||
return ('unknown', 550, 'SPF Permanent Error: ' + str(x))
|
# 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):
|
def check1(self, spf, domain, recursion):
|
||||||
# spf rfc: 3.7 Processing Limits
|
# spf rfc: 3.7 Processing Limits
|
||||||
@@ -676,12 +687,11 @@ class query(object):
|
|||||||
p = CIDParser(q=self)
|
p = CIDParser(q=self)
|
||||||
try:
|
try:
|
||||||
return p.spf_txt(domain)
|
return p.spf_txt(domain)
|
||||||
except xml.sax._exceptions.SAXParseException,x:
|
except xml.sax._exceptions.SAXParseException:
|
||||||
raise PermError("Caller-ID parse error",domain)
|
raise PermError("Caller-ID parse error",domain)
|
||||||
|
|
||||||
if len(a) == 1:
|
if len(a) == 1:
|
||||||
return a[0]
|
return a[0]
|
||||||
else:
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def dns_txt(self, domainname):
|
def dns_txt(self, domainname):
|
||||||
@@ -966,12 +976,12 @@ def bin2addr(addr):
|
|||||||
def expand_one(expansion, str, joiner):
|
def expand_one(expansion, str, joiner):
|
||||||
if not str:
|
if not str:
|
||||||
return expansion
|
return expansion
|
||||||
len, reverse, delimiters = RE_ARGS.split(str)[1:4]
|
ln, reverse, delimiters = RE_ARGS.split(str)[1:4]
|
||||||
if not delimiters:
|
if not delimiters:
|
||||||
delimiters = '.'
|
delimiters = '.'
|
||||||
expansion = split(expansion, delimiters, joiner)
|
expansion = split(expansion, delimiters, joiner)
|
||||||
if reverse: expansion.reverse()
|
if reverse: expansion.reverse()
|
||||||
if len: expansion = expansion[-int(len)*2+1:]
|
if ln: expansion = expansion[-int(ln)*2+1:]
|
||||||
return ''.join(expansion)
|
return ''.join(expansion)
|
||||||
|
|
||||||
def split(str, delimiters, joiner=None):
|
def split(str, delimiters, joiner=None):
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
#!/usr/bin/python2.3
|
#!/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$
|
# $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
|
# Revision 2.3 2004/04/19 22:12:11 stuart
|
||||||
# Release 0.6.9
|
# Release 0.6.9
|
||||||
#
|
#
|
||||||
|
|||||||
+66
@@ -0,0 +1,66 @@
|
|||||||
|
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://spf.pobox.com
|
||||||
|
|
||||||
|
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
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
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----
|
||||||
|
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
From ttaie1@thfalcon.com Thu Jun 16 10:23:13 2005
|
||||||
|
Received: from thfalcon.com (unknown [202.90.113.150])
|
||||||
|
by thfalcon.com (Postfix) with ESMTP id 32F0DD819C
|
||||||
|
for <stuart@bmsi.com>; Thu, 16 Jun 2005 15:42:08 +0700 (ICT)
|
||||||
|
From: ttaie1@thfalcon.com
|
||||||
|
To: stuart@bmsi.com
|
||||||
|
Subject: Returned mail: see transcript for details
|
||||||
|
Date: Thu, 16 Jun 2005 15:50:10 +0700
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: multipart/mixed;
|
||||||
|
boundary="----=_NextPart_000_0014_E4E04420.5619685C"
|
||||||
|
X-Priority: 3
|
||||||
|
X-MSMail-Priority: Normal
|
||||||
|
X-Mailer: Microsoft Outlook Express 6.00.2600.0000
|
||||||
|
X-MIMEOLE: Produced By Microsoft MimeOLE V6.00.2600.0000
|
||||||
|
Message-Id: <20050616084208.32F0DD819C@thfalcon.com>
|
||||||
|
Received-SPF: pass (mail.bmsi.com: guessing: domain of thfalcon.com designates 203.147.3.44 as permitted sender) client-ip=203.147.3.44; envelope-from=ttaie1@thfalcon.com; helo=thfalcon.com;
|
||||||
|
|
||||||
|
This is a multi-part message in MIME format.
|
||||||
|
|
||||||
|
------=_NextPart_000_0014_E4E04420.5619685C
|
||||||
|
Content-Type: text/plain;
|
||||||
|
charset=us-ascii
|
||||||
|
Content-Transfer-Encoding: 7bit
|
||||||
|
|
||||||
|
Message could not be delivered
|
||||||
|
|
||||||
|
|
||||||
|
------=_NextPart_000_0014_E4E04420.5619685C
|
||||||
|
Content-Type: application/octet-stream;
|
||||||
|
name="stuart@bmsi.com.zip"
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
Content-Disposition: attachment;
|
||||||
|
filename="stuart@bmsi.com.zip"
|
||||||
|
|
||||||
|
UEsDBAoAAAAAAM6r0DL7SfbCBAEAAAQBAAAFABUAdC56aXBVVAkAA7MnskK4J7JCVXgEAIYD
|
||||||
|
ZQBQSwMECgAAAAAANVXCMtpXacQaAAAAGgAAADMAFQB6aXAuZG9jICAgICAgICAgICAgICAg
|
||||||
|
ICAgICAgICAgICAgICAgICAgICAgICAgIC5leGVVVAkAA6Yan0KmGp9CVXgEAIYDZQBUaGlz
|
||||||
|
IHByb2dyYW0gd2FzIGEgdmlydXMuClBLAQIXAwoAAAAAADVVwjLaV2nEGgAAABoAAAAzAA0A
|
||||||
|
AAAAAAEAAAC0gQAAAAB6aXAuZG9jICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
|
||||||
|
ICAgICAgIC5leGVVVAUAA6Yan0JVeAAAUEsFBgAAAAABAAEAbgAAAIAAAAAAAFBLAQIXAwoA
|
||||||
|
AAAAAM6r0DL7SfbCBAEAAAQBAAAFAA0AAAAAAAAAAAC0gQAAAAB0LnppcFVUBQADsyeyQlV4
|
||||||
|
AABQSwUGAAAAAAEAAQBAAAAAPAEAAAAA
|
||||||
|
|
||||||
|
------=_NextPart_000_0014_E4E04420.5619685C--
|
||||||
|
|
||||||
|
|
||||||
+19
-2
@@ -1,4 +1,10 @@
|
|||||||
# $Log$
|
# $Log$
|
||||||
|
# Revision 1.2 2005/06/02 15:00:17 customdesigned
|
||||||
|
# Configure banned extensions. Scan zipfile option with test case.
|
||||||
|
#
|
||||||
|
# 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
|
# Revision 1.23 2005/02/11 18:34:14 stuart
|
||||||
# Handle garbage after quote in boundary.
|
# Handle garbage after quote in boundary.
|
||||||
#
|
#
|
||||||
@@ -63,7 +69,7 @@ class MimeTestCase(unittest.TestCase):
|
|||||||
def testDefang(self,vname='virus1',part=1,
|
def testDefang(self,vname='virus1',part=1,
|
||||||
fname='LOVE-LETTER-FOR-YOU.TXT.vbs'):
|
fname='LOVE-LETTER-FOR-YOU.TXT.vbs'):
|
||||||
msg = mime.message_from_file(open('test/'+vname,"r"))
|
msg = mime.message_from_file(open('test/'+vname,"r"))
|
||||||
mime.defang(msg)
|
mime.defang(msg,scan_zip=True)
|
||||||
self.failUnless(msg.ismodified(),"virus not removed")
|
self.failUnless(msg.ismodified(),"virus not removed")
|
||||||
oname = vname + '.out'
|
oname = vname + '.out'
|
||||||
msg.dump(open('test/'+oname,"w"))
|
msg.dump(open('test/'+oname,"w"))
|
||||||
@@ -71,7 +77,8 @@ class MimeTestCase(unittest.TestCase):
|
|||||||
txt2 = msg.get_payload()
|
txt2 = msg.get_payload()
|
||||||
if type(txt2) == list:
|
if type(txt2) == list:
|
||||||
txt2 = txt2[part].get_payload()
|
txt2 = txt2[part].get_payload()
|
||||||
self.failUnless(txt2.rstrip()+'\n' == mime.virus_msg % (fname,hostname,None),txt2)
|
self.failUnless(
|
||||||
|
txt2.rstrip()+'\n' == mime.virus_msg % (fname,hostname,None),txt2)
|
||||||
|
|
||||||
def testDefang3(self):
|
def testDefang3(self):
|
||||||
self.testDefang('virus3',0,'READER_DIGEST_LETTER.TXT.pif')
|
self.testDefang('virus3',0,'READER_DIGEST_LETTER.TXT.pif')
|
||||||
@@ -121,6 +128,14 @@ class MimeTestCase(unittest.TestCase):
|
|||||||
name = parts[1].getname()
|
name = parts[1].getname()
|
||||||
self.failUnless(name == "Jim&amp;Girlz.jpg","name=%s"%name)
|
self.failUnless(name == "Jim&amp;Girlz.jpg","name=%s"%name)
|
||||||
|
|
||||||
|
def testZip(self,vname="zip1",fname='zip.zip'):
|
||||||
|
self.testDefang(vname,1,'zip.zip')
|
||||||
|
msg = mime.message_from_file(open('test/'+vname,"r"))
|
||||||
|
mime.defang(msg,scan_zip=False)
|
||||||
|
self.failIf(msg.ismodified())
|
||||||
|
# test zip within zip
|
||||||
|
self.testDefang('ziploop',1,'stuart@bmsi.com.zip')
|
||||||
|
|
||||||
def testHTML(self,fname=""):
|
def testHTML(self,fname=""):
|
||||||
result = StringIO.StringIO()
|
result = StringIO.StringIO()
|
||||||
filter = mime.HTMLScriptFilter(result)
|
filter = mime.HTMLScriptFilter(result)
|
||||||
@@ -143,3 +158,5 @@ if __name__ == '__main__':
|
|||||||
for fname in sys.argv[1:]:
|
for fname in sys.argv[1:]:
|
||||||
fp = open(fname,'r')
|
fp = open(fname,'r')
|
||||||
msg = mime.message_from_file(fp)
|
msg = mime.message_from_file(fp)
|
||||||
|
mime.defang(msg,scan_zip=True)
|
||||||
|
print msg.as_string()
|
||||||
|
|||||||
Reference in New Issue
Block a user