Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 20751ea706 | |||
| 7197b82ed6 | |||
| 39a1fc78d8 | |||
| 5ad23e468d | |||
| 6eedaf7717 | |||
| 4a8018c2de | |||
| 1212a0ef59 | |||
| 5675adeb3c | |||
| 35416dfc46 | |||
| c33de064ee | |||
| 1c05080768 | |||
| dce7c0080a | |||
| 7deec90a59 | |||
| c73b533acb | |||
| 102e042a38 | |||
| 7a5c942d54 | |||
| 1b2c48d8a9 | |||
| 866201ca52 | |||
| 2744175998 | |||
| 599277855c | |||
| e7592c6a96 | |||
| 7df236127b | |||
| 1234869dd6 | |||
| f37090371b | |||
| 7ea839cfb1 | |||
| 879e65bc31 | |||
| 4c7c76fca4 | |||
| 132e8326b5 | |||
| 0efddd316a | |||
| 588153078b | |||
| 4ed12cf825 | |||
| c098f9df6b | |||
| cdae26af47 | |||
| bf3108b938 | |||
| d5f9f86bba | |||
| 805825438c | |||
| 3844751ef0 | |||
| 2b1b01c1ef | |||
| 222afcd555 | |||
| 4251fbc151 | |||
| 4749f0ff98 | |||
| 18186a3c11 | |||
| a01f598e37 | |||
| d0d45c5e61 | |||
| a1714f4838 | |||
| edc2f73375 | |||
| 6373f8965b |
@@ -1,8 +1,11 @@
|
|||||||
*.pyc
|
*.pyc
|
||||||
|
*.tar.gz
|
||||||
build/
|
build/
|
||||||
test/*.out
|
test/*.out
|
||||||
test/*.tstout
|
test/*.tstout
|
||||||
test/*.log
|
test/*.log
|
||||||
|
test/*.db
|
||||||
test.db
|
test.db
|
||||||
dist
|
dist
|
||||||
|
log
|
||||||
MANIFEST
|
MANIFEST
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ PROJECT_NAME = pymilter
|
|||||||
# This could be handy for archiving the generated documentation or
|
# This could be handy for archiving the generated documentation or
|
||||||
# if some version control system is used.
|
# if some version control system is used.
|
||||||
|
|
||||||
PROJECT_NUMBER = 1.0.2
|
PROJECT_NUMBER = 1.0.5
|
||||||
|
|
||||||
# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
|
# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
|
||||||
# base path where the generated documentation will be put.
|
# base path where the generated documentation will be put.
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@ include COPYING
|
|||||||
include TODO
|
include TODO
|
||||||
include NEWS
|
include NEWS
|
||||||
include CREDITS
|
include CREDITS
|
||||||
include README
|
include README.md
|
||||||
include ChangeLog
|
include ChangeLog
|
||||||
include MANIFEST.in
|
include MANIFEST.in
|
||||||
include testsample.py
|
include testsample.py
|
||||||
|
|||||||
+89
-6
@@ -9,11 +9,12 @@
|
|||||||
# This code is under the GNU General Public License. See COPYING for details.
|
# This code is under the GNU General Public License. See COPYING for details.
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
__version__ = '1.0.4'
|
__version__ = '1.0.6'
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import milter
|
import milter
|
||||||
|
import sys
|
||||||
try:
|
try:
|
||||||
import thread
|
import thread
|
||||||
except:
|
except:
|
||||||
@@ -180,15 +181,41 @@ def noreply(func):
|
|||||||
wrapper.milter_protocol = nr_mask
|
wrapper.milter_protocol = nr_mask
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
## Function decorator to set decoding error strategy.
|
||||||
|
# Current RFCs define UTF-8 as the standard encoding for SMTP
|
||||||
|
# envelope and header fields. By default, Milter.Base decodes
|
||||||
|
# envelope and header values with errors='surrogateescape'.
|
||||||
|
# Applications can recover the original bytes with
|
||||||
|
# <pre>
|
||||||
|
# b = s.encode(errors='surrogateescape')
|
||||||
|
# </pre>
|
||||||
|
# This preserves information, but can lead to unexpected exceptions
|
||||||
|
# as you cannot, e.g. print strings with surrogates.
|
||||||
|
# Illegal bytes occur quite often in real life, so there must
|
||||||
|
# be a way to deal with them.
|
||||||
|
# This decorator can change the error strategy to
|
||||||
|
# <ul>
|
||||||
|
# <li> bytes - original bytes are passed unmodified
|
||||||
|
# <li> strict - pass bytes if illegal bytes are present, string otherwise
|
||||||
|
# <li> ignore - illegal bytes are removed
|
||||||
|
# <li> replace - illegal bytes are replaced with a unicode error symbol
|
||||||
|
# </ul>
|
||||||
|
#
|
||||||
|
def decode(strategy):
|
||||||
|
def setstrategy(func):
|
||||||
|
func.error_strategy = strategy
|
||||||
|
return func
|
||||||
|
return setstrategy
|
||||||
|
|
||||||
## Function decorator to set macros used in a callback.
|
## Function decorator to set macros used in a callback.
|
||||||
# By default, the MTA sends all macros defined for a callback.
|
# By default, the MTA sends all macros defined for a callback.
|
||||||
# If some or all of these are unused, the bandwidth can be saved
|
# If some or all of these are unused, the bandwidth can be saved
|
||||||
# by listing the ones that are used.
|
# by listing the ones that are used.
|
||||||
# @since 1.0.2
|
# @since 1.0.2
|
||||||
def symlist(*syms):
|
def symlist(*syms):
|
||||||
|
def setsyms(func):
|
||||||
if len(syms) > 5:
|
if len(syms) > 5:
|
||||||
raise ValueError('@symlist limited to 5 macros by MTA: '+func.__name__)
|
raise ValueError('@symlist limited to 5 macros by MTA: '+func.__name__)
|
||||||
def setsyms(func):
|
|
||||||
if func.__name__ not in MACRO_CALLBACKS:
|
if func.__name__ not in MACRO_CALLBACKS:
|
||||||
raise ValueError('@symlist applied to non-symlist method: '+func.__name__)
|
raise ValueError('@symlist applied to non-symlist method: '+func.__name__)
|
||||||
func._symlist = syms
|
func._symlist = syms
|
||||||
@@ -320,6 +347,20 @@ class Base(object):
|
|||||||
# this almost always results in terminating the connection.
|
# this almost always results in terminating the connection.
|
||||||
@nocallback
|
@nocallback
|
||||||
def hello(self,hostname): return CONTINUE
|
def hello(self,hostname): return CONTINUE
|
||||||
|
## Called with bytes by default global envfrom callback.
|
||||||
|
# @since 1.0.5
|
||||||
|
# Converts from utf-8 to unicode with surrogate escape. Can be overriden
|
||||||
|
# to pass bytes to @link #header the header callback @endlink instead,
|
||||||
|
# or trap utf-8 conversion exception, etc.
|
||||||
|
def envfrom_bytes(self,*b):
|
||||||
|
try:
|
||||||
|
e = getattr(self.envfrom,'error_strategy','surrogateescape')
|
||||||
|
if e == 'bytes':
|
||||||
|
#self.envfrom_bytes = self.envfrom
|
||||||
|
return self.envfrom(*b)
|
||||||
|
s = [v.decode(encoding='utf-8',errors=e) for v in b]
|
||||||
|
except UnicodeDecodeError: s = b
|
||||||
|
return self.envfrom(s[0],*s[1:])
|
||||||
## Called when the SMTP client says MAIL FROM. Called by the
|
## Called when the SMTP client says MAIL FROM. Called by the
|
||||||
# <a href="milter_api/xxfi_envfrom.html">
|
# <a href="milter_api/xxfi_envfrom.html">
|
||||||
# xxfi_envfrom</a> callback.
|
# xxfi_envfrom</a> callback.
|
||||||
@@ -330,7 +371,21 @@ class Base(object):
|
|||||||
# <a href="http://tools.ietf.org/html/rfc5322">RFC 5322</a>,
|
# <a href="http://tools.ietf.org/html/rfc5322">RFC 5322</a>,
|
||||||
# see @link #header the header callback @endlink.
|
# see @link #header the header callback @endlink.
|
||||||
@nocallback
|
@nocallback
|
||||||
def envfrom(self,f,*str): return CONTINUE
|
def envfrom(self,f,*s): return CONTINUE
|
||||||
|
## Called with bytes by default global envrcpt callback.
|
||||||
|
# @since 1.0.5
|
||||||
|
# Converts from utf-8 to unicode with surrogate escape. Can be overriden
|
||||||
|
# to pass bytes to @link #header the header callback @endlink instead,
|
||||||
|
# or trap utf-8 conversion exception, etc.
|
||||||
|
def envrcpt_bytes(self,*b):
|
||||||
|
try:
|
||||||
|
e = getattr(self.envrcpt,'error_strategy','surrogateescape')
|
||||||
|
if e == 'bytes':
|
||||||
|
#self.envrcpt_bytes = self.envrcpt
|
||||||
|
return self.envrcpt(*b)
|
||||||
|
s = [v.decode(encoding='utf-8',errors=e) for v in b]
|
||||||
|
except UnicodeDecodeError: s = b
|
||||||
|
return self.envrcpt(s[0],*s[1:])
|
||||||
## Called when the SMTP client says RCPT TO. Called by the
|
## Called when the SMTP client says RCPT TO. Called by the
|
||||||
# <a href="milter_api/xxfi_envrcpt.html">
|
# <a href="milter_api/xxfi_envrcpt.html">
|
||||||
# xxfi_envrcpt</a> callback.
|
# xxfi_envrcpt</a> callback.
|
||||||
@@ -348,7 +403,30 @@ class Base(object):
|
|||||||
# @since 0.9.2
|
# @since 0.9.2
|
||||||
@nocallback
|
@nocallback
|
||||||
def data(self): return CONTINUE
|
def data(self): return CONTINUE
|
||||||
|
## Called with bytes by default global header callback.
|
||||||
|
# @param fld name decoded as ascii
|
||||||
|
# @param val field value as bytes
|
||||||
|
# @since 1.0.5
|
||||||
|
# Converts from utf-8 to unicode with surrogate escape. Can be overriden
|
||||||
|
# to pass bytes to @link #header the header callback @endlink instead,
|
||||||
|
# e.g. by assignment:
|
||||||
|
# <pre>
|
||||||
|
# mymilter.header_bytes = mymilter.header
|
||||||
|
# </pre>
|
||||||
|
# The <code>@decode('bytes')</code> decorator will also do this.
|
||||||
|
#
|
||||||
|
def header_bytes(self,fld,val):
|
||||||
|
try:
|
||||||
|
e = getattr(self.header,'error_strategy','surrogateescape')
|
||||||
|
if e == 'bytes':
|
||||||
|
self.header_bytes = self.header
|
||||||
|
return self.header(fld,val)
|
||||||
|
s = val.decode(encoding='utf-8',errors=e)
|
||||||
|
except UnicodeDecodeError: s = val
|
||||||
|
return self.header(fld,s)
|
||||||
## Called for each header field in the message body.
|
## Called for each header field in the message body.
|
||||||
|
# @param field name decoded as ascii
|
||||||
|
# @param value field value decoded as utf-8 on python3
|
||||||
@nocallback
|
@nocallback
|
||||||
def header(self,field,value): return CONTINUE
|
def header(self,field,value): return CONTINUE
|
||||||
## Called at the blank line that terminates the header fields.
|
## Called at the blank line that terminates the header fields.
|
||||||
@@ -764,9 +842,14 @@ def runmilter(name,socketname,timeout = 0,rmsock=True):
|
|||||||
# parms, but then all existing users would have to include **kw to accept
|
# parms, but then all existing users would have to include **kw to accept
|
||||||
# arbitrary keywords without crashing. We do provide envcallback and
|
# arbitrary keywords without crashing. We do provide envcallback and
|
||||||
# dictfromlist to make parsing the ESMTP args convenient.
|
# dictfromlist to make parsing the ESMTP args convenient.
|
||||||
milter.set_envfrom_callback(lambda ctx,*str: ctx.getpriv().envfrom(*str))
|
if sys.version < '3.0.0':
|
||||||
milter.set_envrcpt_callback(lambda ctx,*str: ctx.getpriv().envrcpt(*str))
|
milter.set_envfrom_callback(lambda ctx,*s: ctx.getpriv().envfrom(*s))
|
||||||
milter.set_header_callback(lambda ctx,fld,val: ctx.getpriv().header(fld,val))
|
milter.set_envrcpt_callback(lambda ctx,*s: ctx.getpriv().envrcpt(*s))
|
||||||
|
milter.set_header_callback(lambda ctx,f,v: ctx.getpriv().header(f,v))
|
||||||
|
else:
|
||||||
|
milter.set_envfrom_callback(lambda ctx,*b: ctx.getpriv().envfrom_bytes(*b))
|
||||||
|
milter.set_envrcpt_callback(lambda ctx,*b: ctx.getpriv().envrcpt_bytes(*b))
|
||||||
|
milter.set_header_callback(lambda ctx,f,v: ctx.getpriv().header_bytes(f,v))
|
||||||
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())
|
||||||
|
|||||||
+10
-5
@@ -1,3 +1,6 @@
|
|||||||
|
try:
|
||||||
|
from configparser import ConfigParser
|
||||||
|
except:
|
||||||
from ConfigParser import ConfigParser
|
from ConfigParser import ConfigParser
|
||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
@@ -11,10 +14,10 @@ class MilterConfigParser(ConfigParser):
|
|||||||
# which screws up iterating over all options in a section.
|
# which screws up iterating over all options in a section.
|
||||||
# Worse, passing "defaults" with vars= overrides the config file!
|
# Worse, passing "defaults" with vars= overrides the config file!
|
||||||
# So we roll our own defaults.
|
# So we roll our own defaults.
|
||||||
def get(self,sect,opt):
|
def get(self,sect,opt,fallback=None,**kwds):
|
||||||
if not self.has_option(sect,opt) and opt in self.defaults:
|
if not self.has_option(sect,opt) and not fallback and opt in self.defaults:
|
||||||
return self.defaults[opt]
|
return self.defaults[opt]
|
||||||
return ConfigParser.get(self,sect,opt)
|
return ConfigParser.get(self,sect,opt,fallback=fallback,**kwds)
|
||||||
|
|
||||||
def getlist(self,sect,opt):
|
def getlist(self,sect,opt):
|
||||||
if self.has_option(sect,opt):
|
if self.has_option(sect,opt):
|
||||||
@@ -31,7 +34,8 @@ class MilterConfigParser(ConfigParser):
|
|||||||
if q.startswith('file:'):
|
if q.startswith('file:'):
|
||||||
domain = q[5:].lower()
|
domain = q[5:].lower()
|
||||||
fname = os.path.join(dir,domain)
|
fname = os.path.join(dir,domain)
|
||||||
d[domain] = d.setdefault(domain,[]) + open(fname,'r').read().split()
|
with open(fname,'r') as fp:
|
||||||
|
d[domain] = d.setdefault(domain,[]) + fp.read().split()
|
||||||
else:
|
else:
|
||||||
user,domain = q.split('@')
|
user,domain = q.split('@')
|
||||||
d.setdefault(domain.lower(),[]).append(user)
|
d.setdefault(domain.lower(),[]).append(user)
|
||||||
@@ -49,7 +53,8 @@ class MilterConfigParser(ConfigParser):
|
|||||||
addr = addr.strip()
|
addr = addr.strip()
|
||||||
if addr.startswith('file:'):
|
if addr.startswith('file:'):
|
||||||
fname = os.path.join(dir,addr[5:])
|
fname = os.path.join(dir,addr[5:])
|
||||||
for a in open(fname,'r').read().split():
|
with open(fname,'r') as fp:
|
||||||
|
for a in fp.read().split():
|
||||||
d[a] = q
|
d[a] = q
|
||||||
else:
|
else:
|
||||||
d[addr] = q
|
d[addr] = q
|
||||||
|
|||||||
+4
-1
@@ -72,10 +72,13 @@
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
import smtplib
|
import smtplib
|
||||||
import socket
|
import socket
|
||||||
|
try:
|
||||||
|
from email.message import Message
|
||||||
|
except:
|
||||||
from email.Message import Message
|
from email.Message import Message
|
||||||
import Milter
|
import Milter
|
||||||
|
import Milter.dns as dns
|
||||||
import time
|
import time
|
||||||
import dns
|
|
||||||
|
|
||||||
## Send DSN.
|
## Send DSN.
|
||||||
# Try the published MX names in order, rejecting obviously bogus entries
|
# Try the published MX names in order, rejecting obviously bogus entries
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
import time
|
import time
|
||||||
import shelve
|
import shelve
|
||||||
|
try:
|
||||||
import thread
|
import thread
|
||||||
|
except:
|
||||||
|
import _thread as thread
|
||||||
import logging
|
import logging
|
||||||
import urllib
|
import urllib
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
try:
|
||||||
|
try:
|
||||||
|
from berkeleydb import db
|
||||||
|
except:
|
||||||
|
from bsddb3 import db
|
||||||
|
class DB(object):
|
||||||
|
def open(self,fname,mode):
|
||||||
|
if mode == 'r': flags = db.DB_RDONLY
|
||||||
|
else: raise RuntimeException('unsupported mode')
|
||||||
|
self.f = db.DB()
|
||||||
|
self.f.open(fname,flags=flags)
|
||||||
|
def __contains__(self,key):
|
||||||
|
return not not self.f.get(key)
|
||||||
|
def __getitem__(self,key):
|
||||||
|
v = self.f.get(key)
|
||||||
|
if not v: raise KeyError(key)
|
||||||
|
return v
|
||||||
|
def close(self):
|
||||||
|
self.f.close()
|
||||||
|
def dbmopen(fname,mode):
|
||||||
|
f = DB()
|
||||||
|
f.open(fname,mode)
|
||||||
|
return f
|
||||||
|
except ModuleNotFoundError: raise
|
||||||
|
except:
|
||||||
|
import anydbm as dbm
|
||||||
|
dbmopen = dbm.open
|
||||||
|
|
||||||
|
class MTAPolicy(object):
|
||||||
|
"Get SPF policy by result from sendmail style access file."
|
||||||
|
def __init__(self,sender,conf,access_file=None):
|
||||||
|
if not access_file:
|
||||||
|
access_file = conf.access_file
|
||||||
|
self.use_nulls = conf.access_file_nulls
|
||||||
|
try:
|
||||||
|
self.use_colon = conf.access_file_colon
|
||||||
|
except:
|
||||||
|
self.use_colon = True
|
||||||
|
self.sender = sender
|
||||||
|
self.domain = sender.split('@')[-1].lower()
|
||||||
|
self.acf = None
|
||||||
|
self.access_file = access_file
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
if self.acf:
|
||||||
|
self.acf.close()
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self.acf = None
|
||||||
|
if self.access_file:
|
||||||
|
try:
|
||||||
|
self.acf = dbmopen(self.access_file,'r')
|
||||||
|
except:
|
||||||
|
print('%s: Cannot open for reading'%self.access_file)
|
||||||
|
raise
|
||||||
|
return self
|
||||||
|
def __exit__(self,t,v,b): self.close()
|
||||||
|
|
||||||
|
def getPolicy(self,pfx):
|
||||||
|
acf = self.acf
|
||||||
|
if not acf: return None
|
||||||
|
if self.use_nulls: sfx = b'\x00'
|
||||||
|
else: sfx = b''
|
||||||
|
if self.use_colon:
|
||||||
|
sep = b':'
|
||||||
|
else:
|
||||||
|
sep = b'!'
|
||||||
|
pfx = pfx.encode() + sep
|
||||||
|
try: # try with localpart@domain
|
||||||
|
return acf[pfx + self.sender.encode() + sfx].rstrip(b'\x00').decode()
|
||||||
|
except KeyError:
|
||||||
|
try: # try with domain
|
||||||
|
d = self.domain.encode()
|
||||||
|
k = pfx + d + sfx
|
||||||
|
while not k in acf and b'.' in d:
|
||||||
|
# check partial domains
|
||||||
|
d = b'.'.join(d.split(b'.')[1:])
|
||||||
|
k = pfx + b'.' + d + sfx
|
||||||
|
return acf[k].rstrip(b'\x00').decode()
|
||||||
|
except KeyError:
|
||||||
|
try: # try bare prefix
|
||||||
|
return acf[pfx + sfx].rstrip(b'\x00').decode()
|
||||||
|
except KeyError:
|
||||||
|
try:
|
||||||
|
return acf[pfx[:-1] + sfx].rstrip(b'\x00').decode()
|
||||||
|
except KeyError:
|
||||||
|
return None
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
"""A parser for SGML, using the derived class as a static DTD."""
|
"""A parser for SGML, using the derived class as a static DTD."""
|
||||||
|
|
||||||
# XXX This only supports those SGML features used by HTML.
|
# XXX This only supports those SGML features used by HTML.
|
||||||
|
|||||||
+9
-2
@@ -46,6 +46,11 @@ class TestBase(object):
|
|||||||
## The macros returned by protocol stage
|
## The macros returned by protocol stage
|
||||||
self._symlist = [ None, None, None, None, None, None, None ]
|
self._symlist = [ None, None, None, None, None, None, None ]
|
||||||
|
|
||||||
|
def _close(self):
|
||||||
|
if self.logfp:
|
||||||
|
self.logfp.close()
|
||||||
|
self.logfp = None
|
||||||
|
|
||||||
def log(self,*msg):
|
def log(self,*msg):
|
||||||
for i in msg: print(i,file=self.logfp,end=None)
|
for i in msg: print(i,file=self.logfp,end=None)
|
||||||
print(file=self.logfp)
|
print(file=self.logfp)
|
||||||
@@ -59,7 +64,7 @@ class TestBase(object):
|
|||||||
|
|
||||||
def getsymval(self,name):
|
def getsymval(self,name):
|
||||||
stage = self._stage
|
stage = self._stage
|
||||||
if stage >= 0:
|
if stage is not None and stage >= 0:
|
||||||
syms = self._symlist[stage]
|
syms = self._symlist[stage]
|
||||||
if syms is not None and name not in syms:
|
if syms is not None and name not in syms:
|
||||||
return None
|
return None
|
||||||
@@ -178,7 +183,7 @@ class TestBase(object):
|
|||||||
if rc != Milter.CONTINUE: return rc
|
if rc != Milter.CONTINUE: return rc
|
||||||
# header
|
# header
|
||||||
for h,val in msg.items():
|
for h,val in msg.items():
|
||||||
rc = self.header(h,val)
|
rc = self.header_bytes(h,val.encode())
|
||||||
if rc != Milter.CONTINUE: return rc
|
if rc != Milter.CONTINUE: return rc
|
||||||
# eoh
|
# eoh
|
||||||
self._stage = Milter.M_EOH
|
self._stage = Milter.M_EOH
|
||||||
@@ -204,6 +209,8 @@ class TestBase(object):
|
|||||||
self._body.write(header)
|
self._body.write(header)
|
||||||
self._body.write(b'\n\n')
|
self._body.write(b'\n\n')
|
||||||
self._body.write(body)
|
self._body.write(body)
|
||||||
|
self.close()
|
||||||
|
self._close()
|
||||||
return rc
|
return rc
|
||||||
|
|
||||||
## Feed an email contained in a file to the %milter.
|
## Feed an email contained in a file to the %milter.
|
||||||
|
|||||||
+16
-1
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
from socket import AF_INET,AF_INET6
|
from socket import AF_INET,AF_INET6
|
||||||
|
from sys import version as VERSION
|
||||||
import time
|
import time
|
||||||
import mime
|
import mime
|
||||||
try:
|
try:
|
||||||
@@ -14,7 +15,6 @@ except:
|
|||||||
from StringIO import StringIO as BytesIO
|
from StringIO import StringIO as BytesIO
|
||||||
import Milter
|
import Milter
|
||||||
from Milter import utils
|
from Milter import utils
|
||||||
import mime
|
|
||||||
|
|
||||||
## Milter context for unit testing %milter applications.
|
## Milter context for unit testing %milter applications.
|
||||||
# A substitute for milter.milterContext that can be passed to
|
# A substitute for milter.milterContext that can be passed to
|
||||||
@@ -219,7 +219,22 @@ class TestCtx(object):
|
|||||||
return rc
|
return rc
|
||||||
|
|
||||||
def _header(self,fld,val):
|
def _header(self,fld,val):
|
||||||
|
if VERSION < '3.0.0':
|
||||||
return self._priv.header(fld,val)
|
return self._priv.header(fld,val)
|
||||||
|
# email.message_from_binary_file uses surrogateescape to
|
||||||
|
# preserve original bytes in unicode string for decoding errors.
|
||||||
|
# convert str or Header back to original bytes
|
||||||
|
if hasattr(val, '_chunks'):
|
||||||
|
# val is a Header object for invalid header values
|
||||||
|
v = b''
|
||||||
|
for s,charset in val._chunks:
|
||||||
|
# recover the original bytes
|
||||||
|
b = s.encode(encoding='ascii',errors='surrogateescape')
|
||||||
|
v += b
|
||||||
|
else:
|
||||||
|
v = val.encode(encoding='ascii',errors='surrogateescape')
|
||||||
|
# invoke the Milter header_callback
|
||||||
|
return self._priv.header_bytes(fld,v)
|
||||||
|
|
||||||
def _eoh(self):
|
def _eoh(self):
|
||||||
if self._protocol & Milter.P_NOEOH:
|
if self._protocol & Milter.P_NOEOH:
|
||||||
|
|||||||
+2
-1
@@ -216,7 +216,8 @@ def parse_header(val):
|
|||||||
u.append(s.decode())
|
u.append(s.decode())
|
||||||
else:
|
else:
|
||||||
u.append(s.decode())
|
u.append(s.decode())
|
||||||
u = u''.join(u)
|
u = ''.join(u)
|
||||||
|
if type(u) is str: return u
|
||||||
for enc in ('us-ascii','iso-8859-1','utf-8'):
|
for enc in ('us-ascii','iso-8859-1','utf-8'):
|
||||||
try:
|
try:
|
||||||
return u.encode(enc)
|
return u.encode(enc)
|
||||||
|
|||||||
+34
-61
@@ -1,5 +1,4 @@
|
|||||||
Abstract
|
# Abstract
|
||||||
--------
|
|
||||||
|
|
||||||
This is a python extension module to enable python scripts to attach to
|
This is a python extension module to enable python scripts to attach to
|
||||||
Sendmail's libmilter API, enabling filtering of messages as they arrive.
|
Sendmail's libmilter API, enabling filtering of messages as they arrive.
|
||||||
@@ -7,45 +6,27 @@ Since it's a script, you can do anything you want to the message - screen
|
|||||||
out viruses, collect statistics, add or modify headers, etc. You can, at
|
out viruses, collect statistics, add or modify headers, etc. You can, at
|
||||||
any point, tell Sendmail to reject, discard, or accept the message.
|
any point, tell Sendmail to reject, discard, or accept the message.
|
||||||
|
|
||||||
|
Additional python modules provide for navigating and modifying MIME parts, and
|
||||||
|
sending DSNs or doing CBVs.
|
||||||
|
|
||||||
Requirements
|
# Requirements
|
||||||
------------
|
|
||||||
|
|
||||||
Python milter extension: http://https://pypi.python.org/pypi/pymilter/
|
Python milter extension: https://pypi.org/project/pymilter/
|
||||||
Python: http://www.python.org
|
Python: http://www.python.org
|
||||||
Sendmail: http://www.sendmail.org
|
Sendmail: http://www.sendmail.org
|
||||||
|
or
|
||||||
|
Postfix: http://www.postfix.org/MILTER_README.html
|
||||||
|
|
||||||
NB: From Sendmail's libmilter/README:
|
# Quick Installation
|
||||||
|
|
||||||
libmilter requires pthread support in the operating system. Moreover, it
|
|
||||||
requires that the library functions it uses are thread safe; which is true
|
|
||||||
for the operating systems libmilter has been developed and tested on. On
|
|
||||||
some operating systems this requires special compile time options (e.g.,
|
|
||||||
not just -pthread). libmilter is currently known to work on (modulo problems
|
|
||||||
in the pthread support of some specific versions):
|
|
||||||
|
|
||||||
FreeBSD 3.x, 4.x
|
|
||||||
SunOS 5.x (x >= 5)
|
|
||||||
AIX 4.3.x
|
|
||||||
HP UX 11.x
|
|
||||||
Linux (recent versions/distributions)
|
|
||||||
|
|
||||||
libmilter is currently not supported on:
|
|
||||||
|
|
||||||
IRIX 6.x
|
|
||||||
Ultrix
|
|
||||||
|
|
||||||
Quick Installation
|
|
||||||
------------------
|
|
||||||
|
|
||||||
1. Build and install Sendmail, enabling libmilter (see libmilter/README).
|
1. Build and install Sendmail, enabling libmilter (see libmilter/README).
|
||||||
2. Build and install Python, enabling threading.
|
2. Build and install Python, enabling threading.
|
||||||
3. Install this module: python setup.py --help
|
3. Install this module: python setup.py --help
|
||||||
4. Add these two lines to sendmail.cf[*]:
|
4. Add these two lines to sendmail.cf[a]:
|
||||||
|
```
|
||||||
O InputMailFilters=pythonfilter
|
O InputMailFilters=pythonfilter
|
||||||
Xpythonfilter, S=local:/home/username/pythonsock
|
Xpythonfilter, S=local:/home/username/pythonsock
|
||||||
|
```
|
||||||
5. Run the sample.py example milter with: python sample.py
|
5. Run the sample.py example milter with: python sample.py
|
||||||
Note that milters should almost certainly not run as root.
|
Note that milters should almost certainly not run as root.
|
||||||
|
|
||||||
@@ -54,16 +35,15 @@ some email will be rejected (see the "header" method). Edit and play.
|
|||||||
See spfmilter.py for a functional SPF milter, or see bms.py for an complex
|
See spfmilter.py for a functional SPF milter, or see bms.py for an complex
|
||||||
milter used in production.
|
milter used in production.
|
||||||
|
|
||||||
[*] This is for a quick test. Your sendmail.cf in most distros will get
|
[a] This is for a quick test. Your sendmail.cf in most distros will get
|
||||||
overwritten whenever sendmail.mc is updated. To make a milter permanent,
|
overwritten whenever sendmail.mc is updated. To make a milter permanent,
|
||||||
add something like:
|
add something like:
|
||||||
|
```
|
||||||
INPUT_MAIL_FILTER(`pythonfilter', `S=local:/home/username/pythonsock, F=T, T=C:5m;S:20s;R:5m;E:5m')
|
INPUT_MAIL_FILTER(`pythonfilter', `S=local:/home/username/pythonsock, F=T, T=C:5m;S:20s;R:5m;E:5m')
|
||||||
|
```
|
||||||
to sendmail.mc instead.
|
to sendmail.mc instead.
|
||||||
|
|
||||||
Not-so-quick Installation
|
# Not-so-quick Installation
|
||||||
-------------------------
|
|
||||||
|
|
||||||
First install Sendmail. Make sure you read libmilter/README in the Sendmail
|
First install Sendmail. Make sure you read libmilter/README in the Sendmail
|
||||||
source directory, and make sure you enable libmilter before you build. The
|
source directory, and make sure you enable libmilter before you build. The
|
||||||
@@ -76,18 +56,13 @@ Install this miltermodule package; DistUtils Automatic Installation:
|
|||||||
|
|
||||||
$ python setup.py --help
|
$ python setup.py --help
|
||||||
|
|
||||||
For versions of python prior to 2.0, you will need to download distutils
|
|
||||||
separately or build manually. You will need to download unittest
|
|
||||||
separately to run the test programs. The bdist_rpm distutils option seems
|
|
||||||
not to work for python 2.0; upgrade to at least 2.1.1.
|
|
||||||
|
|
||||||
Now that everything is installed, we need to tell sendmail that we're going
|
Now that everything is installed, we need to tell sendmail that we're going
|
||||||
to filter incoming email. Add lines similar to the following to
|
to filter incoming email. Add lines similar to the following to
|
||||||
sendmail.cf:
|
sendmail.cf:
|
||||||
|
```
|
||||||
O InputMailFilters=pythonfilter
|
O InputMailFilters=pythonfilter
|
||||||
Xpythonfilter, S=local:/home/username/pythonsock
|
Xpythonfilter, S=local:/home/username/pythonsock
|
||||||
|
```
|
||||||
The "O" line tells sendmail which filters to use in what order; here we're
|
The "O" line tells sendmail which filters to use in what order; here we're
|
||||||
telling sendmail to use the filter named "pythonfilter".
|
telling sendmail to use the filter named "pythonfilter".
|
||||||
|
|
||||||
@@ -101,25 +76,24 @@ NB: The name is specified in two places: here, in sendmail's cf file, and
|
|||||||
in the milter itself. Make sure the two match.
|
in the milter itself. Make sure the two match.
|
||||||
|
|
||||||
NB: The above lines can be added in your .mc file with this line:
|
NB: The above lines can be added in your .mc file with this line:
|
||||||
|
```
|
||||||
INPUT_MAIL_FILTER(`pythonfilter', `S=local:/home/username/pythonsock')
|
INPUT_MAIL_FILTER(`pythonfilter', `S=local:/home/username/pythonsock')
|
||||||
|
```
|
||||||
For versions of sendmail prior to 8.12, you will need to enable
|
For versions of sendmail prior to 8.12, you will need to enable
|
||||||
_FFR_MILTER for the cf macros. For example,
|
`_FFR_MILTER` for the cf macros. For example,
|
||||||
|
```
|
||||||
m4 -D_FFR_MILTER ../m4/cf.m4 myconfig.mc > myconfig.cf
|
m4 -D_FFR_MILTER ../m4/cf.m4 myconfig.mc > myconfig.cf
|
||||||
|
```
|
||||||
IPv6 Notes
|
# IPv6 Notes
|
||||||
----------
|
|
||||||
|
|
||||||
The IPv6 protocol is supported if your operation system supports it
|
The IPv6 protocol is supported if your operation system supports it
|
||||||
and if sendmail was compiled with IPv6 support. To determine if your
|
and if sendmail was compiled with IPv6 support. To determine if your
|
||||||
sendmail supports IPv6, run "sendmail -d0" and check for the NETINET6
|
sendmail supports IPv6, run "sendmail -d0" and check for the NETINET6
|
||||||
compilation option. To compile sendmail with IPv6 support, add this
|
compilation option. To compile sendmail with IPv6 support, add this
|
||||||
declaration to your site.config.m4 before building it:
|
declaration to your site.config.m4 before building it:
|
||||||
|
```
|
||||||
APPENDDEF(`confENVDEF', `-DNETINET6=1')
|
APPENDDEF(`confENVDEF', `-DNETINET6=1')
|
||||||
|
```
|
||||||
IPv6 support can show up in two places; the communications socket
|
IPv6 support can show up in two places; the communications socket
|
||||||
between the milter and sendmail processes and in the host address
|
between the milter and sendmail processes and in the host address
|
||||||
argument to the connect() callback method.
|
argument to the connect() callback method.
|
||||||
@@ -130,26 +104,26 @@ want to allow both IPv4 and IPv6 connections, some operating systems
|
|||||||
will require that each listens to different port numbers. For an
|
will require that each listens to different port numbers. For an
|
||||||
IPv6-only setup, your sendmail configuration should contain a line
|
IPv6-only setup, your sendmail configuration should contain a line
|
||||||
similar to (first line is for sendmail.mc, second is sendmail.cf):
|
similar to (first line is for sendmail.mc, second is sendmail.cf):
|
||||||
|
```
|
||||||
DAEMON_OPTIONS(`Name=MTA-v6, Family=inet6, Modify=C, Port=25')
|
DAEMON_OPTIONS(`Name=MTA-v6, Family=inet6, Modify=C, Port=25')
|
||||||
O DaemonPortOptions=Name=MTA-v6, Family=inet6, Modify=C, Port=25
|
O DaemonPortOptions=Name=MTA-v6, Family=inet6, Modify=C, Port=25
|
||||||
|
```
|
||||||
To allow sendmail and the milter process to communicate with each
|
To allow sendmail and the milter process to communicate with each
|
||||||
other over IPv6, you may use the "inet6" socket name prefix, as in:
|
other over IPv6, you may use the "inet6" socket name prefix, as in:
|
||||||
|
```
|
||||||
Xpythonfilter, S=inet6:1234@fec0:0:0:7::5c
|
Xpythonfilter, S=inet6:1234@fec0:0:0:7::5c
|
||||||
|
```
|
||||||
The connect() callback method in the milter class will pass the
|
The connect() callback method in the milter class will pass the
|
||||||
IPv6-specific information in the 'hostaddr' argument as a tuple. Note
|
IPv6-specific information in the 'hostaddr' argument as a tuple. Note
|
||||||
that the type of this value is dependent upon the protocol family, and
|
that the type of this value is dependent upon the protocol family, and
|
||||||
is not compatible with IPv4 connections. Therefore you should always
|
is not compatible with IPv4 connections. Therefore you should always
|
||||||
check the family argument before attempting to use the hostaddr
|
check the family argument before attempting to use the hostaddr
|
||||||
argument. A quick example showing this follows:
|
argument. A quick example showing this follows:
|
||||||
|
```
|
||||||
import socket
|
import socket
|
||||||
...
|
|
||||||
class ipv6awareMilter(Milter.Milter):
|
class ipv6awareMilter(Milter.Milter):
|
||||||
...
|
|
||||||
def connect(self,hostname,family,hostaddr):
|
def connect(self,hostname,family,hostaddr):
|
||||||
if family==socket.AF_INET:
|
if family==socket.AF_INET:
|
||||||
ipaddress, port = hostaddr
|
ipaddress, port = hostaddr
|
||||||
@@ -157,7 +131,7 @@ argument. A quick example showing this follows:
|
|||||||
ip6address, port, flowinfo, scopeid = hostaddr
|
ip6address, port, flowinfo, scopeid = hostaddr
|
||||||
elif family==socket.AF_UNIX:
|
elif family==socket.AF_UNIX:
|
||||||
socketpath = hostaddr
|
socketpath = hostaddr
|
||||||
|
```
|
||||||
The hostname argument is always safe to use without interpreting the
|
The hostname argument is always safe to use without interpreting the
|
||||||
protocol family. For IPv6 connections for which the hostname can not
|
protocol family. For IPv6 connections for which the hostname can not
|
||||||
be determined the hostname will appear similar to the string
|
be determined the hostname will appear similar to the string
|
||||||
@@ -165,8 +139,7 @@ be determined the hostname will appear similar to the string
|
|||||||
RFC 2553 for information on interpreting and using the flowinfo and
|
RFC 2553 for information on interpreting and using the flowinfo and
|
||||||
scopeid socket attributes, both of which are integers.
|
scopeid socket attributes, both of which are integers.
|
||||||
|
|
||||||
Authors
|
# Authors
|
||||||
-------
|
|
||||||
|
|
||||||
Jim Niemira (urmane@urmane.org) wrote the original C module and some quick
|
Jim Niemira (urmane@urmane.org) wrote the original C module and some quick
|
||||||
and dirty python to use it. Stuart D. Gathman (stuart@gathman.org) took that
|
and dirty python to use it. Stuart D. Gathman (stuart@gathman.org) took that
|
||||||
@@ -1,2 +1,4 @@
|
|||||||
|
Test case for Milter/dsn.py
|
||||||
|
|
||||||
Lookup exact RFC syntax of real name / email and make
|
Lookup exact RFC syntax of real name / email and make
|
||||||
Milter.utils.parse_addr() pass all unit tests.
|
Milter.utils.parse_addr() pass all unit tests.
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
web:
|
web:
|
||||||
doxygen
|
doxygen
|
||||||
test -L doc/html/milter_api || ln -sf /usr/share/doc/sendmail-milter-devel doc/html/milter_api
|
test -L doc/html/milter_api || ln -sf /usr/share/doc/sendmail-milter-devel doc/html/milter_api
|
||||||
rsync -ravKk doc/html/ bmsi.com:/var/www/html/pymilter
|
rsync -ravKk doc/html/ pymilter.org:/var/www/html/milter/pymilter
|
||||||
cd doc/html; zip -r ../../doc .
|
cd doc/html; zip -r ../../doc .
|
||||||
|
|
||||||
VERSION=1.0.4
|
VERSION=1.0.6
|
||||||
PKG=pymilter-$(VERSION)
|
PKG=pymilter-$(VERSION)
|
||||||
SRCTAR=$(PKG).tar.gz
|
SRCTAR=$(PKG).tar.gz
|
||||||
|
|
||||||
$(SRCTAR):
|
$(SRCTAR):
|
||||||
git archive --format=tar.gz --prefix=$(PKG)/ -o $(SRCTAR) $(PKG)
|
git archive --format=tar.gz --prefix=$(PKG)/ -o $(SRCTAR) $(PKG)
|
||||||
|
|
||||||
|
# add extra copy of name like github so annoyingly does...
|
||||||
|
github:
|
||||||
|
git archive --format=tar.gz --prefix=pymilter-$(PKG)/ -o $(SRCTAR) $(PKG)
|
||||||
|
|
||||||
gittar: $(SRCTAR)
|
gittar: $(SRCTAR)
|
||||||
|
|||||||
+19
-8
@@ -43,6 +43,7 @@ $ python setup.py help
|
|||||||
#error MAX_ML_REPLY must be 1 or 11 or 32
|
#error MAX_ML_REPLY must be 1 or 11 or 32
|
||||||
#endif
|
#endif
|
||||||
#define _FFR_MULTILINE (MAX_ML_REPLY > 1)
|
#define _FFR_MULTILINE (MAX_ML_REPLY > 1)
|
||||||
|
#define PY_SSIZE_T_CLEAN
|
||||||
|
|
||||||
//#include <pthread.h> // shouldn't be needed - use Python API
|
//#include <pthread.h> // shouldn't be needed - use Python API
|
||||||
#include <Python.h> // Python C API
|
#include <Python.h> // Python C API
|
||||||
@@ -71,7 +72,7 @@ $ python setup.py help
|
|||||||
* published. Unfortunately I know of no good way to do this
|
* published. Unfortunately I know of no good way to do this
|
||||||
* other than with OS-specific tests.
|
* other than with OS-specific tests.
|
||||||
*/
|
*/
|
||||||
#if defined(__FreeBSD__) || defined(__linux__) || defined(__sun__) || defined(__GLIBC__) || (defined(__APPLE__) && defined(__MACH__))
|
#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__linux__) || defined(__sun__) || defined(__GLIBC__) || (defined(__APPLE__) && defined(__MACH__))
|
||||||
#define HAVE_IPV6_RFC2553
|
#define HAVE_IPV6_RFC2553
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
#endif
|
#endif
|
||||||
@@ -250,7 +251,7 @@ static const char milter_set_flags__doc__[] =
|
|||||||
Set flags for filter capabilities; OR of one or more of:\n\
|
Set flags for filter capabilities; OR of one or more of:\n\
|
||||||
ADDHDRS - filter may add headers\n\
|
ADDHDRS - filter may add headers\n\
|
||||||
CHGBODY - filter may replace body\n\
|
CHGBODY - filter may replace body\n\
|
||||||
CHGFROM - filter may replace body\n\
|
CHGFROM - filter may replace sender\n\
|
||||||
ADDRCPT - filter may add recipients\n\
|
ADDRCPT - filter may add recipients\n\
|
||||||
DELRCPT - filter may delete recipients\n\
|
DELRCPT - filter may delete recipients\n\
|
||||||
CHGHDRS - filter may change/delete headers";
|
CHGHDRS - filter may change/delete headers";
|
||||||
@@ -490,7 +491,7 @@ _generic_wrapper(milter_ContextObject *self, PyObject *cb, PyObject *arglist) {
|
|||||||
int retval;
|
int retval;
|
||||||
|
|
||||||
if (arglist == NULL) return _report_exception(self);
|
if (arglist == NULL) return _report_exception(self);
|
||||||
result = PyEval_CallObject(cb, arglist);
|
result = PyObject_CallObject(cb, arglist);
|
||||||
Py_DECREF(arglist);
|
Py_DECREF(arglist);
|
||||||
if (result == NULL) return _report_exception(self);
|
if (result == NULL) return _report_exception(self);
|
||||||
#if PY_MAJOR_VERSION >= 3
|
#if PY_MAJOR_VERSION >= 3
|
||||||
@@ -643,7 +644,7 @@ generic_env_wrapper(SMFICTX *ctx, PyObject*cb, char **argv) {
|
|||||||
/* There's some error checking performed in do_mkvalue() for a string */
|
/* There's some error checking performed in do_mkvalue() for a string */
|
||||||
/* that's not currently done here - it probably should be */
|
/* that's not currently done here - it probably should be */
|
||||||
#if PY_MAJOR_VERSION >= 3
|
#if PY_MAJOR_VERSION >= 3
|
||||||
PyObject *o = PyUnicode_FromStringAndSize(argv[i], strlen(argv[i]));
|
PyObject *o = PyBytes_FromStringAndSize(argv[i], strlen(argv[i]));
|
||||||
#else
|
#else
|
||||||
PyObject *o = PyString_FromStringAndSize(argv[i], strlen(argv[i]));
|
PyObject *o = PyString_FromStringAndSize(argv[i], strlen(argv[i]));
|
||||||
#endif
|
#endif
|
||||||
@@ -674,7 +675,12 @@ milter_wrap_header(SMFICTX *ctx, char *headerf, char *headerv) {
|
|||||||
if (header_callback == NULL) return SMFIS_CONTINUE;
|
if (header_callback == NULL) return SMFIS_CONTINUE;
|
||||||
c = _get_context(ctx);
|
c = _get_context(ctx);
|
||||||
if (!c) return SMFIS_TEMPFAIL;
|
if (!c) return SMFIS_TEMPFAIL;
|
||||||
|
#if PY_MAJOR_VERSION >= 3
|
||||||
|
/* pass val as bytes so Milter.Base.header_bytes can do surrogate escape. */
|
||||||
|
arglist = Py_BuildValue("(Osy)", c, headerf, headerv);
|
||||||
|
#else
|
||||||
arglist = Py_BuildValue("(Oss)", c, headerf, headerv);
|
arglist = Py_BuildValue("(Oss)", c, headerf, headerv);
|
||||||
|
#endif
|
||||||
return _generic_wrapper(c, header_callback, arglist);
|
return _generic_wrapper(c, header_callback, arglist);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -704,9 +710,9 @@ milter_wrap_body(SMFICTX *ctx, u_char *bodyp, size_t bodylen) {
|
|||||||
if (!c) return SMFIS_TEMPFAIL;
|
if (!c) return SMFIS_TEMPFAIL;
|
||||||
/* Unclear whether this should be s#, z#, or t# */
|
/* Unclear whether this should be s#, z#, or t# */
|
||||||
#if PY_MAJOR_VERSION >= 3
|
#if PY_MAJOR_VERSION >= 3
|
||||||
arglist = Py_BuildValue("(Oy#)", c, bodyp, bodylen);
|
arglist = Py_BuildValue("(Oy#)", c, bodyp, (Py_ssize_t)bodylen);
|
||||||
#else
|
#else
|
||||||
arglist = Py_BuildValue("(Os#)", c, bodyp, bodylen);
|
arglist = Py_BuildValue("(Os#)", c, bodyp, (Py_ssize_t)bodylen);
|
||||||
#endif
|
#endif
|
||||||
return _generic_wrapper(c, body_callback, arglist);
|
return _generic_wrapper(c, body_callback, arglist);
|
||||||
}
|
}
|
||||||
@@ -898,7 +904,10 @@ milter_main(PyObject *self, PyObject *args) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
/* libmilter requires thread support */
|
/* libmilter requires thread support */
|
||||||
|
#if PY_VERSION_HEX < 0x03070000
|
||||||
|
/* called in Py_Initialize beginning with 3.7 */
|
||||||
PyEval_InitThreads();
|
PyEval_InitThreads();
|
||||||
|
#endif
|
||||||
/* let other threads run while in smfi_main() */
|
/* let other threads run while in smfi_main() */
|
||||||
interp = PyThreadState_Get()->interp;
|
interp = PyThreadState_Get()->interp;
|
||||||
_main = PyEval_SaveThread(); /* must be done before smfi_main() */
|
_main = PyEval_SaveThread(); /* must be done before smfi_main() */
|
||||||
@@ -1129,6 +1138,8 @@ milter_chgfrom(PyObject *self, PyObject *args) {
|
|||||||
SMFICTX *ctx;
|
SMFICTX *ctx;
|
||||||
PyThreadState *t;
|
PyThreadState *t;
|
||||||
|
|
||||||
|
/* FIXME: use s# to transition to allow passing bytes, but milter api
|
||||||
|
* requires NUL terminated bytes. */
|
||||||
if (!PyArg_ParseTuple(args, "s|z:chgfrom", &sender, ¶ms))
|
if (!PyArg_ParseTuple(args, "s|z:chgfrom", &sender, ¶ms))
|
||||||
return NULL;
|
return NULL;
|
||||||
ctx = _find_context(self);
|
ctx = _find_context(self);
|
||||||
@@ -1225,7 +1236,7 @@ can only be called from the EOM callback.";
|
|||||||
static PyObject *
|
static PyObject *
|
||||||
milter_replacebody(PyObject *self, PyObject *args) {
|
milter_replacebody(PyObject *self, PyObject *args) {
|
||||||
char *bodyp;
|
char *bodyp;
|
||||||
int bodylen;
|
Py_ssize_t bodylen;
|
||||||
SMFICTX *ctx;
|
SMFICTX *ctx;
|
||||||
PyThreadState *t;
|
PyThreadState *t;
|
||||||
|
|
||||||
@@ -1234,7 +1245,7 @@ milter_replacebody(PyObject *self, PyObject *args) {
|
|||||||
if (ctx == NULL) return NULL;
|
if (ctx == NULL) return NULL;
|
||||||
t = PyEval_SaveThread();
|
t = PyEval_SaveThread();
|
||||||
return _thread_return(t,smfi_replacebody(ctx,
|
return _thread_return(t,smfi_replacebody(ctx,
|
||||||
(unsigned char *)bodyp, bodylen), "cannot replace message body");
|
(unsigned char *)bodyp, (int)bodylen), "cannot replace message body");
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char milter_setpriv__doc__[] =
|
static const char milter_setpriv__doc__[] =
|
||||||
|
|||||||
@@ -1,87 +1,3 @@
|
|||||||
# $Log$
|
|
||||||
# Revision 1.8 2011/11/05 15:51:03 customdesigned
|
|
||||||
# New example
|
|
||||||
#
|
|
||||||
# Revision 1.7 2009/06/13 21:15:12 customdesigned
|
|
||||||
# Doxygen updates.
|
|
||||||
#
|
|
||||||
# Revision 1.6 2009/06/09 03:13:13 customdesigned
|
|
||||||
# More doxygen docs.
|
|
||||||
#
|
|
||||||
# Revision 1.5 2005/07/20 14:49:43 customdesigned
|
|
||||||
# Handle corrupt and empty ZIP files.
|
|
||||||
#
|
|
||||||
# Revision 1.4 2005/06/17 01:49:39 customdesigned
|
|
||||||
# Handle zip within zip.
|
|
||||||
#
|
|
||||||
# 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
|
|
||||||
# _parseparam replacement not needed for python2.4
|
|
||||||
#
|
|
||||||
# Revision 1.61 2005/02/12 02:11:11 stuart
|
|
||||||
# Pass unit tests with python2.4.
|
|
||||||
#
|
|
||||||
# Revision 1.60 2005/02/11 18:34:14 stuart
|
|
||||||
# Handle garbage after quote in boundary.
|
|
||||||
#
|
|
||||||
# Revision 1.59 2005/02/10 01:10:59 stuart
|
|
||||||
# Fixed MimeMessage.ismodified()
|
|
||||||
#
|
|
||||||
# Revision 1.58 2005/02/10 00:56:49 stuart
|
|
||||||
# Runs with python2.4. Defang not working correctly - more work needed.
|
|
||||||
#
|
|
||||||
# Revision 1.57 2004/11/20 16:37:52 stuart
|
|
||||||
# fix regex for splitting header and body
|
|
||||||
#
|
|
||||||
# Revision 1.56 2004/11/09 20:33:51 stuart
|
|
||||||
# Recognize more dynamic PTR variations.
|
|
||||||
#
|
|
||||||
# Revision 1.55 2004/10/06 21:39:20 stuart
|
|
||||||
# Handle message attachments with boundary errors by not parsing them
|
|
||||||
# until needed.
|
|
||||||
#
|
|
||||||
# Revision 1.54 2004/08/18 01:59:46 stuart
|
|
||||||
# Handle mislabeled multipart messages
|
|
||||||
#
|
|
||||||
# Revision 1.53 2004/04/24 22:53:20 stuart
|
|
||||||
# Rename some local variables to avoid shadowing builtins
|
|
||||||
#
|
|
||||||
# Revision 1.52 2004/04/24 22:47:13 stuart
|
|
||||||
# Convert header values to str
|
|
||||||
#
|
|
||||||
# Revision 1.51 2004/03/25 03:19:10 stuart
|
|
||||||
# Correctly defang rfc822 attachments when boundary specified with
|
|
||||||
# content-type message/rfc822.
|
|
||||||
#
|
|
||||||
# Revision 1.50 2003/10/15 22:01:00 stuart
|
|
||||||
# Test for and work around email bug with encoded filenames.
|
|
||||||
#
|
|
||||||
# Revision 1.49 2003/09/04 18:48:13 stuart
|
|
||||||
# Support python-2.2.3
|
|
||||||
#
|
|
||||||
# Revision 1.48 2003/09/02 00:27:27 stuart
|
|
||||||
# Should have full milter based dspam support working
|
|
||||||
#
|
|
||||||
# Revision 1.47 2003/08/26 06:08:18 stuart
|
|
||||||
# Use new python boolean since we now require 2.2.2
|
|
||||||
#
|
|
||||||
# Revision 1.46 2003/08/26 05:01:38 stuart
|
|
||||||
# Release 0.6.0
|
|
||||||
#
|
|
||||||
# Revision 1.45 2003/08/26 04:01:24 stuart
|
|
||||||
# Use new email module for parsing mail. Still need mime module to
|
|
||||||
# provide various bug fixes to email module, and maintain some compatibility
|
|
||||||
# with old milter code.
|
|
||||||
#
|
|
||||||
|
|
||||||
## @package mime
|
## @package mime
|
||||||
# This module provides a "defang" function to replace naughty attachments.
|
# This module provides a "defang" function to replace naughty attachments.
|
||||||
#
|
#
|
||||||
@@ -108,10 +24,11 @@ import email
|
|||||||
from email.message import Message
|
from email.message import Message
|
||||||
try:
|
try:
|
||||||
from email.generator import BytesGenerator
|
from email.generator import BytesGenerator
|
||||||
from email import message_from_binary_file
|
from email import message_from_binary_file, encoders
|
||||||
except:
|
except:
|
||||||
from email.generator import Generator as BytesGenerator
|
from email.generator import Generator as BytesGenerator
|
||||||
from email import message_from_file as message_from_binary_file
|
from email import message_from_file as message_from_binary_file
|
||||||
|
from email import Encoders as encoders
|
||||||
from email.utils import quote
|
from email.utils import quote
|
||||||
|
|
||||||
if not getattr(Message,'as_bytes',None):
|
if not getattr(Message,'as_bytes',None):
|
||||||
@@ -208,7 +125,7 @@ class MimeMessage(Message):
|
|||||||
"""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 = []
|
||||||
for attr,val in self._get_params_preserve([],'content-type'):
|
for attr,val in self.get_params([],'content-type',False):
|
||||||
if isinstance(val, tuple):
|
if isinstance(val, tuple):
|
||||||
# It's an RFC 2231 encoded parameter
|
# It's an RFC 2231 encoded parameter
|
||||||
newvalue = _unquotevalue(val)
|
newvalue = _unquotevalue(val)
|
||||||
@@ -278,6 +195,11 @@ class MimeMessage(Message):
|
|||||||
|
|
||||||
def get_payload(self,i=None,decode=False):
|
def get_payload(self,i=None,decode=False):
|
||||||
msg = self.submsg
|
msg = self.submsg
|
||||||
|
if msg is None:
|
||||||
|
t = self.get_content_type().lower()
|
||||||
|
if t == 'message/rfc822' or t.startswith('multipart/'):
|
||||||
|
msg = super().get_payload()
|
||||||
|
self.submsg = msg
|
||||||
if isinstance(msg,Message) and msg.ismodified():
|
if isinstance(msg,Message) and msg.ismodified():
|
||||||
self.set_payload([msg])
|
self.set_payload([msg])
|
||||||
return Message.get_payload(self,i,decode)
|
return Message.get_payload(self,i,decode)
|
||||||
@@ -296,7 +218,11 @@ class MimeMessage(Message):
|
|||||||
if t == 'message/rfc822' or t.startswith('multipart/'):
|
if t == 'message/rfc822' or t.startswith('multipart/'):
|
||||||
if not self.submsg:
|
if not self.submsg:
|
||||||
txt = self.get_payload()
|
txt = self.get_payload()
|
||||||
if type(txt) == str:
|
if type(txt) is bytes:
|
||||||
|
self.submsg = email.message_from_bytes(txt,MimeMessage)
|
||||||
|
for part in self.submsg.walk():
|
||||||
|
part.modified = False
|
||||||
|
elif type(txt) is str:
|
||||||
txt = self.get_payload(decode=True)
|
txt = self.get_payload(decode=True)
|
||||||
self.submsg = email.message_from_string(txt,MimeMessage)
|
self.submsg = email.message_from_string(txt,MimeMessage)
|
||||||
for part in self.submsg.walk():
|
for part in self.submsg.walk():
|
||||||
@@ -357,17 +283,24 @@ def check_name(msg,savname=None,ckname=check_ext,scan_zip=False):
|
|||||||
msg["Content-Type"] = "text/plain; name="+name
|
msg["Content-Type"] = "text/plain; name="+name
|
||||||
return Milter.CONTINUE
|
return Milter.CONTINUE
|
||||||
|
|
||||||
def check_attachments(msg,check):
|
def check_attachments(msg,check,lev=None):
|
||||||
"""Scan attachments.
|
"""Scan attachments.
|
||||||
msg MimeMessage
|
msg MimeMessage
|
||||||
check function(MimeMessage): int
|
check function(MimeMessage): int
|
||||||
Return CONTINUE, REJECT, ACCEPT
|
Return CONTINUE, REJECT, ACCEPT
|
||||||
"""
|
"""
|
||||||
if msg.is_multipart():
|
if msg.is_multipart():
|
||||||
|
if not lev: lev = []
|
||||||
|
lev.append(1)
|
||||||
|
if msg.get_content_type().endswith('/rfc822'):
|
||||||
|
foo = 1
|
||||||
for i in msg.get_payload():
|
for i in msg.get_payload():
|
||||||
rc = check_attachments(i,check)
|
print('chkm',lev,msg.get_content_type())
|
||||||
|
rc = check_attachments(i,check,lev=lev)
|
||||||
if rc != Milter.CONTINUE: return rc
|
if rc != Milter.CONTINUE: return rc
|
||||||
|
lev[-1] += 1
|
||||||
return Milter.CONTINUE
|
return Milter.CONTINUE
|
||||||
|
print('chk',lev,msg.get_content_type())
|
||||||
return check(msg)
|
return check(msg)
|
||||||
|
|
||||||
# save call context for Python without nested_scopes
|
# save call context for Python without nested_scopes
|
||||||
@@ -534,7 +467,7 @@ def check_html(msg,savname=None):
|
|||||||
if htmlfilter.modified:
|
if htmlfilter.modified:
|
||||||
msg.set_payload(out) # remove embedded scripts
|
msg.set_payload(out) # remove embedded scripts
|
||||||
del msg["content-transfer-encoding"]
|
del msg["content-transfer-encoding"]
|
||||||
email.Encoders.encode_quopri(msg)
|
encoders.encode_quopri(msg)
|
||||||
return Milter.CONTINUE
|
return Milter.CONTINUE
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
@@ -548,7 +481,7 @@ if __name__ == '__main__':
|
|||||||
return Milter.CONTINUE
|
return Milter.CONTINUE
|
||||||
|
|
||||||
for fname in sys.argv[1:]:
|
for fname in sys.argv[1:]:
|
||||||
fp = open(fname,'rb')
|
with open(fname,'rb') as fp:
|
||||||
msg = message_from_file(fp)
|
msg = message_from_file(fp)
|
||||||
email.iterators._structure(msg)
|
email.iterators._structure(msg)
|
||||||
check_attachments(msg,_list_attach)
|
check_attachments(msg,_list_attach)
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
diff -up ./Milter/utils.py.check ./Milter/utils.py
|
||||||
|
--- ./Milter/utils.py.check 2018-08-04 23:01:23.858668412 -0400
|
||||||
|
+++ ./Milter/utils.py 2018-08-04 23:01:39.460869588 -0400
|
||||||
|
@@ -68,10 +68,6 @@ def iniplist(ipaddr,iplist):
|
||||||
|
True
|
||||||
|
>>> iniplist('192.168.0.45',['192.168.0.*'])
|
||||||
|
True
|
||||||
|
- >>> iniplist('4.2.2.2',['b.resolvers.Level3.net'])
|
||||||
|
- True
|
||||||
|
- >>> iniplist('2606:2800:220:1::',['example.com/40'])
|
||||||
|
- True
|
||||||
|
>>> iniplist('4.2.2.2',['nothing.example.com'])
|
||||||
|
False
|
||||||
|
>>> iniplist('2001:610:779:0:223:6cff:fe9a:9cf3',['127.0.0.1','172.20.1.0/24','2001:610:779::/48'])
|
||||||
|
diff -up ./test.py.check ./test.py
|
||||||
|
--- ./test.py.check 2018-08-04 23:04:58.609420815 -0400
|
||||||
|
+++ ./test.py 2018-08-04 23:05:40.070949438 -0400
|
||||||
|
@@ -14,6 +14,8 @@ def suite():
|
||||||
|
return s
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
+ import sys
|
||||||
|
try: os.remove('test/milter.log')
|
||||||
|
except: pass
|
||||||
|
- unittest.TextTestRunner().run(suite())
|
||||||
|
+ rc = unittest.TextTestRunner().run(suite())
|
||||||
|
+ sys.exit(len(rc.failures))
|
||||||
+5
-6
@@ -2,7 +2,7 @@
|
|||||||
%global sum Python interface to sendmail milter API
|
%global sum Python interface to sendmail milter API
|
||||||
%global __provides_exclude_from ^(%{python2_sitearch})/.*\\.so$
|
%global __provides_exclude_from ^(%{python2_sitearch})/.*\\.so$
|
||||||
%if 0%{?epel} == 7
|
%if 0%{?epel} == 7
|
||||||
%global python3 python34
|
%global python3 python36
|
||||||
%else
|
%else
|
||||||
%global python3 python3
|
%global python3 python3
|
||||||
%endif
|
%endif
|
||||||
@@ -13,7 +13,7 @@ Version: 1.0.4
|
|||||||
Release: 1%{?dist}
|
Release: 1%{?dist}
|
||||||
Url: http://bmsi.com/pymilter
|
Url: http://bmsi.com/pymilter
|
||||||
Source: https://github.com/sdgathman/pymilter/archive/pymilter-%{version}.tar.gz
|
Source: https://github.com/sdgathman/pymilter/archive/pymilter-%{version}.tar.gz
|
||||||
Source1: tmpfiles-python-pymilter.conf
|
#Source1: tmpfiles-python-pymilter.conf
|
||||||
# remove unit tests that require network for check
|
# remove unit tests that require network for check
|
||||||
Patch: pymilter-check.patch
|
Patch: pymilter-check.patch
|
||||||
License: GPLv2+
|
License: GPLv2+
|
||||||
@@ -79,11 +79,10 @@ with milters.
|
|||||||
|
|
||||||
%prep
|
%prep
|
||||||
%setup -q -n pymilter-pymilter-%{version}
|
%setup -q -n pymilter-pymilter-%{version}
|
||||||
%patch -p1 -b .check
|
#patch -p1 -b .check
|
||||||
|
|
||||||
%build
|
%build
|
||||||
%py2_build
|
%py2_build
|
||||||
#patch -p1 -b -z .py3 <milter.patch # not needed since 1.0.3
|
|
||||||
%py3_build
|
%py3_build
|
||||||
checkmodule -m -M -o pymilter.mod pymilter.te
|
checkmodule -m -M -o pymilter.mod pymilter.te
|
||||||
semodule_package -o pymilter.pp -m pymilter.mod
|
semodule_package -o pymilter.pp -m pymilter.mod
|
||||||
@@ -95,8 +94,8 @@ semodule_package -o pymilter.pp -m pymilter.mod
|
|||||||
mkdir -p %{buildroot}/run/milter
|
mkdir -p %{buildroot}/run/milter
|
||||||
mkdir -p %{buildroot}%{_localstatedir}/log/milter
|
mkdir -p %{buildroot}%{_localstatedir}/log/milter
|
||||||
mkdir -p %{buildroot}%{_libexecdir}/milter
|
mkdir -p %{buildroot}%{_libexecdir}/milter
|
||||||
mkdir -p %{buildroot}%{_prefix}/lib/tmpfiles.d
|
#mkdir -p %{buildroot}%{_prefix}/lib/tmpfiles.d
|
||||||
install -m 0644 %{SOURCE1} %{buildroot}%{_prefix}/lib/tmpfiles.d/%{name}.conf
|
#install -m 0644 %{SOURCE1} %{buildroot}%{_prefix}/lib/tmpfiles.d/%{name}.conf
|
||||||
|
|
||||||
# install selinux modules
|
# install selinux modules
|
||||||
mkdir -p %{buildroot}%{_datadir}/selinux/targeted
|
mkdir -p %{buildroot}%{_datadir}/selinux/targeted
|
||||||
|
|||||||
@@ -24,7 +24,12 @@ class sampleMilter(Milter.Milter):
|
|||||||
|
|
||||||
def log(self,*msg):
|
def log(self,*msg):
|
||||||
print("%s [%d]" % (strftime('%Y%b%d %H:%M:%S'),self.id),end=None)
|
print("%s [%d]" % (strftime('%Y%b%d %H:%M:%S'),self.id),end=None)
|
||||||
for i in msg: print(i,end=None)
|
for i in msg:
|
||||||
|
try:
|
||||||
|
print(i,end=None)
|
||||||
|
except UnicodeEncodeError:
|
||||||
|
s = i.encode(encoding='utf-8',errors='surrogateescape')
|
||||||
|
print(s,end=None)
|
||||||
print()
|
print()
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -62,28 +67,31 @@ class sampleMilter(Milter.Milter):
|
|||||||
self.log("rcpt to",to,str)
|
self.log("rcpt to",to,str)
|
||||||
return Milter.CONTINUE
|
return Milter.CONTINUE
|
||||||
|
|
||||||
|
@Milter.decode('bytes')
|
||||||
def header(self,name,val):
|
def header(self,name,val):
|
||||||
lname = name.lower()
|
lname = name.lower()
|
||||||
if lname == 'subject':
|
if lname == 'subject':
|
||||||
|
|
||||||
# even if we wanted the Taiwanese spam, we can't read Chinese
|
# even if we wanted the Taiwanese spam, we can't read Chinese
|
||||||
# (delete if you read chinese mail)
|
# (delete if you read chinese mail)
|
||||||
if val.startswith('=?big5') or val.startswith('=?ISO-2022-JP'):
|
#print('val=',val.encode(errors='surrogateescape'))
|
||||||
|
print('val=',val)
|
||||||
|
if val.startswith(b'=?big5') or val.startswith(b'=?ISO-2022-JP'):
|
||||||
self.log('REJECT: %s: %s' % (name,val))
|
self.log('REJECT: %s: %s' % (name,val))
|
||||||
#self.setreply('550','','Go away spammer')
|
#self.setreply('550','','Go away spammer')
|
||||||
return Milter.REJECT
|
return Milter.REJECT
|
||||||
|
|
||||||
# check for common spam keywords
|
# check for common spam keywords
|
||||||
if val.find("$$$") >= 0 or val.find("XXX") >= 0 \
|
if val.find(b"$$$") >= 0 or val.find(b"XXX") >= 0 \
|
||||||
or val.find("!!!") >= 0 or val.find("FREE") >= 0:
|
or val.find(b"!!!") >= 0 or val.find(b"FREE") >= 0:
|
||||||
self.log('REJECT: %s: %s' % (name,val))
|
self.log('REJECT: %s: %s' % (name,val))
|
||||||
#self.setreply('550','','Go away spammer')
|
#self.setreply('550','','Go away spammer')
|
||||||
return Milter.REJECT
|
return Milter.REJECT
|
||||||
|
|
||||||
# check for spam that pretends to be legal
|
# check for spam that pretends to be legal
|
||||||
lval = val.lower()
|
lval = val.lower()
|
||||||
if lval.startswith("adv:") or lval.startswith("adv.") \
|
if lval.startswith(b"adv:") or lval.startswith(b"adv.") \
|
||||||
or lval.find('viagra') >= 0:
|
or lval.find(b'viagra') >= 0:
|
||||||
self.log('REJECT: %s: %s' % (name,val))
|
self.log('REJECT: %s: %s' % (name,val))
|
||||||
return Milter.REJECT
|
return Milter.REJECT
|
||||||
|
|
||||||
@@ -95,7 +103,7 @@ class sampleMilter(Milter.Milter):
|
|||||||
|
|
||||||
# check for common bulk mailers
|
# check for common bulk mailers
|
||||||
if lname == 'x-mailer' and \
|
if lname == 'x-mailer' and \
|
||||||
val.lower() in ('direct email','calypso','mail bomber'):
|
val.lower() in (b'direct email',b'calypso',b'mail bomber'):
|
||||||
self.log('REJECT: %s: %s' % (name,val))
|
self.log('REJECT: %s: %s' % (name,val))
|
||||||
#self.setreply('550','','Go away spammer')
|
#self.setreply('550','','Go away spammer')
|
||||||
return Milter.REJECT
|
return Milter.REJECT
|
||||||
@@ -104,7 +112,7 @@ class sampleMilter(Milter.Milter):
|
|||||||
if lname in ('subject','x-mailer'):
|
if lname in ('subject','x-mailer'):
|
||||||
self.log('%s: %s' % (name,val))
|
self.log('%s: %s' % (name,val))
|
||||||
if self.fp:
|
if self.fp:
|
||||||
self.fp.write(("%s: %s\n" % (name,val)).encode()) # add header to buffer
|
self.fp.write(b"%s: %s\n" % (name.encode(),val)) # add header to buffer
|
||||||
return Milter.CONTINUE
|
return Milter.CONTINUE
|
||||||
|
|
||||||
def eoh(self):
|
def eoh(self):
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from distutils.core import setup, Extension
|
from setuptools import setup, Extension
|
||||||
|
|
||||||
if sys.version < '2.6.5':
|
if sys.version < '2.6.5':
|
||||||
sys.exit('ERROR: Sorry, python 2.6.5 is required for this module.')
|
sys.exit('ERROR: Sorry, python 2.6.5 is required for this module.')
|
||||||
|
|
||||||
|
with open("README.md", "r") as fh:
|
||||||
|
long_description = fh.read()
|
||||||
|
|
||||||
# FIXME: on some versions of sendmail, smutil is renamed to sm.
|
# FIXME: on some versions of sendmail, smutil is renamed to sm.
|
||||||
# On slackware and debian, leave it out entirely. It depends
|
# On slackware and debian, leave it out entirely. It depends
|
||||||
# on how libmilter was built by the sendmail package.
|
# on how libmilter was built by the sendmail package.
|
||||||
@@ -14,20 +17,16 @@ libdirs = ["/usr/lib/libmilter"] # needed for Debian
|
|||||||
modules = ["mime"]
|
modules = ["mime"]
|
||||||
|
|
||||||
# NOTE: importing Milter to obtain version fails when milter.so not built
|
# NOTE: importing Milter to obtain version fails when milter.so not built
|
||||||
setup(name = "pymilter", version = '1.0.4',
|
setup(name = "pymilter", version = '1.0.5',
|
||||||
description="Python interface to sendmail milter API",
|
description="Python interface to sendmail milter API",
|
||||||
long_description="""\
|
long_description=long_description,
|
||||||
This is a python extension module to enable python scripts to
|
long_description_content_type='text/markdown',
|
||||||
attach to sendmail's libmilter functionality. Additional python
|
|
||||||
modules provide for navigating and modifying MIME parts, and
|
|
||||||
sending DSNs or doing CBVs.
|
|
||||||
""",
|
|
||||||
author="Jim Niemira",
|
author="Jim Niemira",
|
||||||
author_email="urmane@urmane.org",
|
author_email="urmane@urmane.org",
|
||||||
maintainer="Stuart D. Gathman",
|
maintainer="Stuart D. Gathman",
|
||||||
maintainer_email="stuart@gathman.org",
|
maintainer_email="stuart@gathman.org",
|
||||||
license="GPL",
|
license="GPL",
|
||||||
url="https://pythonhosted.org/milter/",
|
url="https://www.pymilter.org/",
|
||||||
py_modules=modules,
|
py_modules=modules,
|
||||||
packages = ['Milter'],
|
packages = ['Milter'],
|
||||||
ext_modules=[
|
ext_modules=[
|
||||||
@@ -37,7 +36,10 @@ sending DSNs or doing CBVs.
|
|||||||
# set MAX_ML_REPLY to 1 for sendmail < 8.13
|
# set MAX_ML_REPLY to 1 for sendmail < 8.13
|
||||||
define_macros = [ ('MAX_ML_REPLY',32) ],
|
define_macros = [ ('MAX_ML_REPLY',32) ],
|
||||||
# save lots of debugging time testing rfc2553 compliance
|
# save lots of debugging time testing rfc2553 compliance
|
||||||
extra_compile_args = [ "-Werror=implicit-function-declaration" ]
|
extra_compile_args = [
|
||||||
|
"-Werror=implicit-function-declaration",
|
||||||
|
"-std=gnu17",
|
||||||
|
]
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
keywords = ['sendmail','milter'],
|
keywords = ['sendmail','milter'],
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
## To roll your own milter, create a class that extends Milter.
|
## To roll your own milter, create a class that extends Milter.
|
||||||
# See the pymilter project at http://bmsi.com/python/milter.html
|
# This is a useless example to show basic features of Milter.
|
||||||
# based on Sendmail's milter API
|
# See the pymilter project at https://pymilter.org based
|
||||||
|
# on Sendmail's milter API
|
||||||
# This code is open-source on the same terms as Python.
|
# This code is open-source on the same terms as Python.
|
||||||
|
|
||||||
## Milter calls methods of your class at milter events.
|
## Milter calls methods of your class at milter events.
|
||||||
@@ -10,21 +11,26 @@
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
import Milter
|
import Milter
|
||||||
try:
|
try:
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO as BytesIO
|
||||||
except:
|
except:
|
||||||
from io import StringIO
|
from io import BytesIO
|
||||||
import time
|
import time
|
||||||
import email
|
import email
|
||||||
|
from email import message_from_binary_file
|
||||||
|
from email import policy
|
||||||
|
import mimetypes
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
from socket import AF_INET, AF_INET6
|
from socket import AF_INET, AF_INET6
|
||||||
from Milter.utils import parse_addr
|
from Milter.utils import parse_addr
|
||||||
if True:
|
if True:
|
||||||
|
# for logging process - usually not needed
|
||||||
from multiprocessing import Process as Thread, Queue
|
from multiprocessing import Process as Thread, Queue
|
||||||
else:
|
else:
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from Queue import Queue
|
from Queue import Queue
|
||||||
|
|
||||||
logq = Queue(maxsize=4)
|
logq = None
|
||||||
|
|
||||||
class myMilter(Milter.Base):
|
class myMilter(Milter.Base):
|
||||||
|
|
||||||
@@ -78,9 +84,10 @@ class myMilter(Milter.Base):
|
|||||||
# NOTE: self.fp is only an *internal* copy of message data. You
|
# NOTE: self.fp is only an *internal* copy of message data. You
|
||||||
# must use addheader, chgheader, replacebody to change the message
|
# must use addheader, chgheader, replacebody to change the message
|
||||||
# on the MTA.
|
# on the MTA.
|
||||||
self.fp = StringIO()
|
self.fp = BytesIO()
|
||||||
self.canon_from = '@'.join(parse_addr(mailfrom))
|
self.canon_from = '@'.join(parse_addr(mailfrom))
|
||||||
self.fp.write('From %s %s\n' % (self.canon_from,time.ctime()))
|
self.fp.write(b'From %s %s\n' % (self.canon_from.encode(),
|
||||||
|
time.ctime().encode()))
|
||||||
return Milter.CONTINUE
|
return Milter.CONTINUE
|
||||||
|
|
||||||
|
|
||||||
@@ -95,12 +102,12 @@ class myMilter(Milter.Base):
|
|||||||
|
|
||||||
@Milter.noreply
|
@Milter.noreply
|
||||||
def header(self, name, hval):
|
def header(self, name, hval):
|
||||||
self.fp.write("%s: %s\n" % (name,hval)) # add header to buffer
|
self.fp.write(b'%s: %s\n' % (name.encode(),hval.encode())) # add header to buffer
|
||||||
return Milter.CONTINUE
|
return Milter.CONTINUE
|
||||||
|
|
||||||
@Milter.noreply
|
@Milter.noreply
|
||||||
def eoh(self):
|
def eoh(self):
|
||||||
self.fp.write("\n") # terminate headers
|
self.fp.write(b'\n') # terminate headers
|
||||||
return Milter.CONTINUE
|
return Milter.CONTINUE
|
||||||
|
|
||||||
@Milter.noreply
|
@Milter.noreply
|
||||||
@@ -110,7 +117,16 @@ class myMilter(Milter.Base):
|
|||||||
|
|
||||||
def eom(self):
|
def eom(self):
|
||||||
self.fp.seek(0)
|
self.fp.seek(0)
|
||||||
msg = email.message_from_file(self.fp)
|
msg = email.message_from_binary_file(self.fp, policy=policy.default)
|
||||||
|
|
||||||
|
#example on how to iterate through attachments
|
||||||
|
for attachment in msg.iter_attachments():
|
||||||
|
#attachment holds the attachment object so that it can be used with a new MIMEMultipart() message
|
||||||
|
self.log("Attachment filename is %s" % (attachment.get_filename(),))
|
||||||
|
self.log("Attachment content/type is %s" % (attachment.get_content_type(),))
|
||||||
|
data = attachment.get_content()
|
||||||
|
self.log("Attachment content is %s" % (data,))
|
||||||
|
|
||||||
# many milter functions can only be called from eom()
|
# many milter functions can only be called from eom()
|
||||||
# example of adding a Bcc:
|
# example of adding a Bcc:
|
||||||
self.addrcpt('<%s>' % 'spy@example.com')
|
self.addrcpt('<%s>' % 'spy@example.com')
|
||||||
@@ -128,13 +144,14 @@ class myMilter(Milter.Base):
|
|||||||
## === Support Functions ===
|
## === Support Functions ===
|
||||||
|
|
||||||
def log(self,*msg):
|
def log(self,*msg):
|
||||||
logq.put((msg,self.id,time.time()))
|
t = (msg,self.id,time.time())
|
||||||
|
if logq:
|
||||||
|
logq.put(t)
|
||||||
|
else:
|
||||||
|
# logmsg(*t)
|
||||||
|
pass
|
||||||
|
|
||||||
def background():
|
def logmsg(msg,id,ts):
|
||||||
while True:
|
|
||||||
t = logq.get()
|
|
||||||
if not t: break
|
|
||||||
msg,id,ts = t
|
|
||||||
print("%s [%d]" % (time.strftime('%Y%b%d %H:%M:%S',time.localtime(ts)),id),
|
print("%s [%d]" % (time.strftime('%Y%b%d %H:%M:%S',time.localtime(ts)),id),
|
||||||
end=None)
|
end=None)
|
||||||
# 2005Oct13 02:34:11 [1] msg1 msg2 msg3 ...
|
# 2005Oct13 02:34:11 [1] msg1 msg2 msg3 ...
|
||||||
@@ -142,12 +159,20 @@ def background():
|
|||||||
print()
|
print()
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
def background():
|
||||||
|
while True:
|
||||||
|
t = logq.get()
|
||||||
|
if not t: break
|
||||||
|
logmsg(*t)
|
||||||
|
|
||||||
## ===
|
## ===
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
bt = Thread(target=background)
|
bt = Thread(target=background)
|
||||||
bt.start()
|
bt.start()
|
||||||
socketname = "/home/stuart/pythonsock"
|
# This is NOT a good socket location for production, it is for
|
||||||
|
# playing around. I suggest /var/run/milter/myappnamesock for production.
|
||||||
|
socketname = os.path.expanduser('~/pythonsock')
|
||||||
timeout = 600
|
timeout = 600
|
||||||
# Register to have the Milter factory create instances of your class:
|
# Register to have the Milter factory create instances of your class:
|
||||||
Milter.factory = myMilter
|
Milter.factory = myMilter
|
||||||
@@ -163,4 +188,7 @@ def main():
|
|||||||
print("%s bms milter shutdown" % time.strftime('%Y%b%d %H:%M:%S'))
|
print("%s bms milter shutdown" % time.strftime('%Y%b%d %H:%M:%S'))
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
# You probably do not need a logging process, but if you do, this
|
||||||
|
# is one way to do it.
|
||||||
|
logq = Queue(maxsize=4)
|
||||||
main()
|
main()
|
||||||
@@ -3,6 +3,8 @@ import testmime
|
|||||||
import testsample
|
import testsample
|
||||||
import testutils
|
import testutils
|
||||||
import testgrey
|
import testgrey
|
||||||
|
import testcfg
|
||||||
|
import testpolicy
|
||||||
import os
|
import os
|
||||||
|
|
||||||
def suite():
|
def suite():
|
||||||
@@ -11,6 +13,8 @@ def suite():
|
|||||||
s.addTest(testsample.suite())
|
s.addTest(testsample.suite())
|
||||||
s.addTest(testutils.suite())
|
s.addTest(testutils.suite())
|
||||||
s.addTest(testgrey.suite())
|
s.addTest(testgrey.suite())
|
||||||
|
s.addTest(testcfg.suite())
|
||||||
|
s.addTest(testpolicy.suite())
|
||||||
return s
|
return s
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
SPF-Pass:example.com OK
|
||||||
|
SPF-Neutral:example.com REJECT
|
||||||
|
HELO-Neutral:example.com OK
|
||||||
|
SPF-Permerror:foo@bad.example.com OK
|
||||||
|
SPF-Permerror: REJECT
|
||||||
|
SMTP-Auth:good@example.com OK
|
||||||
|
SMTP-Auth:example.com REJECT
|
||||||
|
SMTP-Auth:bad@localhost.localdomain REJECT
|
||||||
|
SMTP-Test: REJECT
|
||||||
|
SMTP-Test:.baz.com WILDCARD
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# sample SRS configuration
|
||||||
|
[srs]
|
||||||
|
;secret="shhhh!"
|
||||||
|
;maxage=21
|
||||||
|
;hashlength=5
|
||||||
|
# if defined, SRS uses a database for opaque rewriting
|
||||||
|
;database=/var/log/milter/srsdata
|
||||||
|
# sign these domains using SES to prevent forged bounces instead of SRS
|
||||||
|
;ses = localdomain1.com, localdomain2.org
|
||||||
|
# sign these domains using SRS in signing mode to prevent forged bounces
|
||||||
|
;sign = localdomain1.com, localdomain2.org
|
||||||
|
# rewrite all other domains to this domain using SRS
|
||||||
|
;fwdomain = mydomain.com
|
||||||
|
# additional domains to decode (reverse) srs
|
||||||
|
# NOTE: bms.py in milter package can also do this, as can pysrs.m4 HACK.
|
||||||
|
;srs = otherdomain.com
|
||||||
|
# do not rewrite mail to these domains
|
||||||
|
;nosrs = braindeadmail.com
|
||||||
|
# Treat these localparts as a DSN. Lot's of braindead systems
|
||||||
|
# send non-DSN mail to MAIL FROM.
|
||||||
|
;banned_users = mailer-daemon, clamav, postmaster
|
||||||
|
|
||||||
|
[srsmilter]
|
||||||
|
;datadir=/var/lib/milter
|
||||||
|
socketname = /var/run/milter/srsmilter
|
||||||
|
miltername = pysrsfilter
|
||||||
|
# reject DSNs to unsigned recipients (bounce spam)
|
||||||
|
reject_spoofed = true
|
||||||
|
;trusted_relay = 1.2.3.4
|
||||||
|
internal_connect = 192.168.*.*,127.0.0.1,::1
|
||||||
|
# Enable outgoing SRS via CHGFROM (see code for limitations)
|
||||||
|
miltersrs = false
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
From the-concourse-on-high Sat Feb 2 13:01:43 2019
|
||||||
|
Date: Sat, 02 Feb 2019 19:48:56 +0100
|
||||||
|
To: stuart@[IPv6:fcd9:7f8a:e050:4b48:7fd6:7fa:5509:6e26]
|
||||||
|
Subject: 来自qq.com的退信
|
||||||
|
|
||||||
|
Does you receive this email?
|
||||||
Binary file not shown.
-72
@@ -1,72 +0,0 @@
|
|||||||
Received: from www.bmsi.com (bmsweb.bmsi.com [219.109.11.130])
|
|
||||||
by bmsaix.bmsi.com (8.9.1/8.9.1) with ESMTP id FAA42304
|
|
||||||
for <ed@bmsi.com>; Thu, 4 May 2000 05:22:03 -0400
|
|
||||||
Received: from camco.celestial.com (root@dagney.celestial.com [192.136.111.7])
|
|
||||||
by www.bmsi.com (8.9.1/8.9.1) with ESMTP id FAA21364
|
|
||||||
for <ed@bmsi.com>; Thu, 4 May 2000 05:22:01 -0400
|
|
||||||
Received: (12482 bytes) by camco.celestial.com
|
|
||||||
via sendmail with P:stdio/D:lists/R:inet_hosts/T:smtp
|
|
||||||
(sender: <owner-flexfax@celestial.com> owner: <owner-flexfax-outbound>)
|
|
||||||
id <m12nHjG-000eNHa@camco.celestial.com>
|
|
||||||
for flexfax-outbound; Thu, 4 May 2000 02:15:30 -0700 (PDT)
|
|
||||||
(Smail-3.2.0.111 2000-Feb-17 #1 built 2000-Apr-13)
|
|
||||||
Received: from sgi.com(sgi.SGI.COM[192.48.153.1]) (12116 bytes) by camco.celestial.com
|
|
||||||
via sendmail with P:esmtp/D:aliases/T:pipe
|
|
||||||
(sender: <owner-flexfax@sgi.com> owner: <owner-flexfax>)
|
|
||||||
id <m12nHh6-000eN7C@camco.celestial.com>
|
|
||||||
for <flexfax@celestial.com>; Thu, 4 May 2000 02:13:16 -0700 (PDT)
|
|
||||||
(Smail-3.2.0.111 2000-Feb-17 #1 built 2000-Apr-13)
|
|
||||||
Received: from proxy.internet ([195.184.42.82])
|
|
||||||
by sgi.com (980327.SGI.8.8.8-aspam/980304.SGI-aspam:
|
|
||||||
SGI does not authorize the use of its proprietary
|
|
||||||
systems or networks for unsolicited or bulk email
|
|
||||||
from the Internet.)
|
|
||||||
via ESMTP id CAA02330
|
|
||||||
for <flexfax@sgi.com>; Thu, 4 May 2000 02:13:10 -0700 (PDT)
|
|
||||||
mail_from (orum@ditas.dk)
|
|
||||||
Received: from [172.16.96.14] by proxy.daab.dkproxy.internet (NTMail 4.30.0013/NU4152.00.32401f35) with ESMTP id zmlyaaaa for <flexfax@sgi.com>; Thu, 4 May 2000 11:13:09 +0200
|
|
||||||
Received: by mars with Internet Mail Service (5.5.2650.21)
|
|
||||||
id <KGM63KG3>; Thu, 4 May 2000 11:11:13 +0100
|
|
||||||
Message-ID: <9704D2AA604ED311BF6D0008C79F0A990B57BE@mars>
|
|
||||||
From: =?iso-8859-1?Q?Peter_=D8rum?= <orum@ditas.dk>
|
|
||||||
To: "'flexfax@sgi.com'" <flexfax@sgi.com>
|
|
||||||
Subject: flexfax: ILOVEYOU
|
|
||||||
Date: Thu, 4 May 2000 11:11:11 +0100
|
|
||||||
MIME-Version: 1.0
|
|
||||||
X-Mailer: Internet Mail Service (5.5.2650.21)
|
|
||||||
Content-Type: multipart/mixed;
|
|
||||||
boundary="----_=_NextPart_000_01BFB5B1.13228432"
|
|
||||||
Sender: owner-flexfax@celestial.com
|
|
||||||
Precedence: bulk
|
|
||||||
|
|
||||||
This message is in MIME format. Since your mail reader does not understand
|
|
||||||
this format, some or all of this message may not be legible.
|
|
||||||
|
|
||||||
------_=_NextPart_000_01BFB5B1.13228432
|
|
||||||
Content-Type: text/plain
|
|
||||||
|
|
||||||
|
|
||||||
kindly check the attached LOVELETTER coming from me.
|
|
||||||
|
|
||||||
|
|
||||||
------_=_NextPart_000_01BFB5B1.13228432
|
|
||||||
Content-Type: application/octet-stream;
|
|
||||||
name="LOVE-LETTER-FOR-YOU.TXT.vbs"
|
|
||||||
Content-Transfer-Encoding: quoted-printable
|
|
||||||
Content-Disposition: attachment;
|
|
||||||
filename="LOVE-LETTER-FOR-YOU.TXT.vbs"
|
|
||||||
|
|
||||||
rem barok -loveletter(vbe) <i hate go to school>
|
|
||||||
rem by: spyder / ispyder@mail.com / @GRAMMERSoft Group / =
|
|
||||||
Manila,Philippines
|
|
||||||
On Error Resume Next
|
|
||||||
set b=3Dfso.CreateTextFile(dirsystem+"\LOVE-LETTER-FOR-YOU.HTM")
|
|
||||||
b.close
|
|
||||||
set d=3Dfso.OpenTextFile(dirsystem+"\LOVE-LETTER-FOR-YOU.HTM",2)
|
|
||||||
d.write dt5
|
|
||||||
d.write join(lines,vbcrlf)
|
|
||||||
d.write vbcrlf
|
|
||||||
d.write dt6
|
|
||||||
d.close
|
|
||||||
end sub
|
|
||||||
------_=_NextPart_000_01BFB5B1.13228432--
|
|
||||||
-127
@@ -1,127 +0,0 @@
|
|||||||
Received: from www.bmsi.com (bmsweb.bmsi.com [219.109.11.130])
|
|
||||||
by bmsaix.bmsi.com (8.12.3/8.12.2) with ESMTP id g41MmROS014480
|
|
||||||
for <stuart@bmsi.com>; Wed, 1 May 2002 18:48:27 -0400
|
|
||||||
Received: from bmsred.bmsi.com (bmsred [219.109.11.50])
|
|
||||||
by www.bmsi.com (8.12.1/8.12.1) with ESMTP id g41MmFGR017812
|
|
||||||
for <stuart@bmsi.com>; Wed, 1 May 2002 18:48:15 -0400
|
|
||||||
X-Received: from www.bmsi.com (bmsweb.bmsi.com [219.109.11.130])
|
|
||||||
by bmsaix.bmsi.com (8.12.3/8.12.2) with ESMTP id g41M3hOS038584
|
|
||||||
for <ed@bmsi.com>; Wed, 1 May 2002 18:03:43 -0400
|
|
||||||
X-Received: from exp.dflinc.com (exppub [12.148.147.210])
|
|
||||||
by www.bmsi.com (8.12.1/8.12.1) with ESMTP id g41M3LGQ017812
|
|
||||||
for <ed@bmsi.com>; Wed, 1 May 2002 18:03:22 -0400
|
|
||||||
X-Received: from exp.dflinc.com (exp.dflinc.com [219.109.14.1])
|
|
||||||
by exp.dflinc.com (8.12.1/8.12.1) with ESMTP id g41M3JGT012258
|
|
||||||
for <ed@bmsi.com>; Wed, 1 May 2002 17:03:19 -0500
|
|
||||||
X-Received: from dns.intervip.psi.br (dns.intervip.psi.br [200.215.126.2])
|
|
||||||
by exp.dflinc.com (8.12.1/8.12.1) with ESMTP id g3NHlhGS032960
|
|
||||||
for <lorraine@dflinc.com>; Tue, 23 Apr 2002 12:47:44 -0500
|
|
||||||
X-Received: from Sncpyf (adsl-fnsbnu-055-k.brt.telesc.net.br [200.180.75.55])
|
|
||||||
by dns.intervip.psi.br (Postfix) with SMTP id 1FAEE24D18
|
|
||||||
for <lorraine@dflinc.com>; Tue, 23 Apr 2002 14:50:41 -0300 (BRT)
|
|
||||||
From: enardelli <enardelli@karsten.com.br>
|
|
||||||
To: lorraine@dflinc.com
|
|
||||||
Subject: A special powful tool
|
|
||||||
MIME-Version: 1.0
|
|
||||||
Content-Type: multipart/alternative;
|
|
||||||
boundary=XQ4T5Cj14m5h2vQ69IpO4mCG
|
|
||||||
Message-Id: <20020423175041.1FAEE24D18@dns.intervip.psi.br>
|
|
||||||
Date: Tue, 23 Apr 2002 14:50:41 -0300 (BRT)
|
|
||||||
X-ReSent-Date: Wed, 1 May 2002 17:03:03 -0500 (CDT)
|
|
||||||
X-ReSent-From: Gwen Bartelle <gwenb@dflinc.com>
|
|
||||||
X-ReSent-To: ed@bmsi.com
|
|
||||||
X-ReSent-Subject: A special powful tool
|
|
||||||
X-ReSent-Message-ID: <Pine.A41.4.10.10205011703030.30638@exp.dflinc.com>
|
|
||||||
ReSent-Date: Wed, 1 May 2002 18:47:52 -0400 (EDT)
|
|
||||||
ReSent-From: Ed Bond <ed@bmsi.com>
|
|
||||||
ReSent-To: Stuart Gathman <stuart@bmsi.com>
|
|
||||||
ReSent-Subject: A special powful tool
|
|
||||||
ReSent-Message-ID: <Pine.LNX.4.44.0205011847520.17454@bmsred.bmsi.com>
|
|
||||||
|
|
||||||
--XQ4T5Cj14m5h2vQ69IpO4mCG
|
|
||||||
Content-Type: text/html;
|
|
||||||
Content-Transfer-Encoding: quoted-printable
|
|
||||||
|
|
||||||
<HTML><HEAD></HEAD><BODY>
|
|
||||||
<iframe src=3Dcid:Ux7VyFy7bTS9q height=3D0 width=3D0>
|
|
||||||
</iframe>
|
|
||||||
<FONT>Hi,This is a special powful tool<br>
|
|
||||||
I wish you would enjoy it.</FONT></BODY></HTML>
|
|
||||||
|
|
||||||
--XQ4T5Cj14m5h2vQ69IpO4mCG
|
|
||||||
Content-Type: audio/x-midi;
|
|
||||||
name=hom1;tile=1;ord=3354010700499224[1].scr
|
|
||||||
Content-Transfer-Encoding: base64
|
|
||||||
Content-ID: <Ux7VyFy7bTS9q>
|
|
||||||
|
|
||||||
TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
CMDDePe/RHj3v5IT+r+Pe/e/kHr3v9Fv97/1Gfq/93H3v1Yc+r/3dve/oGj3v8sK+r+sx/e/
|
|
||||||
Nyz5v7Hu+b98HD==
|
|
||||||
--XQ4T5Cj14m5h2vQ69IpO4mCG
|
|
||||||
--XQ4T5Cj14m5h2vQ69IpO4mCG
|
|
||||||
Content-Type: application/octet-stream;
|
|
||||||
name=hom1;tile=1;ord=3354010700499224[1].htm
|
|
||||||
Content-Transfer-Encoding: base64
|
|
||||||
Content-ID: <Ux7VyFy7bTS9q>
|
|
||||||
|
|
||||||
PGh0bWw+PGhlYWQ+PHRpdGxlPkNsaWNrIGhlcmUgdG8gZmluZCBvdXQgbW9yZSE8L3RpdGxl
|
|
||||||
PjwvaGVhZD4NCjxib2R5PjxTQ1JJUFQgTEFOR1VBR0U9SmF2YVNjcmlwdD4KPCEtLQp2YXIg
|
|
||||||
U2hvY2tNb2RlID0gMDsKaWYgKG5hdmlnYXRvci5taW1lVHlwZXMgJiYgbmF2aWdhdG9yLm1p
|
|
||||||
bWVUeXBlc1siYXBwbGljYXRpb24veC1zaG9ja3dhdmUtZmxhc2giXSAmJiBuYXZpZ2F0b3Iu
|
|
||||||
bWltZVR5cGVzWyJhcHBsaWNhdGlvbi94LXNob2Nrd2F2ZS1mbGFzaCJdLmVuYWJsZWRQbHVn
|
|
||||||
aW4pIHsKaWYgKG5hdmlnYXRvci5wbHVnaW5zICYmIG5hdmlnYXRvci5wbHVnaW5zWyJTaG9j
|
|
||||||
a3dhdmUgRmxhc2giXSkKU2hvY2tNb2RlID0gMTsKfQplbHNlIGlmIChuYXZpZ2F0b3IudXNl
|
|
||||||
ckFnZW50ICYmIG5hdmlnYXRvci51c2VyQWdlbnQuaW5kZXhPZigiTVNJRSIpPj0wIAomJiAo
|
|
||||||
bmF2aWdhdG9yLnVzZXJBZ2VudC5pbmRleE9mKCJXaW5kb3dzIDkiKT49MCB8fCBuYXZpZ2F0
|
|
||||||
b3IudXNlckFnZW50LmluZGV4T2YoIldpbmRvd3MgTlQiKT49MCkpIHsKZG9jdW1lbnQud3Jp
|
|
||||||
dGUoJzxTQ1JJUFQgTEFOR1VBR0U9VkJTY3JpcHRcPiBcbicpOwpkb2N1bWVudC53cml0ZSgn
|
|
||||||
b24gZXJyb3IgcmVzdW1lIG5leHQgXG4nKTsKZG9jdW1lbnQud3JpdGUoJ1Nob2NrTW9kZSA9
|
|
||||||
IChJc09iamVjdChDcmVhdGVPYmplY3QoIlNob2Nrd2F2ZUZsYXNoLlNob2Nrd2F2ZUZsYXNo
|
|
||||||
LjMiKSkpICcpOwpkb2N1bWVudC53cml0ZSgnPFwvU0NSSVBUXD4gJyk7Cn0KaWYgKCBTaG9j
|
|
||||||
a01vZGUgKSB7CmRvY3VtZW50LndyaXRlKCc8T0JKRUNUIGNsYXNzaWQ9ImNsc2lkOkQyN0NE
|
|
||||||
QjZFLUFFNkQtMTFjZi05NkI4LTQ0NDU1MzU0MDAwMCInKTsKZG9jdW1lbnQud3JpdGUoJyBj
|
|
||||||
b2RlYmFzZT0iaHR0cDovL2FjdGl2ZS5tYWNyb21lZGlhLmNvbS9mbGFzaDIvY2Ficy9zd2Zs
|
|
||||||
YXNoLmNhYiN2ZXJzaW9uPTMsMCwwLDAiJyk7CmRvY3VtZW50LndyaXRlKCcgSUQ9YmFubmVy
|
|
||||||
IFdJRFRIPTIzMCBIRUlHSFQ9MjIwPicpOwpkb2N1bWVudC53cml0ZSgnIDxQQVJBTSBOQU1F
|
|
||||||
PW1vdmllIFZBTFVFPSJodHRwOi8vd3d3LnRlcnJhLmNvbS5ici9hZHMvcG9wXzIzMHgyMjBf
|
|
||||||
Z3Z0X3RlbGVmb25lLnN3Zj9jbGlja3RhZz1odHRwOi8vYWQuYnIuZG91YmxlY2xpY2submV0
|
|
||||||
L2NsaWNrJTNCaD12MnwyZGRkfDN8MHwlfHAlM0IzOTI1ODU3JTNCMC0wJTNCMCUzQjY2NjEw
|
|
||||||
MDIlM0IxLTQ2OHw2MCUzQjUwOTkxN3w1MDkyNDR8MSUzQiUzQiUzZmh0dHAlM2ElMmYlMmZ3
|
|
||||||
d3cuZ3Z0Lm5ldC5ici9taWRpYV9wb3B1cHRlcnJhX3Byb21vcG9ydGFsLmpzcCI+ICcpOwpk
|
|
||||||
b2N1bWVudC53cml0ZSgnIDxQQVJBTSBOQU1FPXF1YWxpdHkgVkFMVUU9YXV0b2hpZ2g+ICcp
|
|
||||||
Owpkb2N1bWVudC53cml0ZSgnPEVNQkVEIFNSQz0iaHR0cDovL3d3dy50ZXJyYS5jb20uYnIv
|
|
||||||
YWRzL3BvcF8yMzB4MjIwX2d2dF90ZWxlZm9uZS5zd2Y/Y2xpY2t0YWc9aHR0cDovL2FkLmJy
|
|
||||||
LmRvdWJsZWNsaWNrLm5ldC9jbGljayUzQmg9djJ8MmRkZHwzfDB8JXxwJTNCMzkyNTg1NyUz
|
|
||||||
QjAtMCUzQjAlM0I2NjYxMDAyJTNCMS00Njh8NjAlM0I1MDk5MTd8NTA5MjQ0fDElM0IlM0Il
|
|
||||||
M2ZodHRwJTNhJTJmJTJmd3d3Lmd2dC5uZXQuYnIvbWlkaWFfcG9wdXB0ZXJyYV9wcm9tb3Bv
|
|
||||||
cnRhbC5qc3AiJyk7CmRvY3VtZW50LndyaXRlKCcgc3dMaXZlQ29ubmVjdD1GQUxTRSBXSURU
|
|
||||||
SD0yMzAgSEVJR0hUPTIyMCcpOwpkb2N1bWVudC53cml0ZSgnIFFVQUxJVFk9YXV0b2hpZ2gn
|
|
||||||
KTsKZG9jdW1lbnQud3JpdGUoJyBUWVBFPSJhcHBsaWNhdGlvbi94LXNob2Nrd2F2ZS1mbGFz
|
|
||||||
aCIgUExVR0lOU1BBR0U9Imh0dHA6Ly93d3cubWFjcm9tZWRpYS5jb20vc2hvY2t3YXZlL2Rv
|
|
||||||
d25sb2FkL2luZGV4LmNnaT9QMV9Qcm9kX1ZlcnNpb249U2hvY2t3YXZlRmxhc2giPicpOwpk
|
|
||||||
b2N1bWVudC53cml0ZSgnPC9FTUJFRD4nKTsKZG9jdW1lbnQud3JpdGUoJzwvT0JKRUNUPicp
|
|
||||||
Owp9IGVsc2UgaWYgKCEobmF2aWdhdG9yLmFwcE5hbWUgJiYgbmF2aWdhdG9yLmFwcE5hbWUu
|
|
||||||
aW5kZXhPZigiTmV0c2NhcGUiKT49MCAmJiBuYXZpZ2F0b3IuYXBwVmVyc2lvbi5pbmRleE9m
|
|
||||||
KCIyLiIpPj0wKSl7CmRvY3VtZW50LndyaXRlKCc8QSBIUkVGPSJodHRwOi8vYWQuYnIuZG91
|
|
||||||
YmxlY2xpY2submV0L2NsaWNrJTNCaD12MnwyZGRkfDN8MHwlfHAlM0IzOTI1ODU3JTNCMC0w
|
|
||||||
JTNCMCUzQjY2NjEwMDIlM0IxLTQ2OHw2MCUzQjUwOTkxN3w1MDkyNDR8MSUzQiUzQiUzZmh0
|
|
||||||
dHAlM2ElMmYlMmZ3d3cuZ3Z0Lm5ldC5ici9taWRpYV9wb3B1cHRlcnJhX3Byb21vcG9ydGFs
|
|
||||||
LmpzcCIgVEFSR0VUPSJfYmxhbmsiPjxJTUcgU1JDPSJodHRwOi8vd3d3LnRlcnJhLmNvbS5i
|
|
||||||
ci9hZHMvcG9wXzIzMHgyMjBfZ3Z0X3RlbGVmb25lLmdpZiIgV0lEVEg9MjMwIEhFSUdIVD0y
|
|
||||||
MjAgQk9SREVSPTA+PC9BPicpOwp9Ci8vLS0+CjwvU0NSSVBUPgo8Tk9FTUJFRD48QSBIUkVG
|
|
||||||
PT0iaHR0cDovL2FkLmJyLmRvdWJsZWNsaWNrLm5ldC9jbGljayUzQmg9djJ8MmRkZHwzfDB8
|
|
||||||
JXxwJTNCMzkyNTg1NyUzQjAtMCUzQjAlM0I2NjYxMDAyJTNCMS00Njh8NjAlM0I1MDk5MTd8
|
|
||||||
NTA5MjQ0fDElM0IlM0IlM2ZodHRwJTNhJTJmJTJmd3d3Lmd2dC5uZXQuYnIvbWlkaWFfcG9w
|
|
||||||
dXB0ZXJyYV9wcm9tb3BvcnRhbC5qc3AiIFRBUkdFVD0iX2JsYW5rIj48SU1HIFNSQz0iaHR0
|
|
||||||
cDovL3d3dy50ZXJyYS5jb20uYnIvYWRzL3BvcF8yMzB4MjIwX2d2dF90ZWxlZm9uZS5naWYi
|
|
||||||
IFdJRFRIPTIzMCBIRUlHSFQ9MjIwIEJPUkRFUj0wPjwvQT48L05PRU1CRUQ+CjxOT1NDUklQ
|
|
||||||
VD48QSBIUkVGPT0iaHR0cDovL2FkLmJyLmRvdWJsZWNsaWNrLm5ldC9jbGljayUzQmg9djJ8
|
|
||||||
MmRkZHwzfDB8JXxwJTNCMzkyNTg1NyUzQjAtMCUzQjAlM0I2NjYxMDAyJTNCMS00Njh8NjAl
|
|
||||||
M0I1MDk5MTd8NTA5MjQ0fDElM0IlM0IlM2ZodHRwJTNhJTJmJTJmd3d3Lmd2dC5uZXQuYnIv
|
|
||||||
bWlkaWFfcG9wdXB0ZXJyYV9wcm9tb3BvcnRhbC5qc3AiIFRBUkdFVD0iX2JsYW5rIj48SU1H
|
|
||||||
IFNSQz0iaHR0cDovL3d3dy50ZXJyYS5jb20uYnIvYWRzL3BvcF8yMzB4MjIwX2d2dF90ZWxl
|
|
||||||
Zm9uZS5naWYiIFdJRFRIPTIzMCBIRUlHSFQ9MjIwIEJPUkRFUj0wPjwvQT48L05PU0NSSVBU
|
|
||||||
PjwvYm9keT4NCjwvaHRtbD
|
|
||||||
--XQ4T5Cj14m5h2vQ69IpO4mCG--
|
|
||||||
|
|
||||||
|
|
||||||
-90
@@ -1,90 +0,0 @@
|
|||||||
Received: from www.bmsi.com (bmsweb.bmsi.com [219.109.11.130])
|
|
||||||
by bmsaix.bmsi.com (8.9.1/8.9.1) with ESMTP id QAA24094
|
|
||||||
for <ed@bmsi.com>; Fri, 12 Jan 2001 16:30:00 -0500
|
|
||||||
Received: from jscaix.jsconnor.com (jscaix [209.193.177.106])
|
|
||||||
by www.bmsi.com (8.9.1/8.9.1) with ESMTP id QAA30044
|
|
||||||
for <ed@bmsi.com>; Fri, 12 Jan 2001 16:29:54 -0500
|
|
||||||
Received: from connor.jsconnor.com (connor.jsconnor.com [192.168.100.15])
|
|
||||||
by jscaix.jsconnor.com (8.9.1/8.9.1) with ESMTP id QAA12022
|
|
||||||
for <ed@bmsi.com>; Fri, 12 Jan 2001 16:31:51 -0500
|
|
||||||
X-Received: from goodspeed2.apical.com (ns1.apical.com [209.150.15.130])
|
|
||||||
by jscaix.jsconnor.com (8.9.1/8.9.1) with ESMTP id HAA36550
|
|
||||||
for <carrollf@jsconnor.com>; Fri, 12 Jan 2001 07:19:10 -0500
|
|
||||||
X-Received: from SalCanino (cz-cblk-150-16-32.cyberzone.net [209.150.16.32])
|
|
||||||
by goodspeed2.apical.com (8.9.3/8.9.3) with SMTP id HAA14946
|
|
||||||
for <carrollf@jsconnor.com>; Fri, 12 Jan 2001 07:16:37 -0500
|
|
||||||
Reply-To: <sal.canino@innovativeconcepts.com>
|
|
||||||
From: "Sal Canino" <sal.canino@innovativeconcepts.com>
|
|
||||||
To: "Carroll Forehand" <carrollf@jsconnor.com>
|
|
||||||
Subject: AUTEAE
|
|
||||||
Date: Fri, 12 Jan 2001 04:16:36 -0800
|
|
||||||
Message-ID: <NEBBKLEPKLBIEKBANDGCIEMOCGAA.sal.canino@innovativeconcepts.com>
|
|
||||||
MIME-Version: 1.0
|
|
||||||
Content-Type: multipart/mixed;
|
|
||||||
boundary="----=_NextPart_000_0003_01C07C4E.74368FC0"
|
|
||||||
X-Priority: 3 (Normal)
|
|
||||||
X-MSMail-Priority: Normal
|
|
||||||
X-Mailer: Microsoft Outlook IMO, Build 9.0.2416 (9.0.2910.0)
|
|
||||||
Importance: Normal
|
|
||||||
X-MimeOLE: Produced By Microsoft MimeOLE V5.50.4133.2400
|
|
||||||
Disposition-Notification-To: "Sal Canino" <sal.canino@innovativeconcepts.com>
|
|
||||||
ReSent-Date: Fri, 12 Jan 2001 16:29:03 -0500 (EST)
|
|
||||||
ReSent-From: Carroll Forehand <carrollf@jsconnor.com>
|
|
||||||
ReSent-To: ed@bmsi.com
|
|
||||||
ReSent-Subject: AUTEAE
|
|
||||||
ReSent-Message-ID: <Pine.A41.4.10.10101121629001.171826@connor.jsconnor.com>
|
|
||||||
|
|
||||||
This is a multi-part message in MIME format.
|
|
||||||
|
|
||||||
------=_NextPart_000_0003_01C07C4E.74368FC0
|
|
||||||
Content-Type: text/plain;
|
|
||||||
charset="iso-8859-1"
|
|
||||||
Content-Transfer-Encoding: 7bit
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
------=_NextPart_000_0003_01C07C4E.74368FC0
|
|
||||||
Content-Type: application/octet-stream;
|
|
||||||
name="PEDI.JPG.vbs"
|
|
||||||
Content-Transfer-Encoding: quoted-printable
|
|
||||||
Content-Disposition: attachment;
|
|
||||||
filename="PEDI.JPG.vbs"
|
|
||||||
|
|
||||||
rem =
|
|
||||||
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
|
|
||||||
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
|
|
||||||
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
|
|
||||||
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=0A=
|
|
||||||
rem "Plan Colombia" virus v1.0=0A=
|
|
||||||
rem by Sand Ja9e Gr0w (www.colombia.com)=0A=
|
|
||||||
=0A=
|
|
||||||
rem Dedicated to all the people that want to be hackers or crackers, in =
|
|
||||||
Colombia =0A=
|
|
||||||
rem This program is also a protest act against the violence and =
|
|
||||||
corruption that Colombia lives...=0A=
|
|
||||||
rem I always wanting that all this finishes, I have said...=0A=
|
|
||||||
=0A=
|
|
||||||
=0A=
|
|
||||||
rem Santa fe de Bogot=E1 2000/09=0A=
|
|
||||||
rem I dedicate to all you the song "GoodBye" of Andreas Bochelli=0A=
|
|
||||||
rem =
|
|
||||||
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
|
|
||||||
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
|
|
||||||
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
|
|
||||||
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=0A=
|
|
||||||
=0A=
|
|
||||||
=0A=
|
|
||||||
rem Thanks God..!=0A=
|
|
||||||
rem A greeting for "Lina Mar=EDa" from "Santa fe de Bogot=E1"=0A=
|
|
||||||
rem A greeting for "Tizo" from "Spain"=0A=
|
|
||||||
rem And One kicked of tail to my friends, "eL ChE" and "ThE SpY"=0A=
|
|
||||||
=0A=
|
|
||||||
rem okay, ok... =0A=
|
|
||||||
rem my baby start here...=0A=
|
|
||||||
=0A=
|
|
||||||
=0A=
|
|
||||||
On Error Resume Next=0A=
|
|
||||||
|
|
||||||
------=_NextPart_000_0003_01C07C4E.74368FC0--
|
|
||||||
|
|
||||||
|
|
||||||
-50
@@ -1,50 +0,0 @@
|
|||||||
Received: from www.bmsi.com (bmsweb.bmsi.com [219.109.11.130])
|
|
||||||
by bmsaix.bmsi.com (8.11.5/8.11.3) with ESMTP id f8EMUxS24174
|
|
||||||
for <stuart@bmsi.com>; Fri, 14 Sep 2001 18:30:59 -0400
|
|
||||||
Received: from bmsred.bmsi.com (bmsred [219.109.11.50])
|
|
||||||
by www.bmsi.com (8.9.1/8.9.1) with ESMTP id SAA12740
|
|
||||||
for <stuart@bmsi.com>; Fri, 14 Sep 2001 18:30:58 -0400
|
|
||||||
X-Received: from www.bmsi.com (bmsweb.bmsi.com [219.109.11.130])
|
|
||||||
by bmsaix.bmsi.com (8.11.5/8.11.3) with ESMTP id f8EESNW28934
|
|
||||||
for <ed@bmsi.com>; Fri, 14 Sep 2001 10:28:23 -0400
|
|
||||||
X-Received: from bwi.bwicorp.com (bwi.bwicorp.com [209.116.254.106])
|
|
||||||
by www.bmsi.com (8.9.1/8.9.1) with ESMTP id KAA34262
|
|
||||||
for <ed@bmsi.com>; Fri, 14 Sep 2001 10:28:20 -0400
|
|
||||||
X-Received: from bwicorp.com (bwi3 [192.168.3.22])
|
|
||||||
by bwi.bwicorp.com (8.9.1/8.9.1) with ESMTP id KAA42970
|
|
||||||
for <ed@bmsi.com>; Fri, 14 Sep 2001 10:33:54 -0400
|
|
||||||
Date: Fri, 14 Sep 2001 10:33:54 -0400
|
|
||||||
From: Mary Smith <mary@bwicorp.com>
|
|
||||||
Message-Id: <200109141433.KAA42970@bwi.bwicorp.com>
|
|
||||||
MIME-Version: 1.0
|
|
||||||
Content-Type: multipart/mixed; boundary="==i3.9.0oisdboibsd((kncd"
|
|
||||||
ReSent-Date: Fri, 14 Sep 2001 18:30:47 -0400 (EDT)
|
|
||||||
ReSent-From: Ed Bond <ed@bmsi.com>
|
|
||||||
ReSent-To: Stuart Gathman <stuart@bmsi.com>
|
|
||||||
ReSent-Subject: Resent mail....
|
|
||||||
ReSent-Message-ID: <Pine.LNX.4.33.0109141830470.13214@bmsred.bmsi.com>
|
|
||||||
|
|
||||||
--==i3.9.0oisdboibsd((kncd
|
|
||||||
Content-Type: application/octet-stream; name="READER_DIGEST_LETTER.TXT.pif"
|
|
||||||
Content-Transfer-Encoding: base64
|
|
||||||
Content-Disposition: attachment; filename="READER_DIGEST_LETTER.TXT.pif"
|
|
||||||
|
|
||||||
TVpQAAIAAAAEAA8A//8AALgAAAAAAAAAQAAaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAEAALoQAA4ftAnNIbgBTM0hkJBUaGlzIHByb2dyYW0gbXVzdCBiZSBydW4gdW5kZXIgV2lu
|
|
||||||
MzINCiQ3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBFAABMAQQA5ijojgAAAAAAAAAA4ACOgQsBAhkA
|
|
||||||
FAAAAAYAAAAAAAAAEAAAABAAAAAwAAAAAEAAABAAAAACAAABAAAAAAAAAAMACgAAAAAAAMAAAAAE
|
|
||||||
AAAAAAAAAgAAAAAAEAAAIAAAAAAQAAAQAAAAAAAAEAAAAAAAAAAAAAAAAEAAAIoAAAAAUAAAAAYA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ09ERQAAAAAA
|
|
||||||
IAAAABAAAAAUAAAABgAAAAAAAAAAAAAAAAAAIAAA4ERBVEEAAAAAABAAAAAwAAAAAgAAABoAAAAA
|
|
||||||
AAAAAAAAAAAAAEAAAMAuaWRhdGEAAAAQAAAAQAAAAAIAAAAcAAAAAAAAAAAAAAAAAABAAADALnJz
|
|
||||||
cmMAAAAAgAAAAFAAAAAwAAAAHgAAAAAAAAAAAAAAAAAAQAAA0AAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
RDY5alLDAJCK/jLsU0G8R03PAwt5DjEcFVK3ICRNw5dh2gxwqg7aZ3VtO1ynbZr2zAD/////////
|
|
||||||
/////6IDEwBbAAggAAAA
|
|
||||||
|
|
||||||
|
|
||||||
--==i3.9.0oisdboibsd((kncd--
|
|
||||||
|
|
||||||
|
|
||||||
-60
@@ -1,60 +0,0 @@
|
|||||||
From mdb@go2net.com Tue Sep 18 10:31:34 2001
|
|
||||||
Received: from www.bmsi.com (bmsweb.bmsi.com [219.109.11.130])
|
|
||||||
by bmsaix.bmsi.com (8.11.5/8.11.3) with ESMTP id f8IEVXM42662
|
|
||||||
for <stuart@bmsi.com>; Tue, 18 Sep 2001 10:31:34 -0400
|
|
||||||
Received: from STOREULV2 (mail.indexas.no [195.70.182.114])
|
|
||||||
by www.bmsi.com (8.9.1/8.9.1) with SMTP id KAA27604
|
|
||||||
for <stuart@bmsi.com>; Tue, 18 Sep 2001 10:31:31 -0400
|
|
||||||
Date: Tue, 18 Sep 2001 10:31:31 -0400
|
|
||||||
From: mdb@go2net.com
|
|
||||||
Message-Id: <200109181431.KAA27604@www.bmsi.com>
|
|
||||||
Subject: udesktopdesktopeksempeleksempeldesktopeksempeldesktopeksempeldesktopdesktopdesktopeksempeleksempeleksempeleksempeldesktopeksempeleksempeleksempeleksempeleksempeleksempeldesktopeksempeleksempeleksempeldesktopeksempeleksempeldesktopdesktopdesktopeksempeldeskmail.bmsi.com.desktop
|
|
||||||
MIME-Version: 1.0
|
|
||||||
Content-Type: multipart/related;
|
|
||||||
type="multipart/alternative";
|
|
||||||
boundary="====_ABC1234567890DEF_===="
|
|
||||||
X-Priority: 3
|
|
||||||
X-MSMail-Priority: Normal
|
|
||||||
X-Unsent: 1
|
|
||||||
Status: RO
|
|
||||||
X-Status:
|
|
||||||
X-Keywords:
|
|
||||||
|
|
||||||
--====_ABC1234567890DEF_====
|
|
||||||
Content-Type: multipart/alternative;
|
|
||||||
boundary="====_ABC0987654321DEF_===="
|
|
||||||
|
|
||||||
--====_ABC0987654321DEF_====
|
|
||||||
Content-Type: text/html;
|
|
||||||
charset="iso-8859-1"
|
|
||||||
Content-Transfer-Encoding: quoted-printable
|
|
||||||
|
|
||||||
|
|
||||||
<HTML><HEAD></HEAD><BODY bgColor=3D#ffffff>
|
|
||||||
<iframe src=3Dcid:EA4DMGBP9p height=3D0 width=3D0>
|
|
||||||
</iframe></BODY></HTML>
|
|
||||||
--====_ABC0987654321DEF_====--
|
|
||||||
|
|
||||||
--====_ABC1234567890DEF_====
|
|
||||||
Content-Type: audio/x-wav;
|
|
||||||
name="readme.exe"
|
|
||||||
Content-Transfer-Encoding: base64
|
|
||||||
Content-ID: <EA4DMGBP9p>
|
|
||||||
|
|
||||||
TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAA2AAAAA4fug4AtAnNIbgBTM0hVGhpcyBwcm9ncmFtIGNhbm5vdCBiZSBydW4gaW4gRE9TIG1v
|
|
||||||
ZGUuDQ0KJAAAAAAAAAA11CFvcbVPPHG1TzxxtU88E6pcPHW1TzyZqkU8dbVPPJmqSzxytU88cbVO
|
|
||||||
PBG1TzyZqkQ8fbVPPMmzSTxwtU88UmljaHG1TzwAAAAAAAAAAMBEAWMAAAB/UEUAAEwBBQB1Oqc7
|
|
||||||
AAAAAAAAAADgAA4BCwEGAABwAAAAYAAAAAAAALN0AAAAEAAAAIAAAAAAFzYAEAAAABAAAAQAAAAA
|
|
||||||
AAAABAAAAAAAAAAAEAEAABAAAAAAAAACAAAAAAAQAAAQAAAAABAAABAAAAAAAAAQAAAAAAAAAAAA
|
|
||||||
AACEgQAAUAAAAADgAACIHgAAAAAAAAAAAAAAAAAAAAAAAAAAAQA4CgAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAIQBAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAudGV4dAAAAFZlAAAAEAAAAHAAAAAQAAAAAAAAAAAAAAAAAAAgAABgLnJkYXRhAAAq
|
|
||||||
CQAAAIAAAAAQAAAAgAAAAAAAAAAAAAAAAAAAQAAAQC5kYXRhAAAAKEcAAACQAAAAIAAAAJAAAAAA
|
|
||||||
AAAAAAAAAAAAAEAAAMAucnNyYwAAAAAgAAAA4AAAACAAAACwAAAAAAAAAAAAAAAAAABAAABALnJl
|
|
||||||
bG9jAABGCwAAAAABAAAQAAAA0AAAAAAAAAAAAAAAAAAAQAAAQgAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAA=
|
|
||||||
|
|
||||||
--====_ABC1234567890DEF_====
|
|
||||||
|
|
||||||
|
|
||||||
-38
@@ -1,38 +0,0 @@
|
|||||||
From mdb@go2net.com Tue Sep 18 10:31:34 2001
|
|
||||||
Received: from localhost (varna148.pip.digsys.bg [193.68.1.148])
|
|
||||||
by danbo.digsys.bg (8.10.1/8.10.1) with SMTP id fAM7FHk06734
|
|
||||||
for butchc@trwonnor.com; Thu, 22 Nov 2001 09:15:18 +0200 (EET)
|
|
||||||
From: POP - interlogvar <interlogvar@mbox.digsys.bg>
|
|
||||||
Message-Id: <200111220715.fAM7FHk06734@danbo.digsys.bg>
|
|
||||||
To: butchc@trwonnor.com
|
|
||||||
Subject: Funny shit to see ?!
|
|
||||||
Date: Thu,22 Nov 2001 09:16:34 -0000
|
|
||||||
MIME-Version: 1.0
|
|
||||||
Content-Type: multipart/mixed;
|
|
||||||
boundary="bound"
|
|
||||||
X-Priority: 3
|
|
||||||
X-MSMail-Priority: Normal
|
|
||||||
X-Mailer: Microsoft Outlook Express 5.50.4522.1300
|
|
||||||
X-MimeOLE: Produced By Microsoft MimeOLE V5.50.4522.1300
|
|
||||||
|
|
||||||
This is a multi-part message in MIME format.
|
|
||||||
|
|
||||||
--bound
|
|
||||||
Content-Type: text/html;
|
|
||||||
charset="iso-8859-1"
|
|
||||||
Content-Transfer-Encoding: quoted-printable
|
|
||||||
|
|
||||||
<HTML><HEAD></HEAD><BODY><iframe src=3Dcid:SOMECID height=3D0 width=3D0></iframe>
|
|
||||||
<font>peace</font></BODY></HTML>
|
|
||||||
|
|
||||||
--bound
|
|
||||||
Content-Type: audio/x-wav;
|
|
||||||
name="whatever.exe"
|
|
||||||
Content-Transfer-Encoding: base64
|
|
||||||
Content-ID: <SOMECID>
|
|
||||||
|
|
||||||
TVoAAAIAAAACAB4AHgAAAAACAAAAAAAAAAAAAMWnLuEOH7oOALQJ
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAA=
|
|
||||||
|
|
||||||
--bound--
|
|
||||||
-27
@@ -1,27 +0,0 @@
|
|||||||
From mdb@go2net.com Tue Sep 18 10:31:34 2001
|
|
||||||
Received: from aglnss01.grupoagrisal.net ([172.16.0.1])
|
|
||||||
by agntss05 (Lotus Domino Release 5.07a)
|
|
||||||
with ESMTP id 2001120416164050:5294 ;
|
|
||||||
Tue, 4 Dec 2001 16:16:40 -0600
|
|
||||||
Subject: MAEU XSS025786 - ORDER 1251 - CONTAINER MAEU 6053725
|
|
||||||
To: kathyp@jsconnor.com
|
|
||||||
Cc: Blanca@ace-of-hearts.net
|
|
||||||
X-Mailer: Lotus Notes Release 5.07a May 14, 2001
|
|
||||||
Message-ID: <OF28551015.C47BCC85-ON06256B18.0079DD92@grupoagrisal.net>
|
|
||||||
From: sherrera.dco.lc@agrisal.com
|
|
||||||
Date: Tue, 4 Dec 2001 16:11:48 -0600
|
|
||||||
MIME-Version: 1.0
|
|
||||||
X-MIMETrack: Serialize by Router on AGLNSS01/AGRISAL(Release 5.07a |May 14, 2001) at 04/12/2001
|
|
||||||
04:11:57 p.m.,
|
|
||||||
Itemize by SMTP Server on aglnss03/Grupo_Agrisal(Release 5.07a |May 14, 2001) at
|
|
||||||
12/04/2001 04:16:41 PM,
|
|
||||||
Serialize by Router on aglnss03/Grupo_Agrisal(Release 5.07a |May 14, 2001) at
|
|
||||||
12/04/2001 04:16:51 PM
|
|
||||||
Content-type: application/octet-stream;
|
|
||||||
name="FAX20.exe"
|
|
||||||
Content-Disposition: attachment; filename="FAX20.exe"
|
|
||||||
Content-Transfer-Encoding: base64
|
|
||||||
|
|
||||||
TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAKJsVAAAACIAACIAACIBr6AQA
|
|
||||||
|
|
||||||
-62
@@ -1,62 +0,0 @@
|
|||||||
From pandora.owner@pandora.cz Wed Mar 24 21:02:22 2004
|
|
||||||
Received: from pandora.cz (localhost [127.0.0.1])
|
|
||||||
by pandora3.mobil.cz (8.12.8/8.12.8) with ESMTP id i2O88iWu021270
|
|
||||||
for <stuart@bmsi.com>; Wed, 24 Mar 2004 09:08:44 +0100
|
|
||||||
Message-Id: <200403240808.i2O88iWu021270@pandora3.mobil.cz>
|
|
||||||
X-Sender: Pandora
|
|
||||||
MIME-Version: 1.0
|
|
||||||
Date: Wed, 24 Mar 2004 09:08:44 +0100
|
|
||||||
From: "administrator@pandora.cz" <administrator@pandora.cz>
|
|
||||||
To: "stuart@bmsi.com" <stuart@bmsi.com>
|
|
||||||
Subject: Konferenceneexistuje
|
|
||||||
Content-Type: multipart/mixed; boundary="Pandora3Bndry_1080115724426044878"
|
|
||||||
|
|
||||||
|
|
||||||
--Pandora3Bndry_1080115724426044878
|
|
||||||
Content-Type: multipart/alternative; boundary="Pandora3Bndry_1080115724783315537"
|
|
||||||
|
|
||||||
|
|
||||||
--Pandora3Bndry_1080115724783315537
|
|
||||||
Content-Type: text/plain; charset="ISO-8859-2"
|
|
||||||
|
|
||||||
Konference '2003-07-46063' neexistuje.
|
|
||||||
|
|
||||||
--Pandora3Bndry_1080115724783315537
|
|
||||||
Content-Type: text/html; charset="ISO-8859-2"
|
|
||||||
|
|
||||||
Konference '2003-07-46063' neexistuje.
|
|
||||||
|
|
||||||
--Pandora3Bndry_1080115724783315537--
|
|
||||||
|
|
||||||
--Pandora3Bndry_1080115724426044878
|
|
||||||
Content-Type: message/rfc822; boundary="----=_NextPart_000_0010_00000FFF.00007545"
|
|
||||||
|
|
||||||
MIME-Version: 1.0
|
|
||||||
Date: Wed, 24 Mar 2004 09:03:28 +0100
|
|
||||||
From: "" <stuart@bmsi.com>
|
|
||||||
To: "" <2003-07-46063@pandora.cz>
|
|
||||||
Subject: =?ISO-8859-2?q?Re=3A_Your_software?=
|
|
||||||
Content-Type: multipart/mixed; boundary="Pandora3Bndry_10801157231587976770"
|
|
||||||
|
|
||||||
|
|
||||||
--Pandora3Bndry_10801157231587976770
|
|
||||||
Content-Type: text/plain; charset="Windows-1252"
|
|
||||||
Content-Transfer-Encoding: 7bit
|
|
||||||
|
|
||||||
See the attached file for details.
|
|
||||||
|
|
||||||
|
|
||||||
--Pandora3Bndry_10801157231587976770
|
|
||||||
Content-Type: application/octet-stream; name="application.pif"
|
|
||||||
Content-Disposition: attachment; filename="application.pif"
|
|
||||||
Content-Transfer-Encoding: base64
|
|
||||||
|
|
||||||
TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAuAAAAKvnXsbvhjCV74Ywle+GMJVsmj6V44YwlQeZOpX2hjCV74YxlbiGMJVsjm2V
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
|
|
||||||
--Pandora3Bndry_10801157231587976770--
|
|
||||||
|
|
||||||
--Pandora3Bndry_1080115724426044878--
|
|
||||||
|
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
import unittest
|
||||||
|
from Milter.config import MilterConfigParser
|
||||||
|
|
||||||
|
class ConfigTestCase(unittest.TestCase):
|
||||||
|
def testConfig(self):
|
||||||
|
cp = MilterConfigParser()
|
||||||
|
cp.read(['test/pysrs.cfg'])
|
||||||
|
socketname = cp.getdefault('srsmilter','socketname',
|
||||||
|
'/var/run/milter/srsmilter')
|
||||||
|
self.assertEqual(socketname,'/var/run/milter/srsmilter')
|
||||||
|
miltersrs = cp.getboolean('srsmilter','miltersrs')
|
||||||
|
self.assertFalse(miltersrs)
|
||||||
|
|
||||||
|
def suite(): return unittest.TestLoader().loadTestsFromTestCase(ConfigTestCase)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
+1
-1
@@ -49,7 +49,7 @@ class GreylistTestCase(unittest.TestCase):
|
|||||||
grey.close()
|
grey.close()
|
||||||
|
|
||||||
def suite():
|
def suite():
|
||||||
s = unittest.makeSuite(GreylistTestCase,'test')
|
s = unittest.TestLoader().loadTestsFromTestCase(GreylistTestCase)
|
||||||
return s
|
return s
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
+45
-30
@@ -1,34 +1,10 @@
|
|||||||
# $Log$
|
# @author Stuart D. Gathman <stuart@bmsi.com>
|
||||||
# Revision 1.5 2011/06/09 17:27:42 customdesigned
|
# Copyright 2005,2009,2020 Business Management Systems, Inc.
|
||||||
# Documentation updates.
|
# This code is under the GNU General Public License. See COPYING for details.
|
||||||
#
|
|
||||||
# Revision 1.4 2005/07/20 14:49:44 customdesigned
|
|
||||||
# Handle corrupt and empty ZIP files.
|
|
||||||
#
|
|
||||||
# Revision 1.3 2005/06/17 01:49:39 customdesigned
|
|
||||||
# Handle zip within zip.
|
|
||||||
#
|
|
||||||
# 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
|
|
||||||
# Handle garbage after quote in boundary.
|
|
||||||
#
|
|
||||||
# Revision 1.22 2005/02/10 01:10:59 stuart
|
|
||||||
# Fixed MimeMessage.ismodified()
|
|
||||||
#
|
|
||||||
# Revision 1.21 2005/02/10 00:56:49 stuart
|
|
||||||
# Runs with python2.4. Defang not working correctly - more work needed.
|
|
||||||
#
|
|
||||||
# Revision 1.20 2004/11/20 16:38:17 stuart
|
|
||||||
# Add rcs log
|
|
||||||
#
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
import unittest
|
import unittest
|
||||||
import mime
|
import mime
|
||||||
|
import zipfile
|
||||||
import socket
|
import socket
|
||||||
try:
|
try:
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
@@ -51,6 +27,14 @@ hostname = socket.gethostname()
|
|||||||
|
|
||||||
class MimeTestCase(unittest.TestCase):
|
class MimeTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.zf = zipfile.ZipFile('test/virus.zip','r')
|
||||||
|
self.zf.setpassword(b'denatured')
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.zf.close()
|
||||||
|
self.zf = None
|
||||||
|
|
||||||
# test mime parameter parsing
|
# test mime parameter parsing
|
||||||
def testParam(self):
|
def testParam(self):
|
||||||
plist = mime._parseparam('; boundary="----=_NextPart_000_4e56_490d_48e3"')
|
plist = mime._parseparam('; boundary="----=_NextPart_000_4e56_490d_48e3"')
|
||||||
@@ -90,6 +74,10 @@ 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'):
|
||||||
|
try:
|
||||||
|
with self.zf.open(vname,"r") as fp:
|
||||||
|
msg = mime.message_from_file(fp)
|
||||||
|
except KeyError:
|
||||||
with open('test/'+vname,"rb") as fp:
|
with open('test/'+vname,"rb") as fp:
|
||||||
msg = mime.message_from_file(fp)
|
msg = mime.message_from_file(fp)
|
||||||
mime.defang(msg,scan_zip=True)
|
mime.defang(msg,scan_zip=True)
|
||||||
@@ -118,7 +106,7 @@ class MimeTestCase(unittest.TestCase):
|
|||||||
|
|
||||||
# virus6 has no parts - the virus is directly inline
|
# virus6 has no parts - the virus is directly inline
|
||||||
def testDefang6(self,vname="virus6",fname='FAX20.exe'):
|
def testDefang6(self,vname="virus6",fname='FAX20.exe'):
|
||||||
with open('test/'+vname,"rb") as fp:
|
with self.zf.open(vname,"r") as fp:
|
||||||
msg = mime.message_from_file(fp)
|
msg = mime.message_from_file(fp)
|
||||||
mime.defang(msg)
|
mime.defang(msg)
|
||||||
oname = vname + '.out'
|
oname = vname + '.out'
|
||||||
@@ -204,6 +192,33 @@ class MimeTestCase(unittest.TestCase):
|
|||||||
self.assertEqual(self.filename,"7501'S FOR TWO GOLDEN SOURCES SHIPMENTS FOR TAX & DUTY PURPOSES ONLY.PDF")
|
self.assertEqual(self.filename,"7501'S FOR TWO GOLDEN SOURCES SHIPMENTS FOR TAX & DUTY PURPOSES ONLY.PDF")
|
||||||
self.assertEqual(rc,Milter.CONTINUE)
|
self.assertEqual(rc,Milter.CONTINUE)
|
||||||
|
|
||||||
|
def test_getnames(self):
|
||||||
|
names = []
|
||||||
|
self.sawpif = False
|
||||||
|
def do_part(m):
|
||||||
|
n = m.getnames()
|
||||||
|
a = names
|
||||||
|
a += n
|
||||||
|
return Milter.CONTINUE
|
||||||
|
def chk_part(m):
|
||||||
|
for k,n in m.getnames():
|
||||||
|
if n and n.lower().endswith('.pif'):
|
||||||
|
self.sawpif = True
|
||||||
|
s = m.get_submsg()
|
||||||
|
print(m.get_content_type(),type(s),'modified:',m.ismodified())
|
||||||
|
if isinstance(s,email.message.Message):
|
||||||
|
return mime.check_attachments(s,chk_part)
|
||||||
|
return Milter.CONTINUE
|
||||||
|
|
||||||
|
with self.zf.open('virus7','r') as fp:
|
||||||
|
msg = mime.message_from_file(fp)
|
||||||
|
self.assertTrue(msg.ismultipart())
|
||||||
|
mime.check_attachments(msg,do_part)
|
||||||
|
self.assertTrue(('filename','application.pif') in names)
|
||||||
|
self.assertFalse(self.sawpif)
|
||||||
|
mime.check_attachments(msg,chk_part)
|
||||||
|
self.assertTrue(self.sawpif)
|
||||||
|
|
||||||
def testHTML(self,fname=""):
|
def testHTML(self,fname=""):
|
||||||
result = StringIO()
|
result = StringIO()
|
||||||
filter = mime.HTMLScriptFilter(result)
|
filter = mime.HTMLScriptFilter(result)
|
||||||
@@ -219,7 +234,7 @@ class MimeTestCase(unittest.TestCase):
|
|||||||
#print(msg + filter.msg)
|
#print(msg + filter.msg)
|
||||||
self.assertTrue(result.getvalue() == msg + filter.msg)
|
self.assertTrue(result.getvalue() == msg + filter.msg)
|
||||||
|
|
||||||
def suite(): return unittest.makeSuite(MimeTestCase,'test')
|
def suite(): return unittest.TestLoader().loadTestsFromTestCase(MimeTestCase)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
if len(sys.argv) < 2:
|
if len(sys.argv) < 2:
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
from __future__ import print_function
|
||||||
|
import unittest
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from Milter.policy import MTAPolicy
|
||||||
|
|
||||||
|
class Config(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.access_file='test/access.db'
|
||||||
|
self.access_file_nulls=True
|
||||||
|
self.access_file_colon = False
|
||||||
|
|
||||||
|
class PolicyTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.config = Config()
|
||||||
|
if os.access('test/access',os.R_OK):
|
||||||
|
if not os.path.exists('test/access.db') or \
|
||||||
|
os.path.getmtime('test/access') > os.path.getmtime('test/access.db'):
|
||||||
|
cmd = 'tr : ! <test/access | makemap hash test/access.db'
|
||||||
|
if os.system(cmd):
|
||||||
|
print('failed!')
|
||||||
|
else:
|
||||||
|
print("Missing test/access")
|
||||||
|
|
||||||
|
def testPolicy(self):
|
||||||
|
self.config.access_file_colon = False
|
||||||
|
self.config.access_file_nulls = False # FIXME: test old and new bsddb
|
||||||
|
with MTAPolicy('good@example.com',conf=self.config) as p:
|
||||||
|
pol = p.getPolicy('smtp-auth')
|
||||||
|
self.assertEqual(pol,'OK')
|
||||||
|
with MTAPolicy('bad@example.com',conf=self.config) as p:
|
||||||
|
pol = p.getPolicy('smtp-auth')
|
||||||
|
self.assertEqual(pol,'REJECT')
|
||||||
|
with MTAPolicy('bad@bad.example.com',conf=self.config) as p:
|
||||||
|
pol = p.getPolicy('smtp-auth')
|
||||||
|
self.assertEqual(pol,None)
|
||||||
|
with MTAPolicy('any@random.com',conf=self.config) as p:
|
||||||
|
pol = p.getPolicy('smtp-test')
|
||||||
|
self.assertEqual(pol,'REJECT')
|
||||||
|
with MTAPolicy('foo@bar.baz.com',conf=self.config) as p:
|
||||||
|
pol = p.getPolicy('smtp-test')
|
||||||
|
self.assertEqual(pol,'WILDCARD')
|
||||||
|
|
||||||
|
def suite(): return unittest.TestLoader().loadTestsFromTestCase(PolicyTestCase)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
unittest.main()
|
||||||
|
else:
|
||||||
|
a = sys.argv[1:]
|
||||||
|
while len(a) >= 2:
|
||||||
|
e,k = a[:2]
|
||||||
|
with MTAPolicy(e,conf=Config()) as p:
|
||||||
|
pol = p.getPolicy(k)
|
||||||
|
print(pol)
|
||||||
|
a = a[2:]
|
||||||
+64
-11
@@ -1,7 +1,9 @@
|
|||||||
import unittest
|
import unittest
|
||||||
import Milter
|
import Milter
|
||||||
import sample
|
import sample
|
||||||
|
import template
|
||||||
import mime
|
import mime
|
||||||
|
import zipfile
|
||||||
from Milter.test import TestBase
|
from Milter.test import TestBase
|
||||||
from Milter.testctx import TestCtx
|
from Milter.testctx import TestCtx
|
||||||
|
|
||||||
@@ -12,6 +14,48 @@ class TestMilter(TestBase,sample.sampleMilter):
|
|||||||
|
|
||||||
class BMSMilterTestCase(unittest.TestCase):
|
class BMSMilterTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.zf = zipfile.ZipFile('test/virus.zip','r')
|
||||||
|
self.zf.setpassword(b'denatured')
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.zf.close()
|
||||||
|
self.zf = None
|
||||||
|
|
||||||
|
def testTemplate(self,fname='test2'):
|
||||||
|
ctx = TestCtx()
|
||||||
|
Milter.factory = template.myMilter
|
||||||
|
ctx._setsymval('{auth_authen}','batman')
|
||||||
|
ctx._setsymval('{auth_type}','batcomputer')
|
||||||
|
ctx._setsymval('j','mailhost')
|
||||||
|
count = 10
|
||||||
|
while count > 0:
|
||||||
|
rc = ctx._connect(helo='milter-template.example.org')
|
||||||
|
self.assertEqual(rc,Milter.CONTINUE)
|
||||||
|
with open('test/'+fname,'rb') as fp:
|
||||||
|
rc = ctx._feedFile(fp)
|
||||||
|
milter = ctx.getpriv()
|
||||||
|
self.assertFalse(ctx._bodyreplaced,"Message body replaced")
|
||||||
|
ctx._close()
|
||||||
|
count -= 1
|
||||||
|
|
||||||
|
def testHeader(self,fname='utf8'):
|
||||||
|
ctx = TestCtx()
|
||||||
|
Milter.factory = sample.sampleMilter
|
||||||
|
ctx._setsymval('{auth_authen}','batman')
|
||||||
|
ctx._setsymval('{auth_type}','batcomputer')
|
||||||
|
ctx._setsymval('j','mailhost')
|
||||||
|
rc = ctx._connect()
|
||||||
|
self.assertEqual(rc,Milter.CONTINUE)
|
||||||
|
with open('test/'+fname,'rb') as fp:
|
||||||
|
rc = ctx._feedFile(fp)
|
||||||
|
milter = ctx.getpriv()
|
||||||
|
self.assertFalse(ctx._bodyreplaced,"Message body replaced")
|
||||||
|
fp = ctx._body
|
||||||
|
with open('test/'+fname+".tstout","wb") as ofp:
|
||||||
|
ofp.write(fp.getvalue())
|
||||||
|
ctx._close()
|
||||||
|
|
||||||
def testCtx(self,fname='virus1'):
|
def testCtx(self,fname='virus1'):
|
||||||
ctx = TestCtx()
|
ctx = TestCtx()
|
||||||
Milter.factory = sample.sampleMilter
|
Milter.factory = sample.sampleMilter
|
||||||
@@ -20,16 +64,18 @@ class BMSMilterTestCase(unittest.TestCase):
|
|||||||
ctx._setsymval('j','mailhost')
|
ctx._setsymval('j','mailhost')
|
||||||
rc = ctx._connect()
|
rc = ctx._connect()
|
||||||
self.assertTrue(rc == Milter.CONTINUE)
|
self.assertTrue(rc == Milter.CONTINUE)
|
||||||
rc = ctx._feedMsg(fname)
|
with self.zf.open(fname) as fp:
|
||||||
|
rc = ctx._feedFile(fp)
|
||||||
milter = ctx.getpriv()
|
milter = ctx.getpriv()
|
||||||
# self.assertTrue(milter.user == 'batman',"getsymval failed: "+
|
# self.assertTrue(milter.user == 'batman',"getsymval failed: "+
|
||||||
# "%s != %s"%(milter.user,'batman'))
|
# "%s != %s"%(milter.user,'batman'))
|
||||||
self.assertEquals(milter.user,'batman')
|
self.assertEqual(milter.user,'batman')
|
||||||
self.assertTrue(milter.auth_type != 'batcomputer',"setsymlist failed")
|
self.assertTrue(milter.auth_type != 'batcomputer',"setsymlist failed")
|
||||||
self.assertTrue(rc == Milter.ACCEPT)
|
self.assertTrue(rc == Milter.ACCEPT)
|
||||||
self.assertTrue(ctx._bodyreplaced,"Message body not replaced")
|
self.assertTrue(ctx._bodyreplaced,"Message body not replaced")
|
||||||
fp = ctx._body
|
fp = ctx._body
|
||||||
open('test/'+fname+".tstout","wb").write(fp.getvalue())
|
with open('test/'+fname+".tstout","wb") as f:
|
||||||
|
f.write(fp.getvalue())
|
||||||
#self.assertTrue(fp.getvalue() == open("test/virus1.out","r").read())
|
#self.assertTrue(fp.getvalue() == open("test/virus1.out","r").read())
|
||||||
fp.seek(0)
|
fp.seek(0)
|
||||||
msg = mime.message_from_file(fp)
|
msg = mime.message_from_file(fp)
|
||||||
@@ -44,14 +90,16 @@ class BMSMilterTestCase(unittest.TestCase):
|
|||||||
milter.setsymval('j','mailhost')
|
milter.setsymval('j','mailhost')
|
||||||
rc = milter.connect()
|
rc = milter.connect()
|
||||||
self.assertTrue(rc == Milter.CONTINUE)
|
self.assertTrue(rc == Milter.CONTINUE)
|
||||||
rc = milter.feedMsg(fname)
|
with self.zf.open(fname) as fp:
|
||||||
|
rc = milter.feedFile(fp)
|
||||||
self.assertTrue(milter.user == 'batman',"getsymval failed")
|
self.assertTrue(milter.user == 'batman',"getsymval failed")
|
||||||
# setsymlist not working in TestBase
|
# setsymlist not working in TestBase
|
||||||
#self.assertTrue(milter.auth_type != 'batcomputer',"setsymlist failed")
|
#self.assertTrue(milter.auth_type != 'batcomputer',"setsymlist failed")
|
||||||
self.assertTrue(rc == Milter.ACCEPT)
|
self.assertTrue(rc == Milter.ACCEPT)
|
||||||
self.assertTrue(milter._bodyreplaced,"Message body not replaced")
|
self.assertTrue(milter._bodyreplaced,"Message body not replaced")
|
||||||
fp = milter._body
|
fp = milter._body
|
||||||
open('test/'+fname+".tstout","wb").write(fp.getvalue())
|
with open('test/'+fname+".tstout","wb") as f:
|
||||||
|
f.write(fp.getvalue())
|
||||||
#self.assertTrue(fp.getvalue() == open("test/virus1.out","r").read())
|
#self.assertTrue(fp.getvalue() == open("test/virus1.out","r").read())
|
||||||
fp.seek(0)
|
fp.seek(0)
|
||||||
msg = mime.message_from_file(fp)
|
msg = mime.message_from_file(fp)
|
||||||
@@ -66,7 +114,8 @@ class BMSMilterTestCase(unittest.TestCase):
|
|||||||
self.assertTrue(rc == Milter.ACCEPT)
|
self.assertTrue(rc == Milter.ACCEPT)
|
||||||
self.assertFalse(milter._bodyreplaced,"Milter needlessly replaced body.")
|
self.assertFalse(milter._bodyreplaced,"Milter needlessly replaced body.")
|
||||||
fp = milter._body
|
fp = milter._body
|
||||||
open('test/'+fname+".tstout","wb").write(fp.getvalue())
|
with open('test/'+fname+".tstout","wb") as f:
|
||||||
|
f.write(fp.getvalue())
|
||||||
milter.close()
|
milter.close()
|
||||||
|
|
||||||
def testDefang2(self):
|
def testDefang2(self):
|
||||||
@@ -75,21 +124,25 @@ class BMSMilterTestCase(unittest.TestCase):
|
|||||||
rc = milter.feedMsg('samp1')
|
rc = milter.feedMsg('samp1')
|
||||||
self.assertTrue(rc == Milter.ACCEPT)
|
self.assertTrue(rc == Milter.ACCEPT)
|
||||||
self.assertFalse(milter._bodyreplaced,"Milter needlessly replaced body.")
|
self.assertFalse(milter._bodyreplaced,"Milter needlessly replaced body.")
|
||||||
rc = milter.feedMsg("virus3")
|
with self.zf.open("virus3") as fp:
|
||||||
|
rc = milter.feedFile(fp)
|
||||||
self.assertTrue(rc == Milter.ACCEPT)
|
self.assertTrue(rc == Milter.ACCEPT)
|
||||||
self.assertTrue(milter._bodyreplaced,"Message body not replaced")
|
self.assertTrue(milter._bodyreplaced,"Message body not replaced")
|
||||||
fp = milter._body
|
fp = milter._body
|
||||||
open("test/virus3.tstout","wb").write(fp.getvalue())
|
with open("test/virus3.tstout","wb") as f:
|
||||||
|
f.write(fp.getvalue())
|
||||||
#self.assertTrue(fp.getvalue() == open("test/virus3.out","r").read())
|
#self.assertTrue(fp.getvalue() == open("test/virus3.out","r").read())
|
||||||
rc = milter.feedMsg("virus6")
|
with self.zf.open("virus6") as fp:
|
||||||
|
rc = milter.feedFile(fp)
|
||||||
self.assertTrue(rc == Milter.ACCEPT)
|
self.assertTrue(rc == Milter.ACCEPT)
|
||||||
self.assertTrue(milter._bodyreplaced,"Message body not replaced")
|
self.assertTrue(milter._bodyreplaced,"Message body not replaced")
|
||||||
self.assertTrue(milter._headerschanged,"Message headers not adjusted")
|
self.assertTrue(milter._headerschanged,"Message headers not adjusted")
|
||||||
fp = milter._body
|
fp = milter._body
|
||||||
open("test/virus6.tstout","wb").write(fp.getvalue())
|
with open("test/virus6.tstout","wb") as f:
|
||||||
|
f.write(fp.getvalue())
|
||||||
milter.close()
|
milter.close()
|
||||||
|
|
||||||
def suite(): return unittest.makeSuite(BMSMilterTestCase,'test')
|
def suite(): return unittest.TestLoader().loadTestsFromTestCase(BMSMilterTestCase)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
+7
-4
@@ -24,11 +24,11 @@ class AddrCacheTestCase(unittest.TestCase):
|
|||||||
self.assertTrue(cache.has_key('foo@bar.com'))
|
self.assertTrue(cache.has_key('foo@bar.com'))
|
||||||
self.assertTrue(not cache.has_key('hello@bar.com'))
|
self.assertTrue(not cache.has_key('hello@bar.com'))
|
||||||
self.assertTrue('baz@bar.com' in cache)
|
self.assertTrue('baz@bar.com' in cache)
|
||||||
self.assertEquals(cache['temp@bar.com'],'testing')
|
self.assertEqual(cache['temp@bar.com'],'testing')
|
||||||
s = open(self.fname).readlines()
|
s = open(self.fname).readlines()
|
||||||
self.assertTrue(len(s) == 2)
|
self.assertTrue(len(s) == 2)
|
||||||
self.assertTrue(s[0].startswith('foo@bar.com '))
|
self.assertTrue(s[0].startswith('foo@bar.com '))
|
||||||
self.assertEquals(s[1].strip(),'baz@bar.com')
|
self.assertEqual(s[1].strip(),'baz@bar.com')
|
||||||
# check that new result overrides old
|
# check that new result overrides old
|
||||||
cache['temp@bar.com'] = None
|
cache['temp@bar.com'] = None
|
||||||
self.assertTrue(not cache['temp@bar.com'])
|
self.assertTrue(not cache['temp@bar.com'])
|
||||||
@@ -43,7 +43,10 @@ class AddrCacheTestCase(unittest.TestCase):
|
|||||||
def testParseHeader(self):
|
def testParseHeader(self):
|
||||||
s='=?UTF-8?B?TGFzdCBGZXcgQ29sZHBsYXkgQWxidW0gQXJ0d29ya3MgQXZhaWxhYmxlAA?='
|
s='=?UTF-8?B?TGFzdCBGZXcgQ29sZHBsYXkgQWxidW0gQXJ0d29ya3MgQXZhaWxhYmxlAA?='
|
||||||
h = Milter.utils.parse_header(s)
|
h = Milter.utils.parse_header(s)
|
||||||
self.assertEqual(h,b'Last Few Coldplay Album Artworks Available\x00')
|
self.assertEqual(h,'Last Few Coldplay Album Artworks Available\x00')
|
||||||
|
s='=?iso-8859-1?Q?Peter_=D8rum?= <orum@ditas.dk>'
|
||||||
|
h = Milter.utils.parse_header(s)
|
||||||
|
self.assertEqual(h,'Peter \xd8rum <orum@ditas.dk>')
|
||||||
|
|
||||||
@unittest.expectedFailure
|
@unittest.expectedFailure
|
||||||
def testParseAddress(self):
|
def testParseAddress(self):
|
||||||
@@ -51,7 +54,7 @@ class AddrCacheTestCase(unittest.TestCase):
|
|||||||
self.assertEqual(s,('WRONG', 'a@b'))
|
self.assertEqual(s,('WRONG', 'a@b'))
|
||||||
|
|
||||||
def suite():
|
def suite():
|
||||||
s = unittest.makeSuite(AddrCacheTestCase,'test')
|
s = unittest.TestLoader().loadTestsFromTestCase(AddrCacheTestCase)
|
||||||
s.addTest(doctest.DocTestSuite(Milter.utils))
|
s.addTest(doctest.DocTestSuite(Milter.utils))
|
||||||
s.addTest(doctest.DocTestSuite(Milter.dynip))
|
s.addTest(doctest.DocTestSuite(Milter.dynip))
|
||||||
s.addTest(doctest.DocTestSuite(Milter.pyip6))
|
s.addTest(doctest.DocTestSuite(Milter.pyip6))
|
||||||
|
|||||||
Reference in New Issue
Block a user