Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f357a237b2 |
@@ -0,0 +1,29 @@
|
|||||||
|
Jim Niemira (urmane@urmane.org) wrote the original C module and some quick
|
||||||
|
and dirty python to use it. Stuart D. Gathman (stuart@bmsi.com) took that
|
||||||
|
kludge and added threading and context objects to it, wrote a proper OO
|
||||||
|
wrapper (Milter.py) that handles attachments, did lots of testing, packaged
|
||||||
|
it with distutils, and generally transformed it from a quick hack to a
|
||||||
|
real, usable Python extension.
|
||||||
|
|
||||||
|
Other contributors:
|
||||||
|
|
||||||
|
Terence Way
|
||||||
|
for providing a Python port of SPF
|
||||||
|
Alexander Kourakos
|
||||||
|
for plugging several memory leaks
|
||||||
|
George Graf at Vienna University of Economics and Business Administration
|
||||||
|
for handling None passed to setreply and chgheader.
|
||||||
|
Deron Meranda
|
||||||
|
for IPv6 patches
|
||||||
|
Jason Erikson
|
||||||
|
for handling NULL hostaddr in connect callback.
|
||||||
|
John Draper
|
||||||
|
for porting Python milter to OpenBSD, and starting to work on tutorials
|
||||||
|
then pointing out that it would be easier to just write the MTA in Python.
|
||||||
|
Eric S. Johansson
|
||||||
|
for helpful design discussions while working on camram
|
||||||
|
Business Management Systems - http://www.bmsi.com
|
||||||
|
for hosting the website, and providing paying clients who need milter service
|
||||||
|
so I can work on it as part of my day job.
|
||||||
|
|
||||||
|
If I have left anybody out, send me a reminder: stuart@bmsi.com
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
include COPYING
|
||||||
|
include TODO
|
||||||
|
include NEWS
|
||||||
|
include CREDITS
|
||||||
|
include README
|
||||||
|
include MANIFEST.in
|
||||||
|
include testsample.py
|
||||||
|
include testmime.py
|
||||||
|
include testbms.py
|
||||||
|
include testdspam.py
|
||||||
|
include rejects.py
|
||||||
|
include bms.py
|
||||||
|
include spf.py
|
||||||
|
include spfquery.py
|
||||||
|
include test.py
|
||||||
|
include sample.py
|
||||||
|
include test/*
|
||||||
|
include *.spec
|
||||||
|
include start.sh
|
||||||
|
include milter.rc
|
||||||
|
include milter.rc7
|
||||||
|
include milter.cfg
|
||||||
|
include rhsbl.m4
|
||||||
+75
-661
@@ -1,34 +1,25 @@
|
|||||||
## @package Milter
|
|
||||||
# A thin OO wrapper for the milter module.
|
|
||||||
#
|
|
||||||
# Clients generally subclass Milter.Base and define callback
|
|
||||||
# methods.
|
|
||||||
#
|
|
||||||
# @author Stuart D. Gathman <stuart@bmsi.com>
|
|
||||||
# Copyright 2001,2009 Business Management Systems, Inc.
|
|
||||||
# This code is under the GNU General Public License. See COPYING for details.
|
|
||||||
|
|
||||||
__version__ = '0.9.8'
|
# Author: Stuart D. Gathman <stuart@bmsi.com>
|
||||||
|
# Copyright 2001 Business Management Systems, Inc.
|
||||||
|
# This code is under GPL. See COPYING for details.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import milter
|
import milter
|
||||||
import thread
|
import thread
|
||||||
|
|
||||||
from milter import *
|
from milter import ACCEPT,CONTINUE,REJECT,DISCARD,TEMPFAIL, \
|
||||||
from functools import wraps
|
set_flags, setdbg, setbacklog, settimeout, \
|
||||||
|
ADDHDRS, CHGBODY, ADDRCPT, DELRCPT, CHGHDRS, \
|
||||||
|
V1_ACTS, V2_ACTS, CURR_ACTS
|
||||||
|
|
||||||
|
try: from milter import QUARANTINE
|
||||||
|
except: pass
|
||||||
|
|
||||||
_seq_lock = thread.allocate_lock()
|
_seq_lock = thread.allocate_lock()
|
||||||
_seq = 0
|
_seq = 0
|
||||||
|
|
||||||
## @fn set_flags(flags)
|
|
||||||
# @brief Enable optional %milter actions.
|
|
||||||
# Certain %milter actions need to be enabled before calling milter.runmilter()
|
|
||||||
# or they throw an exception.
|
|
||||||
# @param flags Bit ored mask of optional actions to enable
|
|
||||||
|
|
||||||
def uniqueID():
|
def uniqueID():
|
||||||
"""Return a unique sequence number (incremented on each call).
|
"""Return a sequence number unique to this process.
|
||||||
"""
|
"""
|
||||||
global _seq
|
global _seq
|
||||||
_seq_lock.acquire()
|
_seq_lock.acquire()
|
||||||
@@ -36,572 +27,30 @@ def uniqueID():
|
|||||||
_seq_lock.release()
|
_seq_lock.release()
|
||||||
return seqno
|
return seqno
|
||||||
|
|
||||||
## @private
|
class Milter:
|
||||||
OPTIONAL_CALLBACKS = {
|
"""A simple class interface to the milter module.
|
||||||
'connect':(P_NR_CONN,P_NOCONNECT),
|
"""
|
||||||
'hello':(P_NR_HELO,P_NOHELO),
|
|
||||||
'envfrom':(P_NR_MAIL,P_NOMAIL),
|
|
||||||
'envrcpt':(P_NR_RCPT,P_NORCPT),
|
|
||||||
'data':(P_NR_DATA,P_NODATA),
|
|
||||||
'unknown':(P_NR_UNKN,P_NOUNKNOWN),
|
|
||||||
'eoh':(P_NR_EOH,P_NOEOH),
|
|
||||||
'body':(P_NR_BODY,P_NOBODY),
|
|
||||||
'header':(P_NR_HDR,P_NOHDRS)
|
|
||||||
}
|
|
||||||
|
|
||||||
## @private
|
|
||||||
R = re.compile(r'%+')
|
|
||||||
|
|
||||||
## @private
|
|
||||||
def decode_mask(bits,names):
|
|
||||||
t = [ (s,getattr(milter,s)) for s in names]
|
|
||||||
nms = [s for s,m in t if bits & m]
|
|
||||||
for s,m in t: bits &= ~m
|
|
||||||
if bits: nms += hex(bits)
|
|
||||||
return nms
|
|
||||||
|
|
||||||
## Class decorator to enable optional protocol steps.
|
|
||||||
# P_SKIP is enabled by default when supported, but
|
|
||||||
# applications may wish to enable P_HDR_LEADSPC
|
|
||||||
# to send and receive the leading space of header continuation
|
|
||||||
# lines unchanged, and/or P_RCPT_REJ to have recipients
|
|
||||||
# detected as invalid by the MTA passed to the envcrpt callback.
|
|
||||||
#
|
|
||||||
# Applications may want to check whether the protocol is actually
|
|
||||||
# supported by the MTA in use. Base._protocol
|
|
||||||
# is a bitmask of protocol options negotiated. So,
|
|
||||||
# for instance, if <code>self._protocol & Milter.P_RCPT_REJ</code>
|
|
||||||
# is true, then that feature was successfully negotiated with the MTA
|
|
||||||
# and the application will see recipients the MTA has flagged as invalid.
|
|
||||||
#
|
|
||||||
# Sample use:
|
|
||||||
# <pre>
|
|
||||||
# class myMilter(Milter.Base):
|
|
||||||
# def envrcpt(self,to,*params):
|
|
||||||
# return Milter.CONTINUE
|
|
||||||
# myMilter = Milter.enable_protocols(myMilter,Milter.P_RCPT_REJ)
|
|
||||||
# </pre>
|
|
||||||
# @since 0.9.3
|
|
||||||
# @param klass the %milter application class to modify
|
|
||||||
# @param mask a bitmask of protocol steps to enable
|
|
||||||
# @return the modified %milter class
|
|
||||||
def enable_protocols(klass,mask):
|
|
||||||
klass._protocol_mask = klass.protocol_mask() & ~mask
|
|
||||||
return klass
|
|
||||||
|
|
||||||
## Milter rejected recipients. A class decorator that calls
|
|
||||||
# enable_protocols() with the P_RCPT_REJ flag. By default, the MTA
|
|
||||||
# does not pass recipients that it knows are invalid on to the milter.
|
|
||||||
# This decorator enables a %milter app to see all recipients if supported
|
|
||||||
# by the MTA. Use like this with python-2.6 and later:
|
|
||||||
# <pre>
|
|
||||||
# @@Milter.rejected_recipients
|
|
||||||
# class myMilter(Milter.Base):
|
|
||||||
# def envrcpt(self,to,*params):
|
|
||||||
# return Milter.CONTINUE
|
|
||||||
# </pre>
|
|
||||||
# @since 0.9.5
|
|
||||||
# @param klass the %milter application class to modify
|
|
||||||
# @return the modified %milter class
|
|
||||||
def rejected_recipients(klass):
|
|
||||||
return enable_protocols(klass,P_RCPT_REJ)
|
|
||||||
|
|
||||||
## Milter leading space on headers. A class decorator that calls
|
|
||||||
# enable_protocols() with the P_HEAD_LEADSPC flag. By default,
|
|
||||||
# header continuation lines are collected and joined before getting
|
|
||||||
# sent to a milter. Headers modified or added by the milter are
|
|
||||||
# folded by the MTA as necessary according to its own standards.
|
|
||||||
# With this flag, header continuation lines are preserved
|
|
||||||
# with their newlines and leading space. In addition, header folding
|
|
||||||
# done by the milter is preserved as well.
|
|
||||||
# Use like this with python-2.6 and later:
|
|
||||||
# <pre>
|
|
||||||
# @@Milter.header_leading_space
|
|
||||||
# class myMilter(Milter.Base):
|
|
||||||
# def header(self,hname,value):
|
|
||||||
# return Milter.CONTINUE
|
|
||||||
# </pre>
|
|
||||||
# @since 0.9.5
|
|
||||||
# @param klass the %milter application class to modify
|
|
||||||
# @return the modified %milter class
|
|
||||||
def header_leading_space(klass):
|
|
||||||
return enable_protocols(klass,P_HEAD_LEADSPC)
|
|
||||||
|
|
||||||
## Function decorator to disable callback methods.
|
|
||||||
# If the MTA supports it, tells the MTA not to invoke this callback,
|
|
||||||
# increasing efficiency. All the callbacks (except negotiate)
|
|
||||||
# are disabled in Milter.Base, and overriding them reenables the
|
|
||||||
# callback. An application may need to use @@nocallback when it extends
|
|
||||||
# another %milter and wants to disable a callback again.
|
|
||||||
# The disabled method should still return Milter.CONTINUE, in case the MTA does
|
|
||||||
# not support protocol negotiation, and for when called from a test harness.
|
|
||||||
# @since 0.9.2
|
|
||||||
def nocallback(func):
|
|
||||||
try:
|
|
||||||
func.milter_protocol = OPTIONAL_CALLBACKS[func.__name__][1]
|
|
||||||
except KeyError:
|
|
||||||
raise ValueError(
|
|
||||||
'@nocallback applied to non-optional method: '+func.__name__)
|
|
||||||
def wrapper(self,*args):
|
|
||||||
if func(self,*args) != CONTINUE:
|
|
||||||
raise RuntimeError('%s return code must be CONTINUE with @nocallback'
|
|
||||||
% func.__name__)
|
|
||||||
return CONTINUE
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
## Function decorator to disable callback reply.
|
|
||||||
# If the MTA supports it, tells the MTA not to wait for a reply from
|
|
||||||
# this callback, and assume CONTINUE. The method should still return
|
|
||||||
# CONTINUE in case the MTA does not support protocol negotiation.
|
|
||||||
# The decorator arranges to change the return code to NOREPLY
|
|
||||||
# when supported by the MTA.
|
|
||||||
# @since 0.9.2
|
|
||||||
def noreply(func):
|
|
||||||
try:
|
|
||||||
nr_mask = OPTIONAL_CALLBACKS[func.__name__][0]
|
|
||||||
except KeyError:
|
|
||||||
raise ValueError(
|
|
||||||
'@noreply applied to non-optional method: '+func.__name__)
|
|
||||||
@wraps(func)
|
|
||||||
def wrapper(self,*args):
|
|
||||||
rc = func(self,*args)
|
|
||||||
if self._protocol & nr_mask:
|
|
||||||
if rc != CONTINUE:
|
|
||||||
raise RuntimeError('%s return code must be CONTINUE with @noreply'
|
|
||||||
% func.__name__)
|
|
||||||
return NOREPLY
|
|
||||||
return rc
|
|
||||||
wrapper.milter_protocol = nr_mask
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
## Disabled action exception.
|
|
||||||
# set_flags() can tell the MTA that this application will not use certain
|
|
||||||
# features (such as CHGFROM). This can also be negotiated for each
|
|
||||||
# connection in the negotiate callback. If the application then calls
|
|
||||||
# the feature anyway via an instance method, this exception is
|
|
||||||
# thrown.
|
|
||||||
# @since 0.9.2
|
|
||||||
class DisabledAction(RuntimeError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
## A do "nothing" Milter base class representing an SMTP connection.
|
|
||||||
#
|
|
||||||
# Python milters should derive from this class
|
|
||||||
# unless they are using the low level milter module directly.
|
|
||||||
#
|
|
||||||
# Most of the methods are either "actions" or "callbacks". Callbacks
|
|
||||||
# are invoked by the MTA at certain points in the SMTP protocol. For
|
|
||||||
# instance when the HELO command is seen, the MTA calls the helo
|
|
||||||
# callback before returning a response code. All callbacks must
|
|
||||||
# return one of these constants: CONTINUE, TEMPFAIL, REJECT, ACCEPT,
|
|
||||||
# DISCARD, SKIP. The NOREPLY response is supplied automatically by
|
|
||||||
# the @@noreply decorator if negotiation with the MTA is successful.
|
|
||||||
# @@noreply and @@nocallback methods should return CONTINUE for two reasons:
|
|
||||||
# the MTA may not support negotiation, and the class may be running in a test
|
|
||||||
# harness.
|
|
||||||
#
|
|
||||||
# Optional callbacks are disabled with the @@nocallback decorator, and
|
|
||||||
# automatically reenabled when overridden. Disabled callbacks should
|
|
||||||
# still return CONTINUE for testing and MTAs that do not support
|
|
||||||
# negotiation.
|
|
||||||
|
|
||||||
# Each SMTP connection to the MTA calls the factory method you provide to
|
|
||||||
# create an instance derived from this class. This is typically the
|
|
||||||
# constructor for a class derived from Base. The _setctx() method attaches
|
|
||||||
# the instance to the low level milter.milterContext object. When the SMTP
|
|
||||||
# connection terminates, the close callback is called, the low level connection
|
|
||||||
# object is destroyed, and this normally causes instances of this class to be
|
|
||||||
# garbage collected as well. The close() method should release any global
|
|
||||||
# resources held by instances.
|
|
||||||
# @since 0.9.2
|
|
||||||
class Base(object):
|
|
||||||
"The core class interface to the %milter module."
|
|
||||||
|
|
||||||
## Attach this Milter to the low level milter.milterContext object.
|
|
||||||
def _setctx(self,ctx):
|
def _setctx(self,ctx):
|
||||||
## The low level @ref milter.milterContext object.
|
self.__ctx = ctx
|
||||||
self._ctx = ctx
|
|
||||||
## A bitmask of actions this connection has negotiated to use.
|
|
||||||
# By default, all actions are enabled. High throughput milters
|
|
||||||
# may want to disable unused actions to increase efficiency.
|
|
||||||
# Some optional actions may be disabled by calling milter.set_flags(), or
|
|
||||||
# by overriding the negotiate callback. The bits include:
|
|
||||||
# <code>ADDHDRS,CHGBODY,MODBODY,ADDRCPT,ADDRCPT_PAR,DELRCPT
|
|
||||||
# CHGHDRS,QUARANTINE,CHGFROM,SETSYMLIST</code>.
|
|
||||||
# The <code>Milter.CURR_ACTS</code> bitmask is all actions
|
|
||||||
# known when the milter module was compiled.
|
|
||||||
# Application code can also inspect this field to determine
|
|
||||||
# which actions are available. This is especially useful in
|
|
||||||
# generic library code designed to work in multiple milters.
|
|
||||||
# @since 0.9.2
|
|
||||||
#
|
|
||||||
self._actions = CURR_ACTS # all actions enabled by default
|
|
||||||
## A bitmask of protocol options this connection has negotiated.
|
|
||||||
# An application may inspect this
|
|
||||||
# variable to determine which protocol steps are supported. Options
|
|
||||||
# of interest to applications: the SKIP result code is allowed
|
|
||||||
# only if the P_SKIP bit is set, rejected recipients are passed to the
|
|
||||||
# %milter application only if the P_RCPT_REJ bit is set, and
|
|
||||||
# header values are sent and received with leading spaces (in the
|
|
||||||
# continuation lines) intact if the P_HDR_LEADSPC bit is set (so
|
|
||||||
# that the application can customize indenting).
|
|
||||||
#
|
|
||||||
# The P_N* bits should be negotiated via the @@noreply and @@nocallback
|
|
||||||
# method decorators, and P_RCPT_REJ, P_HDR_LEADSPC should
|
|
||||||
# be enabled using the enable_protocols class decorator.
|
|
||||||
#
|
|
||||||
# The bits include: <code>
|
|
||||||
# P_RCPT_REJ P_NR_CONN P_NR_HELO P_NR_MAIL P_NR_RCPT P_NR_DATA P_NR_UNKN
|
|
||||||
# P_NR_EOH P_NR_BODY P_NR_HDR P_NOCONNECT P_NOHELO P_NOMAIL P_NORCPT
|
|
||||||
# P_NODATA P_NOUNKNOWN P_NOEOH P_NOBODY P_NOHDRS P_HDR_LEADSPC P_SKIP
|
|
||||||
# </code> (all under the Milter namespace).
|
|
||||||
# @since 0.9.2
|
|
||||||
self._protocol = 0 # no protocol options by default
|
|
||||||
if ctx:
|
if ctx:
|
||||||
ctx.setpriv(self)
|
ctx.setpriv(self)
|
||||||
|
|
||||||
## Defined by subclasses to write log messages.
|
# user replaceable callbacks
|
||||||
def log(self,*msg): pass
|
|
||||||
## Called for each connection to the MTA. Called by the
|
|
||||||
# <a href="https://www.milter.org/developers/api/xxfi_connect">
|
|
||||||
# xxfi_connect</a> callback.
|
|
||||||
# The <code>hostname</code> provided by the local MTA is either
|
|
||||||
# the PTR name or the IP in the form "[1.2.3.4]" if no PTR is available.
|
|
||||||
# The format of hostaddr depends on the socket family:
|
|
||||||
# <dl>
|
|
||||||
# <dt><code>socket.AF_INET</code>
|
|
||||||
# <dd>A tuple of (IP as string in dotted quad form, integer port)
|
|
||||||
# <dt><code>socket.AF_INET6</code>
|
|
||||||
# <dd>A tuple of (IP as a string in standard representation,
|
|
||||||
# integer port, integer flow info, integer scope id)
|
|
||||||
# <dt><code>socket.AF_UNIX</code>
|
|
||||||
# <dd>A string with the socketname
|
|
||||||
# </dl>
|
|
||||||
# To vary behavior based on what port the client connected to,
|
|
||||||
# for example skipping blacklist checks for port 587 (which must
|
|
||||||
# be authenticated), use @link #getsymval getsymval('{daemon_port}') @endlink.
|
|
||||||
# The <code>{daemon_port}</code> macro must be enabled in sendmail.cf
|
|
||||||
# <pre>
|
|
||||||
# O Milter.macros.connect=j, _, {daemon_name}, {daemon_port}, {if_name}, {if_addr}
|
|
||||||
# </pre>
|
|
||||||
# or sendmail.mc
|
|
||||||
# <pre>
|
|
||||||
# define(`confMILTER_MACROS_CONNECT', ``j, _, {daemon_name}, {daemon_port}, {if_name}, {if_addr}'')dnl
|
|
||||||
# </pre>
|
|
||||||
# @param hostname the PTR name or bracketed IP of the SMTP client
|
|
||||||
# @param family <code>socket.AF_INET</code>, <code>socket.AF_INET6</code>,
|
|
||||||
# or <code>socket.AF_UNIX</code>
|
|
||||||
# @param hostaddr a tuple or string with peer IP or socketname
|
|
||||||
@nocallback
|
|
||||||
def connect(self,hostname,family,hostaddr): return CONTINUE
|
|
||||||
## Called when the SMTP client says HELO.
|
|
||||||
# Returning REJECT prevents progress until a valid HELO is provided;
|
|
||||||
# this almost always results in terminating the connection.
|
|
||||||
@nocallback
|
|
||||||
def hello(self,hostname): return CONTINUE
|
|
||||||
## Called when the SMTP client says MAIL FROM. Called by the
|
|
||||||
# <a href="https://www.milter.org/developers/api/xxfi_envfrom">
|
|
||||||
# xxfi_envfrom</a> callback.
|
|
||||||
# Returning REJECT rejects the message, but not the connection.
|
|
||||||
# The sender is the "envelope" from as defined by
|
|
||||||
# <a href="http://tools.ietf.org/html/rfc5321">RFC 5321</a>.
|
|
||||||
# For the From: header (author) defined in
|
|
||||||
# <a href="http://tools.ietf.org/html/rfc5322">RFC 5322</a>,
|
|
||||||
# see @link #header the header callback @endlink.
|
|
||||||
@nocallback
|
|
||||||
def envfrom(self,f,*str): return CONTINUE
|
|
||||||
## Called when the SMTP client says RCPT TO. Called by the
|
|
||||||
# <a href="https://www.milter.org/developers/api/xxfi_envrcpt">
|
|
||||||
# xxfi_envrcpt</a> callback.
|
|
||||||
# Returning REJECT rejects the current recipient, not the entire message.
|
|
||||||
# The recipient is the "envelope" recipient as defined by
|
|
||||||
# <a href="http://tools.ietf.org/html/rfc5321">RFC 5321</a>.
|
|
||||||
# For recipients defined in
|
|
||||||
# <a href="http://tools.ietf.org/html/rfc5322">RFC 5322</a>,
|
|
||||||
# for example To: or Cc:, see @link #header the header callback @endlink.
|
|
||||||
@nocallback
|
|
||||||
def envrcpt(self,to,*str): return CONTINUE
|
|
||||||
## Called when the SMTP client says DATA.
|
|
||||||
# Returning REJECT rejects the message without wasting bandwidth
|
|
||||||
# on the unwanted message.
|
|
||||||
# @since 0.9.2
|
|
||||||
@nocallback
|
|
||||||
def data(self): return CONTINUE
|
|
||||||
## Called for each header field in the message body.
|
|
||||||
@nocallback
|
|
||||||
def header(self,field,value): return CONTINUE
|
|
||||||
## Called at the blank line that terminates the header fields.
|
|
||||||
@nocallback
|
|
||||||
def eoh(self): return CONTINUE
|
|
||||||
## Called to supply the body of the message to the Milter by chunks.
|
|
||||||
# @param blk a block of message bytes
|
|
||||||
@nocallback
|
|
||||||
def body(self,blk): return CONTINUE
|
|
||||||
## Called when the SMTP client issues an unknown command.
|
|
||||||
# @param cmd the unknown command
|
|
||||||
# @since 0.9.2
|
|
||||||
@nocallback
|
|
||||||
def unknown(self,cmd): return CONTINUE
|
|
||||||
## Called at the end of the message body.
|
|
||||||
# Most of the message manipulation actions can only take place from
|
|
||||||
# the eom callback.
|
|
||||||
def eom(self): return CONTINUE
|
|
||||||
## Called when the connection is abnormally terminated.
|
|
||||||
# The close callback is still called also.
|
|
||||||
def abort(self): return CONTINUE
|
|
||||||
## Called when the connection is closed.
|
|
||||||
def close(self): return CONTINUE
|
|
||||||
|
|
||||||
## Return mask of SMFIP_N* protocol option bits to clear for this class
|
|
||||||
# The @@nocallback and @@noreply decorators set the
|
|
||||||
# <code>milter_protocol</code> function attribute to the protocol mask bit to
|
|
||||||
# pass to libmilter, causing that callback or its reply to be skipped.
|
|
||||||
# Overriding a method creates a new function object, so that
|
|
||||||
# <code>milter_protocol</code> defaults to 0.
|
|
||||||
# Libmilter passes the protocol bits that the current MTA knows
|
|
||||||
# how to skip. We clear the ones we don't want to skip.
|
|
||||||
# The negation is somewhat mind bending, but it is simple.
|
|
||||||
# @since 0.9.2
|
|
||||||
@classmethod
|
|
||||||
def protocol_mask(klass):
|
|
||||||
try:
|
|
||||||
return klass._protocol_mask
|
|
||||||
except AttributeError:
|
|
||||||
p = P_RCPT_REJ | P_HDR_LEADSPC # turn these new features off by default
|
|
||||||
for func,(nr,nc) in OPTIONAL_CALLBACKS.items():
|
|
||||||
func = getattr(klass,func)
|
|
||||||
ca = getattr(func,'milter_protocol',0)
|
|
||||||
#print func,hex(nr),hex(nc),hex(ca)
|
|
||||||
p |= (nr|nc) & ~ca
|
|
||||||
klass._protocol_mask = p
|
|
||||||
return p
|
|
||||||
|
|
||||||
## Negotiate milter protocol options. Called by the
|
|
||||||
# <a href="https://www.milter.org/developers/api/xxfi_negotiate">
|
|
||||||
# xffi_negotiate</a> callback. This is an advanced callback,
|
|
||||||
# do not override unless you know what you are doing. Most
|
|
||||||
# negotiation can be done simply by using the supplied
|
|
||||||
# class and function decorators.
|
|
||||||
# Options are passed as
|
|
||||||
# a list of 4 32-bit ints which can be modified and are passed
|
|
||||||
# back to libmilter on return.
|
|
||||||
# Default negotiation sets P_NO* and P_NR* for callbacks
|
|
||||||
# marked @@nocallback and @@noreply respectively, leaves all
|
|
||||||
# actions enabled, and enables Milter.SKIP. The @@enable_protocols
|
|
||||||
# class decorator can customize which protocol steps are implemented.
|
|
||||||
# @param opts a modifiable list of 4 ints with negotiated options
|
|
||||||
# @since 0.9.2
|
|
||||||
def negotiate(self,opts):
|
|
||||||
try:
|
|
||||||
self._actions,p,f1,f2 = opts
|
|
||||||
opts[1] = self._protocol = p & ~self.protocol_mask()
|
|
||||||
opts[2] = 0
|
|
||||||
opts[3] = 0
|
|
||||||
#self.log("Negotiated:",opts)
|
|
||||||
except:
|
|
||||||
# don't change anything if something went wrong
|
|
||||||
return ALL_OPTS
|
|
||||||
return CONTINUE
|
|
||||||
|
|
||||||
# Milter methods which can be invoked from most callbacks
|
|
||||||
|
|
||||||
## Return the value of an MTA macro. Sendmail macro names
|
|
||||||
# are either single chars (e.g. "j") or multiple chars enclosed
|
|
||||||
# in braces (e.g. "{auth_type}"). Macro names are MTA dependent.
|
|
||||||
# See <a href="https://www.milter.org/developers/api/smfi_getsymval">
|
|
||||||
# smfi_getsymval</a> for default sendmail macros.
|
|
||||||
# @param sym the macro name
|
|
||||||
def getsymval(self,sym):
|
|
||||||
return self._ctx.getsymval(sym)
|
|
||||||
|
|
||||||
## Set the SMTP reply code and message.
|
|
||||||
# If the MTA does not support setmlreply, then only the
|
|
||||||
# first msg line is used. Any '%%' in a message line
|
|
||||||
# must be doubled, or libmilter will silently ignore the setreply.
|
|
||||||
# Beginning with 0.9.6, we test for that case and throw ValueError to avoid
|
|
||||||
# head scratching. What will <i>really</i> irritate you, however,
|
|
||||||
# is that if you carefully double any '%%', your message will be
|
|
||||||
# sent - but with the '%%' still doubled!
|
|
||||||
# See <a href="https://www.milter.org/developers/api/smfi_setreply">
|
|
||||||
# smfi_setreply</a> for more information.
|
|
||||||
# @param rcode The three-digit (RFC 821/2821) SMTP reply code as a string.
|
|
||||||
# rcode cannot be None, and <b>must be a valid 4XX or 5XX reply code</b>.
|
|
||||||
# @param xcode The extended (RFC 1893/2034) reply code. If xcode is None,
|
|
||||||
# no extended code is used. Otherwise, xcode must conform to RFC 1893/2034.
|
|
||||||
# @param msg The text part of the SMTP reply. If msg is None,
|
|
||||||
# an empty message is used.
|
|
||||||
# @param ml Optional additional message lines.
|
|
||||||
def setreply(self,rcode,xcode=None,msg=None,*ml):
|
|
||||||
for m in (msg,)+ml:
|
|
||||||
if 1 in [len(s)&1 for s in R.findall(m)]:
|
|
||||||
raise ValueError("'%' must be doubled: "+m)
|
|
||||||
return self._ctx.setreply(rcode,xcode,msg,*ml)
|
|
||||||
|
|
||||||
## Tell the MTA which macro names will be used.
|
|
||||||
# This information can reduce the size of messages received from sendmail,
|
|
||||||
# and hence could reduce bandwidth between sendmail and your milter where
|
|
||||||
# that is a factor. The <code>Milter.SETSYMLIST</code> action flag must be
|
|
||||||
# set. The protocol stages are M_CONNECT, M_HELO, M_ENVFROM, M_ENVRCPT,
|
|
||||||
# M_DATA, M_EOM, M_EOH.
|
|
||||||
#
|
|
||||||
# May only be called from negotiate callback.
|
|
||||||
# @since 0.9.8, previous version was misspelled!
|
|
||||||
# @param stage the protocol stage to set to macro list for,
|
|
||||||
# one of the M_* constants defined in Milter
|
|
||||||
# @param macros space separated and/or lists of strings
|
|
||||||
def setsymlist(self,stage,*macros):
|
|
||||||
if not self._actions & SETSYMLIST: raise DisabledAction("SETSYMLIST")
|
|
||||||
a = []
|
|
||||||
for m in macros:
|
|
||||||
try:
|
|
||||||
m = m.encode('utf8')
|
|
||||||
except: pass
|
|
||||||
try:
|
|
||||||
m = m.split(' ')
|
|
||||||
except: pass
|
|
||||||
a += m
|
|
||||||
return self._ctx.setsmlist(stage,' '.join(a))
|
|
||||||
|
|
||||||
# Milter methods which can only be called from eom callback.
|
|
||||||
|
|
||||||
## Add a mail header field.
|
|
||||||
# Calls <a href="https://www.milter.org/developers/api/smfi_addheader">
|
|
||||||
# smfi_addheader</a>.
|
|
||||||
# The <code>Milter.ADDHDRS</code> action flag must be set.
|
|
||||||
#
|
|
||||||
# May be called from eom callback only.
|
|
||||||
# @param field the header field name
|
|
||||||
# @param value the header field value
|
|
||||||
# @param idx header field index from the top of the message to insert at
|
|
||||||
# @throws DisabledAction if ADDHDRS is not enabled
|
|
||||||
def addheader(self,field,value,idx=-1):
|
|
||||||
if not self._actions & ADDHDRS: raise DisabledAction("ADDHDRS")
|
|
||||||
return self._ctx.addheader(field,value,idx)
|
|
||||||
|
|
||||||
## Change the value of a mail header field.
|
|
||||||
# Calls <a href="https://www.milter.org/developers/api/smfi_chgheader">
|
|
||||||
# smfi_chgheader</a>.
|
|
||||||
# The <code>Milter.CHGHDRS</code> action flag must be set.
|
|
||||||
#
|
|
||||||
# May be called from eom callback only.
|
|
||||||
# @param field the name of the field to change
|
|
||||||
# @param idx index of the field to change when there are multiple instances
|
|
||||||
# @param value the new value of the field
|
|
||||||
# @throws DisabledAction if CHGHDRS is not enabled
|
|
||||||
def chgheader(self,field,idx,value):
|
|
||||||
if not self._actions & CHGHDRS: raise DisabledAction("CHGHDRS")
|
|
||||||
return self._ctx.chgheader(field,idx,value)
|
|
||||||
|
|
||||||
## Add a recipient to the message.
|
|
||||||
# Calls <a href="https://www.milter.org/developers/api/smfi_addrcpt">
|
|
||||||
# smfi_addrcpt</a>.
|
|
||||||
# If no corresponding mail header is added, this is like a Bcc.
|
|
||||||
# The syntax of the recipient is the same as used in the SMTP
|
|
||||||
# RCPT TO command (and as delivered to the envrcpt callback), for example
|
|
||||||
# "self.addrcpt('<foo@example.com>')".
|
|
||||||
# The <code>Milter.ADDRCPT</code> action flag must be set.
|
|
||||||
# If the optional <code>params</code> argument is used, then
|
|
||||||
# the <code>Milter.ADDRCPT_PAR</code> action flag must be set.
|
|
||||||
#
|
|
||||||
# May be called from eom callback only.
|
|
||||||
# @param rcpt the message recipient
|
|
||||||
# @param params an optional list of ESMTP parameters
|
|
||||||
# @throws DisabledAction if ADDRCPT or ADDRCPT_PAR is not enabled
|
|
||||||
def addrcpt(self,rcpt,params=None):
|
|
||||||
if not self._actions & ADDRCPT: raise DisabledAction("ADDRCPT")
|
|
||||||
if params and not self._actions & ADDRCPT_PAR:
|
|
||||||
raise DisabledAction("ADDRCPT_PAR")
|
|
||||||
return self._ctx.addrcpt(rcpt,params)
|
|
||||||
## Delete a recipient from the message.
|
|
||||||
# Calls <a href="https://www.milter.org/developers/api/smfi_delrcpt">
|
|
||||||
# smfi_delrcpt</a>.
|
|
||||||
# The recipient should match one passed to the envrcpt callback.
|
|
||||||
# The <code>Milter.DELRCPT</code> action flag must be set.
|
|
||||||
#
|
|
||||||
# May be called from eom callback only.
|
|
||||||
# @param rcpt the message recipient to delete
|
|
||||||
# @throws DisabledAction if DELRCPT is not enabled
|
|
||||||
def delrcpt(self,rcpt):
|
|
||||||
if not self._actions & DELRCPT: raise DisabledAction("DELRCPT")
|
|
||||||
return self._ctx.delrcpt(rcpt)
|
|
||||||
|
|
||||||
## Replace the message body.
|
|
||||||
# Calls <a href="https://www.milter.org/developers/api/smfi_replacebody">
|
|
||||||
# smfi_replacebody</a>.
|
|
||||||
# The entire message body must be replaced.
|
|
||||||
# Call repeatedly with blocks of data until the entire body is transferred.
|
|
||||||
# The <code>Milter.MODBODY</code> action flag must be set.
|
|
||||||
#
|
|
||||||
# May be called from eom callback only.
|
|
||||||
# @param body a chunk of body data
|
|
||||||
# @throws DisabledAction if MODBODY is not enabled
|
|
||||||
def replacebody(self,body):
|
|
||||||
if not self._actions & MODBODY: raise DisabledAction("MODBODY")
|
|
||||||
return self._ctx.replacebody(body)
|
|
||||||
|
|
||||||
## Change the SMTP envelope sender address.
|
|
||||||
# Calls <a href="https://www.milter.org/developers/api/smfi_chgfrom">
|
|
||||||
# smfi_chgfrom</a>.
|
|
||||||
# The syntax of the sender is that same as used in the SMTP
|
|
||||||
# MAIL FROM command (and as delivered to the envfrom callback),
|
|
||||||
# for example <code>self.chgfrom('<bar@example.com>')</code>.
|
|
||||||
# The <code>Milter.CHGFROM</code> action flag must be set.
|
|
||||||
#
|
|
||||||
# May be called from eom callback only.
|
|
||||||
# @since 0.9.1
|
|
||||||
# @param sender the new sender address
|
|
||||||
# @param params an optional list of ESMTP parameters
|
|
||||||
# @throws DisabledAction if CHGFROM is not enabled
|
|
||||||
def chgfrom(self,sender,params=None):
|
|
||||||
if not self._actions & CHGFROM: raise DisabledAction("CHGFROM")
|
|
||||||
return self._ctx.chgfrom(sender,params)
|
|
||||||
|
|
||||||
## Quarantine the message.
|
|
||||||
# Calls <a href="https://www.milter.org/developers/api/smfi_quarantine">
|
|
||||||
# smfi_quarantine</a>.
|
|
||||||
# When quarantined, a message goes into the mailq as if to be delivered,
|
|
||||||
# but delivery is deferred until the message is unquarantined.
|
|
||||||
# The <code>Milter.QUARANTINE</code> action flag must be set.
|
|
||||||
#
|
|
||||||
# May be called from eom callback only.
|
|
||||||
# @param reason a string describing the reason for quarantine
|
|
||||||
# @throws DisabledAction if QUARANTINE is not enabled
|
|
||||||
def quarantine(self,reason):
|
|
||||||
if not self._actions & QUARANTINE: raise DisabledAction("QUARANTINE")
|
|
||||||
return self._ctx.quarantine(reason)
|
|
||||||
|
|
||||||
## Tell the MTA to wait a bit longer.
|
|
||||||
# Calls <a href="https://www.milter.org/developers/api/smfi_progress">
|
|
||||||
# smfi_progress</a>.
|
|
||||||
# Resets timeouts in the MTA that detect a "hung" milter.
|
|
||||||
def progress(self):
|
|
||||||
return self._ctx.progress()
|
|
||||||
|
|
||||||
## A logging but otherwise do nothing Milter base class.
|
|
||||||
# This is included for compatibility with previous versions of pymilter.
|
|
||||||
# The logging callbacks are marked @@noreply.
|
|
||||||
class Milter(Base):
|
|
||||||
"A simple class interface to the milter module."
|
|
||||||
|
|
||||||
## Provide simple logging to sys.stdout
|
|
||||||
def log(self,*msg):
|
def log(self,*msg):
|
||||||
print 'Milter:',
|
print 'Milter:',
|
||||||
for i in msg: print i,
|
for i in msg: print i,
|
||||||
print
|
print
|
||||||
|
|
||||||
@noreply
|
def connect(self,hostname,unused,hostaddr):
|
||||||
def connect(self,hostname,family,hostaddr):
|
|
||||||
"Called for each connection to sendmail."
|
"Called for each connection to sendmail."
|
||||||
self.log("connect from %s at %s" % (hostname,hostaddr))
|
self.log("connect from %s at %s" % (hostname,hostaddr))
|
||||||
return CONTINUE
|
return CONTINUE
|
||||||
|
|
||||||
@noreply
|
|
||||||
def hello(self,hostname):
|
def hello(self,hostname):
|
||||||
"Called after the HELO command."
|
"Called after the HELO command."
|
||||||
self.log("hello from %s" % hostname)
|
self.log("hello from %s" % hostname)
|
||||||
return CONTINUE
|
return CONTINUE
|
||||||
|
|
||||||
@noreply
|
|
||||||
def envfrom(self,f,*str):
|
def envfrom(self,f,*str):
|
||||||
"""Called to begin each message.
|
"""Called to begin each message.
|
||||||
f -> string message sender
|
f -> string message sender
|
||||||
@@ -610,24 +59,25 @@ class Milter(Base):
|
|||||||
self.log("mail from",f,str)
|
self.log("mail from",f,str)
|
||||||
return CONTINUE
|
return CONTINUE
|
||||||
|
|
||||||
@noreply
|
|
||||||
def envrcpt(self,to,*str):
|
def envrcpt(self,to,*str):
|
||||||
"Called for each message recipient."
|
"Called for each message recipient."
|
||||||
self.log("rcpt to",to,str)
|
self.log("rcpt to",to,str)
|
||||||
return CONTINUE
|
return CONTINUE
|
||||||
|
|
||||||
@noreply
|
|
||||||
def header(self,field,value):
|
def header(self,field,value):
|
||||||
"Called for each message header."
|
"Called for each message header."
|
||||||
self.log("%s: %s" % (field,value))
|
self.log("%s: %s" % (field,value))
|
||||||
return CONTINUE
|
return CONTINUE
|
||||||
|
|
||||||
@noreply
|
|
||||||
def eoh(self):
|
def eoh(self):
|
||||||
"Called after all headers are processed."
|
"Called after all headers are processed."
|
||||||
self.log("eoh")
|
self.log("eoh")
|
||||||
return CONTINUE
|
return CONTINUE
|
||||||
|
|
||||||
|
def body(self,unused):
|
||||||
|
"Called to transfer the message body."
|
||||||
|
return CONTINUE
|
||||||
|
|
||||||
def eom(self):
|
def eom(self):
|
||||||
"Called at the end of message."
|
"Called at the end of message."
|
||||||
self.log("eom")
|
self.log("eom")
|
||||||
@@ -643,87 +93,64 @@ class Milter(Base):
|
|||||||
self.log("close")
|
self.log("close")
|
||||||
return CONTINUE
|
return CONTINUE
|
||||||
|
|
||||||
## The milter connection factory
|
# Milter methods which can be invoked from callbacks
|
||||||
# This factory method is called for each connection to create the
|
def getsymval(self,sym):
|
||||||
# python object that tracks the connection. It should return
|
return self.__ctx.getsymval(sym)
|
||||||
# an object derived from Milter.Base.
|
|
||||||
#
|
# If sendmail does not support setmlreply, then only the
|
||||||
# Note that since python is dynamic, this variable can be changed while
|
# first msg line is used.
|
||||||
# the milter is running: for instance, to a new subclass based on a
|
def setreply(self,rcode,xcode=None,msg=None,*ml):
|
||||||
# change in configuration.
|
return self.__ctx.setreply(rcode,xcode,msg,*ml)
|
||||||
|
|
||||||
|
# Milter methods which can only be called from eom callback.
|
||||||
|
def addheader(self,field,value):
|
||||||
|
return self.__ctx.addheader(field,value)
|
||||||
|
|
||||||
|
def chgheader(self,field,idx,value):
|
||||||
|
return self.__ctx.chgheader(field,idx,value)
|
||||||
|
|
||||||
|
def addrcpt(self,rcpt):
|
||||||
|
return self.__ctx.addrcpt(rcpt)
|
||||||
|
|
||||||
|
def delrcpt(self,rcpt):
|
||||||
|
return self.__ctx.delrcpt(rcpt)
|
||||||
|
|
||||||
|
def replacebody(self,body):
|
||||||
|
return self.__ctx.replacebody(body)
|
||||||
|
|
||||||
|
# When quarantined, a message goes into the mailq as if to be delivered,
|
||||||
|
# but delivery is deferred until the message is unquarantined.
|
||||||
|
def quarantine(self,reason):
|
||||||
|
return self.__ctx.quarantine(reason)
|
||||||
|
|
||||||
|
def progress(self):
|
||||||
|
return self.__ctx.progress()
|
||||||
|
|
||||||
factory = Milter
|
factory = Milter
|
||||||
|
|
||||||
## @private
|
def connectcallback(ctx,hostname,family,hostaddr):
|
||||||
# @brief Connect context to connection instance and return enabled callbacks.
|
|
||||||
def negotiate_callback(ctx,opts):
|
|
||||||
m = factory()
|
m = factory()
|
||||||
m._setctx(ctx)
|
m._setctx(ctx)
|
||||||
return m.negotiate(opts)
|
|
||||||
|
|
||||||
## @private
|
|
||||||
# @brief Connect context if needed and invoke connect method.
|
|
||||||
def connect_callback(ctx,hostname,family,hostaddr,nr_mask=P_NR_CONN):
|
|
||||||
m = ctx.getpriv()
|
|
||||||
if not m:
|
|
||||||
# If not already created (because the current MTA doesn't support
|
|
||||||
# xmfi_negotiate), create the connection object.
|
|
||||||
m = factory()
|
|
||||||
m._setctx(ctx)
|
|
||||||
return m.connect(hostname,family,hostaddr)
|
return m.connect(hostname,family,hostaddr)
|
||||||
|
|
||||||
## @private
|
def closecallback(ctx):
|
||||||
# @brief Disconnect milterContext and call close method.
|
|
||||||
def close_callback(ctx):
|
|
||||||
m = ctx.getpriv()
|
m = ctx.getpriv()
|
||||||
if not m: return CONTINUE
|
if not m: return CONTINUE
|
||||||
try:
|
rc = m.close()
|
||||||
rc = m.close()
|
m._setctx(None) # release milterContext
|
||||||
finally:
|
|
||||||
m._setctx(None) # release milterContext
|
|
||||||
return rc
|
return rc
|
||||||
|
|
||||||
## Convert ESMTP parameters with values to a keyword dictionary.
|
|
||||||
# @deprecated You probably want Milter.param2dict instead.
|
|
||||||
def dictfromlist(args):
|
|
||||||
"Convert ESMTP parms with values to keyword dictionary."
|
|
||||||
kw = {}
|
|
||||||
for s in args:
|
|
||||||
pos = s.find('=')
|
|
||||||
if pos > 0:
|
|
||||||
kw[s[:pos].upper()] = s[pos+1:]
|
|
||||||
return kw
|
|
||||||
|
|
||||||
## Convert ESMTP parm list to keyword dictionary.
|
|
||||||
# Params with no value are set to None in the dictionary.
|
|
||||||
# @since 0.9.3
|
|
||||||
# @param str list of param strings of the form "NAME" or "NAME=VALUE"
|
|
||||||
# @return a dictionary of ESMTP param names and values
|
|
||||||
def param2dict(str):
|
|
||||||
"Convert ESMTP parm list to keyword dictionary."
|
|
||||||
pairs = [x.split('=',1) for x in str]
|
|
||||||
for e in pairs:
|
|
||||||
if len(e) < 2: e.append(None)
|
|
||||||
return dict([(k.upper(),v) for k,v in pairs])
|
|
||||||
|
|
||||||
def envcallback(c,args):
|
def envcallback(c,args):
|
||||||
"""Call function c with ESMTP parms converted to keyword parameters.
|
"""Convert ESMTP parms to keyword parameters.
|
||||||
Can be used in the envfrom and/or envrcpt callbacks to process
|
Can be used in the envfrom and/or envrcpt callbacks to process
|
||||||
ESMTP parameters as python keyword parameters."""
|
ESMTP parameters as python keyword parameters."""
|
||||||
kw = {}
|
kw = {}
|
||||||
pargs = [args[0]]
|
|
||||||
for s in args[1:]:
|
for s in args[1:]:
|
||||||
pos = s.find('=')
|
pos = s.find('=')
|
||||||
if pos > 0:
|
if pos > 0:
|
||||||
kw[s[:pos].upper()] = s[pos+1:]
|
kw[s[:pos]] = s[pos+1:]
|
||||||
else:
|
return apply(c,args,kw)
|
||||||
pargs.append(s)
|
|
||||||
return c(*pargs,**kw)
|
|
||||||
|
|
||||||
## Run the %milter.
|
|
||||||
# @param name the name of the %milter known to the MTA
|
|
||||||
# @param socketname the socket to be passed to milter.setconn()
|
|
||||||
# @param timeout the time in secs the MTA should wait for a response before
|
|
||||||
# considering this %milter dead
|
|
||||||
def runmilter(name,socketname,timeout = 0):
|
def runmilter(name,socketname,timeout = 0):
|
||||||
# This bit is here on the assumption that you will be starting this filter
|
# This bit is here on the assumption that you will be starting this filter
|
||||||
# before sendmail. If sendmail is not running and the socket already exists,
|
# before sendmail. If sendmail is not running and the socket already exists,
|
||||||
@@ -742,40 +169,31 @@ def runmilter(name,socketname,timeout = 0):
|
|||||||
print "Removing %s" % fname
|
print "Removing %s" % fname
|
||||||
try:
|
try:
|
||||||
os.unlink(fname)
|
os.unlink(fname)
|
||||||
except os.error, x:
|
except:
|
||||||
import errno
|
pass
|
||||||
if x.errno != errno.ENOENT:
|
|
||||||
raise milter.error(x)
|
|
||||||
|
|
||||||
# The default flags set include everything
|
# The default flags set include everything
|
||||||
# milter.set_flags(milter.ADDHDRS)
|
# milter.set_flags(milter.ADDHDRS)
|
||||||
milter.set_connect_callback(connect_callback)
|
milter.set_connect_callback(connectcallback)
|
||||||
milter.set_helo_callback(lambda ctx, host: ctx.getpriv().hello(host))
|
milter.set_helo_callback(lambda ctx, host: ctx.getpriv().hello(host))
|
||||||
# For envfrom and envrcpt, we would like to convert ESMTP parms to keyword
|
milter.set_envfrom_callback(lambda ctx,*str:
|
||||||
# parms, but then all existing users would have to include **kw to accept
|
ctx.getpriv().envfrom(*str))
|
||||||
# arbitrary keywords without crashing. We do provide envcallback and
|
# envcallback(ctx.getpriv().envfrom,str))
|
||||||
# dictfromlist to make parsing the ESMTP args convenient.
|
milter.set_envrcpt_callback(lambda ctx,*str:
|
||||||
milter.set_envfrom_callback(lambda ctx,*str: ctx.getpriv().envfrom(*str))
|
ctx.getpriv().envrcpt(*str))
|
||||||
milter.set_envrcpt_callback(lambda ctx,*str: ctx.getpriv().envrcpt(*str))
|
# envcallback(ctx.getpriv().envrcpt,str))
|
||||||
milter.set_header_callback(lambda ctx,fld,val: ctx.getpriv().header(fld,val))
|
milter.set_header_callback(lambda ctx,fld,val:
|
||||||
|
ctx.getpriv().header(fld,val))
|
||||||
milter.set_eoh_callback(lambda ctx: ctx.getpriv().eoh())
|
milter.set_eoh_callback(lambda ctx: ctx.getpriv().eoh())
|
||||||
milter.set_body_callback(lambda ctx,chunk: ctx.getpriv().body(chunk))
|
milter.set_body_callback(lambda ctx,chunk: ctx.getpriv().body(chunk))
|
||||||
milter.set_eom_callback(lambda ctx: ctx.getpriv().eom())
|
milter.set_eom_callback(lambda ctx: ctx.getpriv().eom())
|
||||||
milter.set_abort_callback(lambda ctx: ctx.getpriv().abort())
|
milter.set_abort_callback(lambda ctx: ctx.getpriv().abort())
|
||||||
milter.set_close_callback(close_callback)
|
milter.set_close_callback(closecallback)
|
||||||
|
|
||||||
milter.setconn(socketname)
|
milter.setconn(socketname)
|
||||||
if timeout > 0: milter.settimeout(timeout)
|
if timeout > 0: milter.settimeout(timeout)
|
||||||
# disable negotiate callback if runtime version < (1,0,1)
|
|
||||||
ncb = negotiate_callback
|
|
||||||
if milter.getversion() < (1,0,1):
|
|
||||||
ncb = None
|
|
||||||
# The name *must* match the X line in sendmail.cf (supposedly)
|
# The name *must* match the X line in sendmail.cf (supposedly)
|
||||||
milter.register(name,
|
milter.register(name)
|
||||||
data=lambda ctx: ctx.getpriv().data(),
|
|
||||||
unknown=lambda ctx,cmd: ctx.getpriv().unknown(cmd),
|
|
||||||
negotiate=ncb
|
|
||||||
)
|
|
||||||
start_seq = _seq
|
start_seq = _seq
|
||||||
try:
|
try:
|
||||||
milter.main()
|
milter.main()
|
||||||
@@ -785,10 +203,6 @@ def runmilter(name,socketname,timeout = 0):
|
|||||||
raise milter.error("out of thread resources")
|
raise milter.error("out of thread resources")
|
||||||
|
|
||||||
__all__ = globals().copy()
|
__all__ = globals().copy()
|
||||||
for priv in ('os','milter','thread','factory','_seq','_seq_lock','__version__'):
|
for priv in ('os','milter','thread','factory','_seq','_seq_lock'):
|
||||||
del __all__[priv]
|
del __all__[priv]
|
||||||
__all__ = __all__.keys()
|
__all__ = __all__.keys()
|
||||||
|
|
||||||
## @example milter-template.py
|
|
||||||
## @example milter-nomix.py
|
|
||||||
#
|
|
||||||
|
|||||||
-161
@@ -1,161 +0,0 @@
|
|||||||
# Email address list with expiration
|
|
||||||
#
|
|
||||||
# This class acts like a map. Entries with a value of None are persistent,
|
|
||||||
# but disappear after a time limit. This is useful for automatic whitelists
|
|
||||||
# and blacklists with expiration. The persistent store is a simple ascii
|
|
||||||
# file with sender and timestamp on each line. Entries can be appended
|
|
||||||
# to the store, and will be picked up the next time it is loaded.
|
|
||||||
#
|
|
||||||
# Entries with other values are not persistent. This is used to hold failed
|
|
||||||
# CBV results.
|
|
||||||
#
|
|
||||||
# $Log$
|
|
||||||
# Revision 1.9 2008/05/08 21:35:57 customdesigned
|
|
||||||
# Allow explicitly whitelisted email from banned_users.
|
|
||||||
#
|
|
||||||
# Revision 1.8 2007/09/03 16:18:45 customdesigned
|
|
||||||
# Delete unparseable timestamps when loading address cache. These have
|
|
||||||
# arisen because of failure to parse MAIL FROM properly. Will have to
|
|
||||||
# tighten up MAIL FROM parsing to match RFC.
|
|
||||||
#
|
|
||||||
# Revision 1.7 2007/01/25 22:47:26 customdesigned
|
|
||||||
# Persist blacklisting from delayed DSNs.
|
|
||||||
#
|
|
||||||
# Revision 1.6 2007/01/19 23:31:38 customdesigned
|
|
||||||
# Move parse_header to Milter.utils.
|
|
||||||
# Test case for delayed DSN parsing.
|
|
||||||
# Fix plock when source missing or cannot set owner/group.
|
|
||||||
#
|
|
||||||
# Revision 1.5 2007/01/11 19:59:40 customdesigned
|
|
||||||
# Purge old entries in auto_whitelist and send_dsn logs.
|
|
||||||
#
|
|
||||||
# Revision 1.4 2007/01/11 04:31:26 customdesigned
|
|
||||||
# Negative feedback for bad headers. Purge cache logs on startup.
|
|
||||||
#
|
|
||||||
# Revision 1.3 2007/01/08 23:20:54 customdesigned
|
|
||||||
# Get user feedback.
|
|
||||||
#
|
|
||||||
# Revision 1.2 2007/01/05 23:33:55 customdesigned
|
|
||||||
# Make blacklist an AddrCache
|
|
||||||
#
|
|
||||||
# Revision 1.1 2007/01/05 21:25:40 customdesigned
|
|
||||||
# Move AddrCache to Milter package.
|
|
||||||
#
|
|
||||||
|
|
||||||
# Author: Stuart D. Gathman <stuart@bmsi.com>
|
|
||||||
# Copyright 2001,2002,2003,2004,2005 Business Management Systems, Inc.
|
|
||||||
# This code is under the GNU General Public License. See COPYING for details.
|
|
||||||
|
|
||||||
import time
|
|
||||||
from plock import PLock
|
|
||||||
|
|
||||||
class AddrCache(object):
|
|
||||||
time_format = '%Y%b%d %H:%M:%S %Z'
|
|
||||||
|
|
||||||
def __init__(self,renew=7,fname=None):
|
|
||||||
self.age = renew
|
|
||||||
self.cache = {}
|
|
||||||
self.fname = fname
|
|
||||||
|
|
||||||
def load(self,fname,age=0):
|
|
||||||
"Load address cache from persistent store."
|
|
||||||
if not age:
|
|
||||||
age = self.age
|
|
||||||
self.fname = fname
|
|
||||||
cache = {}
|
|
||||||
self.cache = cache
|
|
||||||
now = time.time()
|
|
||||||
lock = PLock(self.fname)
|
|
||||||
wfp = lock.lock()
|
|
||||||
changed = False
|
|
||||||
try:
|
|
||||||
too_old = now - age*24*60*60 # max age in days
|
|
||||||
try:
|
|
||||||
fp = open(self.fname)
|
|
||||||
except OSError:
|
|
||||||
fp = ()
|
|
||||||
for ln in fp:
|
|
||||||
try:
|
|
||||||
rcpt,ts = ln.strip().split(None,1)
|
|
||||||
try:
|
|
||||||
l = time.strptime(ts,AddrCache.time_format)
|
|
||||||
t = time.mktime(l)
|
|
||||||
if t < too_old:
|
|
||||||
changed = True
|
|
||||||
continue
|
|
||||||
cache[rcpt.lower()] = (t,None)
|
|
||||||
except: # unparsable timestamp - likely garbage
|
|
||||||
changed = True
|
|
||||||
continue
|
|
||||||
except: # manual entry (no timestamp)
|
|
||||||
cache[ln.strip().lower()] = (now,None)
|
|
||||||
wfp.write(ln)
|
|
||||||
if changed:
|
|
||||||
lock.commit(self.fname+'.old')
|
|
||||||
else:
|
|
||||||
lock.unlock()
|
|
||||||
except IOError:
|
|
||||||
lock.unlock()
|
|
||||||
|
|
||||||
def has_precise_key(self,sender):
|
|
||||||
"""True if precise sender is cached and has not expired. Don't
|
|
||||||
try looking up wildcard entries.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
lsender = sender and sender.lower()
|
|
||||||
ts,res = self.cache[lsender]
|
|
||||||
too_old = time.time() - self.age*24*60*60 # max age in days
|
|
||||||
if not ts or ts > too_old:
|
|
||||||
return True
|
|
||||||
del self.cache[lsender]
|
|
||||||
except KeyError: pass
|
|
||||||
return False
|
|
||||||
|
|
||||||
def has_key(self,sender):
|
|
||||||
"True if sender is cached and has not expired."
|
|
||||||
if self.has_precise_key(sender):
|
|
||||||
return True
|
|
||||||
try:
|
|
||||||
user,host = sender.split('@',1)
|
|
||||||
return self.has_precise_key(host)
|
|
||||||
except: pass
|
|
||||||
return False
|
|
||||||
|
|
||||||
__contains__ = has_key
|
|
||||||
|
|
||||||
def __getitem__(self,sender):
|
|
||||||
try:
|
|
||||||
lsender = sender.lower()
|
|
||||||
ts,res = self.cache[lsender]
|
|
||||||
too_old = time.time() - self.age*24*60*60 # max age in days
|
|
||||||
if not ts or ts > too_old:
|
|
||||||
return res
|
|
||||||
del self.cache[lsender]
|
|
||||||
raise KeyError, sender
|
|
||||||
except KeyError,x:
|
|
||||||
try:
|
|
||||||
user,host = sender.split('@',1)
|
|
||||||
return self.__getitem__(host)
|
|
||||||
except ValueError:
|
|
||||||
raise x
|
|
||||||
|
|
||||||
def addperm(self,sender,res=None):
|
|
||||||
"Add a permanent sender."
|
|
||||||
lsender = sender.lower()
|
|
||||||
if self.has_key(lsender):
|
|
||||||
ts,res = self.cache[lsender]
|
|
||||||
if not ts: return # already permanent
|
|
||||||
self.cache[lsender] = (None,res)
|
|
||||||
if not res:
|
|
||||||
print >>open(self.fname,'a'),sender
|
|
||||||
|
|
||||||
def __setitem__(self,sender,res):
|
|
||||||
lsender = sender.lower()
|
|
||||||
now = time.time()
|
|
||||||
self.cache[lsender] = (now,res)
|
|
||||||
if not res and self.fname:
|
|
||||||
s = time.strftime(AddrCache.time_format,time.localtime(now))
|
|
||||||
print >>open(self.fname,'a'),sender,s # log refreshed senders
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return len(self.cache)
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
from ConfigParser import ConfigParser
|
|
||||||
import os.path
|
|
||||||
|
|
||||||
class MilterConfigParser(ConfigParser):
|
|
||||||
|
|
||||||
def __init__(self,defaults={}):
|
|
||||||
ConfigParser.__init__(self)
|
|
||||||
self.defaults = defaults
|
|
||||||
|
|
||||||
# The defaults provided by ConfigParser show up in all sections,
|
|
||||||
# which screws up iterating over all options in a section.
|
|
||||||
# Worse, passing "defaults" with vars= overrides the config file!
|
|
||||||
# So we roll our own defaults.
|
|
||||||
def get(self,sect,opt):
|
|
||||||
if not self.has_option(sect,opt) and opt in self.defaults:
|
|
||||||
return self.defaults[opt]
|
|
||||||
return ConfigParser.get(self,sect,opt)
|
|
||||||
|
|
||||||
def getlist(self,sect,opt):
|
|
||||||
if self.has_option(sect,opt):
|
|
||||||
return [q.strip() for q in self.get(sect,opt).split(',')]
|
|
||||||
return []
|
|
||||||
|
|
||||||
def getaddrset(self,sect,opt,dir=''):
|
|
||||||
if not self.has_option(sect,opt):
|
|
||||||
return {}
|
|
||||||
s = self.get(sect,opt)
|
|
||||||
d = {}
|
|
||||||
for q in s.split(','):
|
|
||||||
q = q.strip()
|
|
||||||
if q.startswith('file:'):
|
|
||||||
domain = q[5:].lower()
|
|
||||||
fname = os.path.join(dir,domain)
|
|
||||||
d[domain] = d.setdefault(domain,[]) + open(fname,'r').read().split()
|
|
||||||
else:
|
|
||||||
user,domain = q.split('@')
|
|
||||||
d.setdefault(domain.lower(),[]).append(user)
|
|
||||||
return d
|
|
||||||
|
|
||||||
def getaddrdict(self,sect,opt,dir=''):
|
|
||||||
if not self.has_option(sect,opt):
|
|
||||||
return {}
|
|
||||||
d = {}
|
|
||||||
for q in self.get(sect,opt).split(','):
|
|
||||||
q = q.strip()
|
|
||||||
if self.has_option(sect,q):
|
|
||||||
l = self.get(sect,q)
|
|
||||||
for addr in l.split(','):
|
|
||||||
addr = addr.strip()
|
|
||||||
if addr.startswith('file:'):
|
|
||||||
fname = os.path.join(dir,addr[5:])
|
|
||||||
for a in open(fname,'r').read().split():
|
|
||||||
d[a] = q
|
|
||||||
else:
|
|
||||||
d[addr] = q
|
|
||||||
return d
|
|
||||||
|
|
||||||
def getdefault(self,sect,opt,default=None):
|
|
||||||
if self.has_option(sect,opt):
|
|
||||||
return self.get(sect,opt)
|
|
||||||
return default
|
|
||||||
|
|
||||||
def getintdefault(self,sect,opt,default=None):
|
|
||||||
if self.has_option(sect,opt):
|
|
||||||
return self.getint(sect,opt)
|
|
||||||
return default
|
|
||||||
-123
@@ -1,123 +0,0 @@
|
|||||||
## @package Milter.dns
|
|
||||||
# Provide a higher level interface to pydns.
|
|
||||||
|
|
||||||
import DNS
|
|
||||||
from DNS import DNSError
|
|
||||||
|
|
||||||
MAX_CNAME = 10
|
|
||||||
|
|
||||||
## Lookup DNS records by label and RR type.
|
|
||||||
# The response can include records of other types that the DNS
|
|
||||||
# server thinks we might need.
|
|
||||||
# @param name the DNS label to lookup
|
|
||||||
# @param qtype the name of the DNS RR type to lookup
|
|
||||||
# @return a list of ((name,type),data) tuples
|
|
||||||
def DNSLookup(name, qtype):
|
|
||||||
try:
|
|
||||||
# To be thread safe, we create a fresh DnsRequest with
|
|
||||||
# each call. It would be more efficient to reuse
|
|
||||||
# a req object stored in a Session.
|
|
||||||
req = DNS.DnsRequest(name, qtype=qtype)
|
|
||||||
resp = req.req()
|
|
||||||
#resp.show()
|
|
||||||
# key k: ('wayforward.net', 'A'), value v
|
|
||||||
# FIXME: pydns returns AAAA RR as 16 byte binary string, but
|
|
||||||
# A RR as dotted quad. For consistency, this driver should
|
|
||||||
# return both as binary string.
|
|
||||||
return [((a['name'], a['typename']), a['data']) for a in resp.answers]
|
|
||||||
except IOError, x:
|
|
||||||
raise DNSError, str(x)
|
|
||||||
|
|
||||||
class Session(object):
|
|
||||||
"""A Session object has a simple cache with no TTL that is valid
|
|
||||||
for a single "session", for example an SMTP conversation."""
|
|
||||||
def __init__(self):
|
|
||||||
self.cache = {}
|
|
||||||
|
|
||||||
## Additional DNS RRs we can safely cache.
|
|
||||||
# We have to be careful which additional DNS RRs we cache. For
|
|
||||||
# instance, PTR records are controlled by the connecting IP, and they
|
|
||||||
# could poison our local cache with bogus A and MX records.
|
|
||||||
# Each entry is a tuple of (query_type,rr_type). So for instance,
|
|
||||||
# the entry ('MX','A') says it is safe (for milter purposes) to cache
|
|
||||||
# any 'A' RRs found in an 'MX' query.
|
|
||||||
SAFE2CACHE = frozenset((
|
|
||||||
('MX','MX'), ('MX','A'),
|
|
||||||
('CNAME','CNAME'), ('CNAME','A'),
|
|
||||||
('A','A'),
|
|
||||||
('AAAA','AAAA'),
|
|
||||||
('PTR','PTR'),
|
|
||||||
('NS','NS'), ('NS','A'),
|
|
||||||
('TXT','TXT'),
|
|
||||||
('SPF','SPF')
|
|
||||||
))
|
|
||||||
|
|
||||||
## Cached DNS lookup.
|
|
||||||
# @param name the DNS label to query
|
|
||||||
# @param qtype the query type, e.g. 'A'
|
|
||||||
# @param cnames tracks CNAMES already followed in recursive calls
|
|
||||||
def dns(self, name, qtype, cnames=None):
|
|
||||||
"""DNS query.
|
|
||||||
|
|
||||||
If the result is in cache, return that. Otherwise pull the
|
|
||||||
result from DNS, and cache ALL answers, so additional info
|
|
||||||
is available for further queries later.
|
|
||||||
|
|
||||||
CNAMEs are followed.
|
|
||||||
|
|
||||||
If there is no data, [] is returned.
|
|
||||||
|
|
||||||
pre: qtype in ['A', 'AAAA', 'MX', 'PTR', 'TXT', 'SPF']
|
|
||||||
post: isinstance(__return__, types.ListType)
|
|
||||||
"""
|
|
||||||
if name.endswith('.'): name = name[:-1]
|
|
||||||
if not reduce(lambda x,y:x and 0 < len(y) < 64, name.split('.'),True):
|
|
||||||
return [] # invalid DNS name (too long or empty)
|
|
||||||
result = self.cache.get( (name, qtype) )
|
|
||||||
cname = None
|
|
||||||
if result: return result
|
|
||||||
cnamek = (name,'CNAME')
|
|
||||||
cname = self.cache.get( cnamek )
|
|
||||||
|
|
||||||
if cname:
|
|
||||||
cname = cname[0]
|
|
||||||
else:
|
|
||||||
safe2cache = Session.SAFE2CACHE
|
|
||||||
for k, v in DNSLookup(name, qtype):
|
|
||||||
if k == cnamek:
|
|
||||||
cname = v
|
|
||||||
if k[1] == 'CNAME' or (qtype,k[1]) in safe2cache:
|
|
||||||
self.cache.setdefault(k, []).append(v)
|
|
||||||
result = self.cache.get( (name, qtype), [])
|
|
||||||
if not result and cname:
|
|
||||||
if not cnames:
|
|
||||||
cnames = {}
|
|
||||||
elif len(cnames) >= MAX_CNAME:
|
|
||||||
#return result # if too many == NX_DOMAIN
|
|
||||||
raise DNSError('Length of CNAME chain exceeds %d' % MAX_CNAME)
|
|
||||||
cnames[name] = cname
|
|
||||||
if cname in cnames:
|
|
||||||
raise DNSError('CNAME loop')
|
|
||||||
result = self.dns(cname, qtype, cnames=cnames)
|
|
||||||
if result:
|
|
||||||
self.cache[(name,qtype)] = result
|
|
||||||
return result
|
|
||||||
|
|
||||||
def dns_txt(self, domainname, enc='ascii'):
|
|
||||||
"Get a list of TXT records for a domain name."
|
|
||||||
if domainname:
|
|
||||||
try:
|
|
||||||
return [''.join(s.decode(enc) for s in a)
|
|
||||||
for a in self.dns(domainname, 'TXT')]
|
|
||||||
except UnicodeEncodeError:
|
|
||||||
raise DNSError('Non-ascii character in SPF TXT record.')
|
|
||||||
return []
|
|
||||||
|
|
||||||
DNS.DiscoverNameServers()
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
import sys
|
|
||||||
s = Session()
|
|
||||||
for n,t in zip(*[iter(sys.argv[1:])]*2):
|
|
||||||
print n,t
|
|
||||||
print s.dns(n,t)
|
|
||||||
+133
-200
@@ -1,235 +1,168 @@
|
|||||||
# Author: Stuart D. Gathman <stuart@bmsi.com>
|
|
||||||
# Copyright 2005 Business Management Systems, Inc.
|
|
||||||
# This code is under the GNU General Public License. See COPYING for details.
|
|
||||||
|
|
||||||
# Send DSNs, do call back verification,
|
|
||||||
# and generate DSN messages from a template
|
|
||||||
# $Log$
|
|
||||||
# Revision 1.22 2011/03/18 20:41:31 customdesigned
|
|
||||||
# Python2.6 SMTP.close() fails when instance never connected.
|
|
||||||
#
|
|
||||||
# Revision 1.21 2011/03/03 05:11:58 customdesigned
|
|
||||||
# Release 0.9.4
|
|
||||||
#
|
|
||||||
# Revision 1.20 2010/10/11 00:29:47 customdesigned
|
|
||||||
# Handle multiple recipients. For CBV or auto whitelist of multiple emails.
|
|
||||||
#
|
|
||||||
# Revision 1.19 2009/07/02 19:41:12 customdesigned
|
|
||||||
# Handle @ in localpart.
|
|
||||||
#
|
|
||||||
# Revision 1.18 2009/06/10 18:01:59 customdesigned
|
|
||||||
# Doxygen updates
|
|
||||||
#
|
|
||||||
# Revision 1.17 2009/05/20 20:08:44 customdesigned
|
|
||||||
# Support non-DSN CBV (non-empty MAIL FROM)
|
|
||||||
#
|
|
||||||
# Revision 1.16 2007/09/25 01:24:59 customdesigned
|
|
||||||
# Allow arbitrary object, not just spf.query like, to provide data for create_msg
|
|
||||||
#
|
|
||||||
# Revision 1.15 2007/09/24 20:13:26 customdesigned
|
|
||||||
# Remove explicit spf dependency.
|
|
||||||
#
|
|
||||||
# Revision 1.14 2007/03/03 18:19:40 customdesigned
|
|
||||||
# Handle DNS error sending DSN.
|
|
||||||
#
|
|
||||||
# Revision 1.13 2007/01/04 18:01:11 customdesigned
|
|
||||||
# Do plain CBV when template missing.
|
|
||||||
#
|
|
||||||
# Revision 1.12 2006/07/26 16:37:35 customdesigned
|
|
||||||
# Support timeout.
|
|
||||||
#
|
|
||||||
# Revision 1.11 2006/06/21 21:07:11 customdesigned
|
|
||||||
# Include header fields in DSN template.
|
|
||||||
#
|
|
||||||
# Revision 1.10 2006/05/24 20:56:35 customdesigned
|
|
||||||
# Remove default templates. Scrub test.
|
|
||||||
#
|
|
||||||
## @package Milter.dsn
|
|
||||||
# Support DSNs and CallBackValidations (CBV).
|
|
||||||
#
|
|
||||||
# A Delivery Status Notification (bounce) is sent to the envelope
|
|
||||||
# sender (original MAIL FROM) with a null MAIL FROM (<>) to notify the
|
|
||||||
# original sender # of delays or problems with delivery. A Callback Validation
|
|
||||||
# starts the DSN process, but stops before issuing the DATA command. The
|
|
||||||
# purpose is to check whether the envelope recipient is accepted (and is
|
|
||||||
# therefore a valid email). The null MAIL FROM tells the remote
|
|
||||||
# MTA to never reply according to RFC2821 (but some braindead MTAs
|
|
||||||
# reply anyway, of course).
|
|
||||||
#
|
|
||||||
# Milters should cache CBV results and should avoid sending DSNs
|
|
||||||
# unless the sender is authenticated somehow (e.g. SPF Pass). However,
|
|
||||||
# when email is quarantined, and is not known to be a forgery, sending a DSN
|
|
||||||
# is better than silently disappearing, and a DSN is better than sending
|
|
||||||
# a normal message as notification - because MAIL FROM signing schemes
|
|
||||||
# can reject bounces of forged emails. Whatever you do, don't copy those
|
|
||||||
# assinine commercial filters that send a normal message to notify you
|
|
||||||
# that some virus is forging your email.
|
|
||||||
#
|
|
||||||
# <b>DSNs should *only* be sent to MAIL FROM addresses.</b> Never send
|
|
||||||
# a DSN or use a null MAIL FROM with an email address obtained from
|
|
||||||
# anywhere else.
|
|
||||||
#
|
|
||||||
import smtplib
|
import smtplib
|
||||||
|
import spf
|
||||||
import socket
|
import socket
|
||||||
from email.Message import Message
|
from email.Message import Message
|
||||||
import Milter
|
|
||||||
import time
|
|
||||||
import dns
|
|
||||||
|
|
||||||
## Send DSN.
|
nospf_msg = """This is an automatically generated Delivery Status Notification.
|
||||||
# Try the published MX names in order, rejecting obviously bogus entries
|
|
||||||
# (like <code>localhost</code>).
|
THIS IS A WARNING MESSAGE ONLY.
|
||||||
# @param mailfrom the original sender we are notifying or validating
|
|
||||||
# @param receiver the HELO name of the MTA we are sending the DSN on behalf of.
|
YOU DO *NOT* NEED TO RESEND YOUR MESSAGE.
|
||||||
# Be sure to send from an IP that matches the HELO.
|
|
||||||
# @param msg the DSN message in RFC2822 format, or None for CBV.
|
Delivery to the following recipients has been delayed.
|
||||||
# @param timeout total seconds to wait for a response from an MX
|
|
||||||
# @param session Milter.dns.Session object from current incoming mail
|
%(rcpt)s
|
||||||
# session to reuse its cache, or None to create a fresh one.
|
|
||||||
# @param ourfrom set to a valid email to send a normal notification from, or
|
Subject: %(subject)s
|
||||||
# to validate emails not obtained from MAIL FROM.
|
|
||||||
# @return None on success or (status_code,msg) on failure.
|
Someone at IP address %(connectip)s sent an email claiming
|
||||||
def send_dsn(mailfrom,receiver,msg=None,timeout=600,session=None,ourfrom=''):
|
to be from %(sender)s.
|
||||||
"""Send DSN. If msg is None, do callback verification.
|
|
||||||
Mailfrom is original sender we are sending DSN or CBV to.
|
If that wasn't you, then your domain, %(sender_domain)s,
|
||||||
Receiver is the MTA sending the DSN.
|
was forged - i.e. used without your knowlege or authorization by
|
||||||
Return None for success or (code,msg) for failure."""
|
someone attempting to steal your mail identity. This is a very
|
||||||
user,domain = mailfrom.rsplit('@',1)
|
serious problem, and you need to provide authentication for your
|
||||||
if not session: session = dns.Session()
|
SMTP (email) servers to prevent criminals from forging your
|
||||||
try:
|
domain. The simplest step is usually to publish an SPF record
|
||||||
mxlist = session.dns(domain,'MX')
|
with your Sender Policy.
|
||||||
except dns.DNSError:
|
|
||||||
return (450,'DNS Timeout: %s MX'%domain) # temp error
|
For more information, see: http://spfhelp.net
|
||||||
|
|
||||||
|
I hate to annoy you with a DSN (Delivery Status
|
||||||
|
Notification) from a possibly forged email, but since you
|
||||||
|
have not published a sender policy, there is no other way
|
||||||
|
of bringing this to your attention.
|
||||||
|
|
||||||
|
If it *was* you that sent the email, then your email domain
|
||||||
|
or configuration is in error. If you don't know anything
|
||||||
|
about mail servers, then pass this on to your SMTP (mail)
|
||||||
|
server administrator. We have accepted the email anyway, in
|
||||||
|
case it is important, but we couldn't find anything about
|
||||||
|
the mail submitter at %(connectip)s to distinguish it from a
|
||||||
|
zombie (compromised/infected computer - usually a Windows
|
||||||
|
PC). There was no PTR record for its IP address (PTR names
|
||||||
|
that contain the IP address don't count). RFC2821 requires
|
||||||
|
that your hello name be a FQN (Fully Qualified domain Name,
|
||||||
|
i.e. at least one dot) that resolves to the IP address of
|
||||||
|
the mail sender. In addition, just like for PTR, we don't
|
||||||
|
accept a helo name that contains the IP, since this doesn't
|
||||||
|
help to identify you. The hello name you used,
|
||||||
|
%(heloname)s, was invalid.
|
||||||
|
|
||||||
|
Furthermore, there was no SPF record for the sending domain
|
||||||
|
%(sender_domain)s. We even tried to find its IP in any A or
|
||||||
|
MX records for your domain, but that failed also. We really
|
||||||
|
should reject mail from anonymous mail clients, but in case
|
||||||
|
it is important, we are accepting it anyway.
|
||||||
|
|
||||||
|
We are sending you this message to alert you to the fact that
|
||||||
|
|
||||||
|
Either - Someone is forging your domain.
|
||||||
|
Or - You have problems with your email configuration.
|
||||||
|
Or - Possibly both.
|
||||||
|
|
||||||
|
If you need further assistance, please do not hesitate to
|
||||||
|
contact me again.
|
||||||
|
|
||||||
|
Kind regards,
|
||||||
|
Stuart D. Gathman
|
||||||
|
postmaster@%(receiver)s
|
||||||
|
"""
|
||||||
|
|
||||||
|
softfail_msg = """
|
||||||
|
This is an automatically generated Delivery Status Notification.
|
||||||
|
|
||||||
|
THIS IS A WARNING MESSAGE ONLY.
|
||||||
|
|
||||||
|
YOU DO *NOT* NEED TO RESEND YOUR MESSAGE.
|
||||||
|
|
||||||
|
Delivery to the following recipients has been delayed.
|
||||||
|
|
||||||
|
%(rcpt)s
|
||||||
|
|
||||||
|
Subject: %(subject)s
|
||||||
|
Received-SPF: %(spf_result)s
|
||||||
|
"""
|
||||||
|
|
||||||
|
def send_dsn(mailfrom,receiver,msg=None):
|
||||||
|
"Send DSN. If msg is None, do callback verification."
|
||||||
|
user,domain = mailfrom.split('@')
|
||||||
|
q = spf.query(None,None,None)
|
||||||
|
mxlist = q.dns(domain,'MX')
|
||||||
if not mxlist:
|
if not mxlist:
|
||||||
mxlist = (0,domain), # fallback to A record when no MX
|
mxlist = (0,domain),
|
||||||
else:
|
else:
|
||||||
mxlist.sort()
|
mxlist.sort()
|
||||||
smtp = smtplib.SMTP()
|
smtp = smtplib.SMTP()
|
||||||
toolate = time.time() + timeout
|
|
||||||
for prior,host in mxlist:
|
for prior,host in mxlist:
|
||||||
try:
|
try:
|
||||||
smtp.connect(host)
|
smtp.connect(host)
|
||||||
code,resp = smtp.helo(receiver)
|
code,resp = smtp.helo(receiver)
|
||||||
# some wiley spammers have MX records that resolve to 127.0.0.1
|
# some wiley spammers have MX records that resolve to 127.0.0.1
|
||||||
a = resp.split()
|
if resp.split()[0] == receiver:
|
||||||
if not a:
|
return (553,'Fraudulent MX for %s' % domain)
|
||||||
return (553,'MX for %s has no hostname in banner: %s' % (domain,host))
|
|
||||||
if a[0] == receiver:
|
|
||||||
return (553,'Fraudulent MX for %s: %s' % (domain,host))
|
|
||||||
if not (200 <= code <= 299):
|
if not (200 <= code <= 299):
|
||||||
raise smtplib.SMTPHeloError(code, resp)
|
raise SMTPHeloError(code, resp)
|
||||||
if msg:
|
if msg:
|
||||||
try:
|
try:
|
||||||
smtp.sendmail('<%s>'%ourfrom,mailfrom,msg)
|
smtp.sendmail('<>',mailfrom,msg)
|
||||||
except smtplib.SMTPSenderRefused:
|
except smtplib.SMTPSenderRefused:
|
||||||
# does not accept DSN, try postmaster (at the risk of mail loops)
|
# does not accept DSN, try postmaster (at the risk of mail loops)
|
||||||
smtp.sendmail('<postmaster@%s>'%receiver,mailfrom,msg)
|
smtp.sendmail('<postmaster@%s>'%receiver,mailfrom,msg)
|
||||||
else: # CBV
|
else: # CBV
|
||||||
code,resp = smtp.docmd('MAIL FROM: <%s>'%ourfrom)
|
code,resp = smtp.docmd('MAIL FROM: <>')
|
||||||
if code != 250:
|
if code != 250:
|
||||||
raise smtplib.SMTPSenderRefused(code, resp, '<%s>'%ourfrom)
|
raise SMTPSenderRefused(code, resp, '<>')
|
||||||
if isinstance(mailfrom,basestring):
|
code,resp = smtp.rcpt(mailfrom)
|
||||||
mailfrom = [mailfrom]
|
if code not in (250,251):
|
||||||
badrcpts = {}
|
return (code,resp) # permanent error
|
||||||
for rcpt in mailfrom:
|
smtp.quit()
|
||||||
code,resp = smtp.rcpt(rcpt)
|
|
||||||
if code not in (250,251):
|
|
||||||
badrcpts[rcpt] = (code,resp)# permanent error
|
|
||||||
smtp.quit()
|
|
||||||
if len(badrcpts) == 1:
|
|
||||||
return badrcpts.values()[0] # permanent error
|
|
||||||
if badrcpts:
|
|
||||||
return badrcpts
|
|
||||||
return None # success
|
return None # success
|
||||||
except smtplib.SMTPRecipientsRefused,x:
|
except smtplib.SMTPRecipientsRefused,x:
|
||||||
if len(x.recipients) == 1:
|
return x.recipients[mailfrom] # permanent error
|
||||||
return x.recipients.values()[0] # permanent error
|
|
||||||
return x.recipients
|
|
||||||
except smtplib.SMTPSenderRefused,x:
|
except smtplib.SMTPSenderRefused,x:
|
||||||
return x.args[:2] # does not accept DSN
|
return x # does not accept DSN
|
||||||
except smtplib.SMTPDataError,x:
|
except smtplib.SMTPDataError,x:
|
||||||
return x.args # permanent error
|
return x # permanent error
|
||||||
except smtplib.SMTPException:
|
except smtplib.SMTPException:
|
||||||
pass # any other error, try next MX
|
pass # any other error, try next MX
|
||||||
except socket.error:
|
except socket.error:
|
||||||
pass # MX didn't accept connections, try next one
|
pass # MX didn't accept connections, try next one
|
||||||
except socket.timeout:
|
smtp.close()
|
||||||
pass # MX too slow, try next one
|
|
||||||
if hasattr(smtp,'sock'): smtp.close()
|
|
||||||
if time.time() > toolate:
|
|
||||||
return (450,'No MX response within %f minutes'%(timeout/60.0))
|
|
||||||
return (450,'No MX servers available') # temp error
|
return (450,'No MX servers available') # temp error
|
||||||
|
|
||||||
class Vars: pass
|
def create_msg(q,rcptlist,origmsg):
|
||||||
|
heloname = q.h
|
||||||
# NOTE: Caller can pass an object to create_msg that in a typical milter
|
sender = q.s
|
||||||
# collects things like heloname or sender anyway.
|
connectip = q.i
|
||||||
def create_msg(v,rcptlist=None,origmsg=None,template=None):
|
receiver = q.r
|
||||||
"""Create a DSN message from a template. Template must be '\n' separated.
|
sender_domain = q.o
|
||||||
v - an object whose attributes are used for substitutions. Must
|
rcpt = '\n\t'.join(rcptlist)
|
||||||
have sender and receiver attributes at a minimum.
|
try: subject = origmsg['Subject']
|
||||||
rcptlist - used to set v.rcpt if given
|
except: subject = '(none)'
|
||||||
origmsg - used to set v.subject and v.spf_result if given
|
try:
|
||||||
template - a '\n' separated string with python '%(name)s' substitutions.
|
spf_result = origmsg['Received-SPF']
|
||||||
"""
|
if not spf_result.startswith('softfail'):
|
||||||
if not template:
|
spf_result = None
|
||||||
return None
|
except: spf_result = None
|
||||||
if hasattr(v,'perm_error'):
|
|
||||||
# likely to be an spf.query, try translating for backward compatibility
|
|
||||||
q = v
|
|
||||||
v = Vars()
|
|
||||||
try:
|
|
||||||
v.heloname = q.h
|
|
||||||
v.sender = q.s
|
|
||||||
v.connectip = q.i
|
|
||||||
v.receiver = q.r
|
|
||||||
v.sender_domain = q.o
|
|
||||||
v.result = q.result
|
|
||||||
v.perm_error = q.perm_error
|
|
||||||
except: v = q
|
|
||||||
if rcptlist:
|
|
||||||
v.rcpt = '\n\t'.join(rcptlist)
|
|
||||||
if origmsg:
|
|
||||||
try: v.subject = origmsg['Subject']
|
|
||||||
except: v.subject = '(none)'
|
|
||||||
try:
|
|
||||||
v.spf_result = origmsg['Received-SPF']
|
|
||||||
except: v.spf_result = None
|
|
||||||
|
|
||||||
msg = Message()
|
msg = Message()
|
||||||
|
msg.add_header('To',sender)
|
||||||
msg.add_header('X-Mailer','PyMilter-'+Milter.__version__)
|
msg.add_header('From','postmaster@%s'%receiver)
|
||||||
|
msg.add_header('Auto-Submitted','auto-generated (configuration error)')
|
||||||
msg.set_type('text/plain')
|
msg.set_type('text/plain')
|
||||||
|
if spf_result:
|
||||||
hdrs,body = template.split('\n\n',1)
|
msg.add_header('Subject','SPF softfail (POSSIBLE FORGERY)')
|
||||||
for ln in hdrs.splitlines():
|
msg.set_payload(softfail_msg % locals())
|
||||||
name,val = ln.split(':',1)
|
else:
|
||||||
msg.add_header(name,(val % v.__dict__).strip())
|
msg.add_header('Subject','Critical mail server configuration error')
|
||||||
msg.set_payload(body % v.__dict__)
|
msg.set_payload(nospf_msg % locals())
|
||||||
# add headers if missing from old template
|
|
||||||
if 'to' not in msg:
|
|
||||||
msg.add_header('To',v.sender)
|
|
||||||
if 'from' not in msg:
|
|
||||||
msg.add_header('From','postmaster@%s'%v.receiver)
|
|
||||||
if 'auto-submitted' not in msg:
|
|
||||||
msg.add_header('Auto-Submitted','auto-generated')
|
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
import spf
|
|
||||||
q = spf.query('192.168.9.50',
|
q = spf.query('192.168.9.50',
|
||||||
'SRS0=pmeHL=RH==stuart@example.com',
|
'SRS0=pmeHL=RH=bmsi.com=stuart@bmsi.com',
|
||||||
'red.example.com',receiver='mail.example.com')
|
'bmsred.bmsi.com',receiver='mail.bmsi.com')
|
||||||
q.result = 'softfail'
|
msg = create_msg(q,'charlie@jsconnor.com')
|
||||||
q.perm_error = None
|
#print msg.as_string()
|
||||||
msg = create_msg(q,['charlie@example.com'],None,
|
|
||||||
"""From: postmaster@%(receiver)s
|
|
||||||
To: %(sender)s
|
|
||||||
Subject: Test
|
|
||||||
|
|
||||||
Test DSN template
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
print msg.as_string()
|
|
||||||
# print send_dsn(f,msg.as_string())
|
# print send_dsn(f,msg.as_string())
|
||||||
# print send_dsn(q.s,'mail.example.com',msg.as_string())
|
print send_dsn(q.s,'mail.bmsi.com',msg.as_string())
|
||||||
|
|||||||
+5
-14
@@ -1,9 +1,3 @@
|
|||||||
# Author: Stuart D. Gathman <stuart@bmsi.com>
|
|
||||||
# Copyright 2005 Business Management Systems, Inc.
|
|
||||||
# This code is under the GNU General Public License. See COPYING for details.
|
|
||||||
|
|
||||||
# Heuristically determine whether a domain name is for a dynamic IP.
|
|
||||||
|
|
||||||
# examples we don't yet recognize:
|
# examples we don't yet recognize:
|
||||||
#
|
#
|
||||||
# wiley-268-8196.roadrunner.nf.net at ('205.251.174.46', 4810)
|
# wiley-268-8196.roadrunner.nf.net at ('205.251.174.46', 4810)
|
||||||
@@ -44,20 +38,17 @@ def is_dynip(host,addr):
|
|||||||
True
|
True
|
||||||
>>> is_dynip('[1.2.3.4]','1.2.3.4')
|
>>> is_dynip('[1.2.3.4]','1.2.3.4')
|
||||||
True
|
True
|
||||||
>>> is_dynip('c-71-63-151-151.hsd1.mn.comcast.net','71.63.151.151')
|
|
||||||
True
|
|
||||||
"""
|
"""
|
||||||
if host.startswith('[') and host.endswith(']'):
|
if host.startswith('[') and host.endswith(']'):
|
||||||
return True # no ptr
|
return True
|
||||||
if addr:
|
if addr:
|
||||||
if host.find(addr) >= 0: return True
|
if host.find(addr) >= 0: return True
|
||||||
if addr.find(':') >= 0: return False # IP6
|
|
||||||
a = addr.split('.')
|
a = addr.split('.')
|
||||||
ia = map(int,a)
|
ia = map(int,a)
|
||||||
h = host
|
h = host
|
||||||
m = ip3.findall(host)
|
m = ip3.findall(host)
|
||||||
if m:
|
if m:
|
||||||
g = map(int,m)[:4]
|
g = map(int,m)
|
||||||
ia3 = (ia[1:],ia[:3])
|
ia3 = (ia[1:],ia[:3])
|
||||||
if g[-3:] in ia3: return True
|
if g[-3:] in ia3: return True
|
||||||
if g[0] == ia[3] and g[1:3] == ia[:2]: return True
|
if g[0] == ia[3] and g[1:3] == ia[:2]: return True
|
||||||
@@ -68,8 +59,8 @@ def is_dynip(host,addr):
|
|||||||
if ia[2:] in (g[:2],g[-2:]): return True
|
if ia[2:] in (g[:2],g[-2:]): return True
|
||||||
for m in ip3.finditer(host):
|
for m in ip3.finditer(host):
|
||||||
if int(m.group()) == ia[3]:
|
if int(m.group()) == ia[3]:
|
||||||
h = host[:m.start()] + '<3>' + host[m.end():]
|
h = host[:m.start()] + '<3>' + host[m.end():]
|
||||||
break
|
break
|
||||||
if rehmac.search(h): return True
|
if rehmac.search(h): return True
|
||||||
if host.find(''.join(a[:3])) >= 0: return True
|
if host.find(''.join(a[:3])) >= 0: return True
|
||||||
if host.find(''.join(a[1:])) >= 0: return True
|
if host.find(''.join(a[1:])) >= 0: return True
|
||||||
@@ -86,7 +77,7 @@ if __name__ == '__main__':
|
|||||||
if a[3:5] == ['connect','from']:
|
if a[3:5] == ['connect','from']:
|
||||||
host = a[5]
|
host = a[5]
|
||||||
if host.startswith('[') and host.endswith(']'):
|
if host.startswith('[') and host.endswith(']'):
|
||||||
continue # no PTR
|
continue # no PTR
|
||||||
ip = a[7][2:-2]
|
ip = a[7][2:-2]
|
||||||
if ip in seen: continue
|
if ip in seen: continue
|
||||||
seen.add(ip)
|
seen.add(ip)
|
||||||
|
|||||||
@@ -1,102 +0,0 @@
|
|||||||
import time
|
|
||||||
import shelve
|
|
||||||
import thread
|
|
||||||
import logging
|
|
||||||
import urllib
|
|
||||||
|
|
||||||
log = logging.getLogger('milter.greylist')
|
|
||||||
|
|
||||||
def quoteAddress(s):
|
|
||||||
'''Quote an address so that it's safe to store in the file-system.
|
|
||||||
Address can either be a domain name, or local part.
|
|
||||||
Returns the quoted address.'''
|
|
||||||
|
|
||||||
s = urllib.quote(s, '@_-+~!.%')
|
|
||||||
if s.startswith('.'): s = '%2e' + s[1:]
|
|
||||||
return s
|
|
||||||
|
|
||||||
class Record(object):
|
|
||||||
__slots__ = ( 'firstseen', 'lastseen', 'umis', 'cnt' )
|
|
||||||
|
|
||||||
def __init__(self,timeinc=0):
|
|
||||||
now = time.time() + timeinc
|
|
||||||
self.firstseen = now
|
|
||||||
self.lastseen = now
|
|
||||||
self.cnt = 0
|
|
||||||
self.umis = None
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "Grey[%s:%s:%s:%d]" % (
|
|
||||||
time.ctime(self.firstseen),time.ctime(self.lastseen),
|
|
||||||
self.umis,self.cnt
|
|
||||||
)
|
|
||||||
|
|
||||||
class Greylist(object):
|
|
||||||
|
|
||||||
def __init__(self,dbname,grey_time=10,grey_expire=4,grey_retain=36):
|
|
||||||
self.ignoreLastByte = False
|
|
||||||
self.greylist_time = grey_time * 60 # minutes
|
|
||||||
self.greylist_expire = grey_expire * 3600 # hours
|
|
||||||
self.greylist_retain = grey_retain * 24 * 3600 # days
|
|
||||||
self.dbp = shelve.open(dbname,'c',protocol=2)
|
|
||||||
self.lock = thread.allocate_lock()
|
|
||||||
|
|
||||||
def clean(self,timeinc=0):
|
|
||||||
"Delete records past the retention limit."
|
|
||||||
now = time.time() + timeinc
|
|
||||||
cnt = 0
|
|
||||||
dbp = self.dbp
|
|
||||||
for key, r in dbp.iteritems():
|
|
||||||
#print key,r,time.ctime(now)
|
|
||||||
if now > r.lastseen + self.greylist_retain:
|
|
||||||
self.lock.acquire()
|
|
||||||
try:
|
|
||||||
r = dbp[key]
|
|
||||||
now = time.time() + timeinc
|
|
||||||
if now > r.lastseen + self.greylist_retain:
|
|
||||||
del dbp[key]
|
|
||||||
cnt += 1
|
|
||||||
finally:
|
|
||||||
self.lock.release()
|
|
||||||
return cnt
|
|
||||||
|
|
||||||
def check(self,ip,sender,recipient,timeinc=0):
|
|
||||||
"Return number of allowed messages for greylist triple."
|
|
||||||
sender = quoteAddress(sender)
|
|
||||||
recipient = quoteAddress(recipient)
|
|
||||||
key = ip + ':' + sender + ':' + recipient
|
|
||||||
self.lock.acquire()
|
|
||||||
try:
|
|
||||||
dbp = self.dbp
|
|
||||||
try:
|
|
||||||
r = dbp[key]
|
|
||||||
now = time.time() + timeinc
|
|
||||||
if now > r.lastseen + self.greylist_retain:
|
|
||||||
# expired
|
|
||||||
log.debug('Expired greylist: %s',key)
|
|
||||||
r = Record(timeinc)
|
|
||||||
elif now < r.firstseen + self.greylist_time + 5:
|
|
||||||
# still greylisted
|
|
||||||
log.debug('Early greylist: %s',key)
|
|
||||||
#r = Record(timeinc)
|
|
||||||
r.lastseen = now
|
|
||||||
elif r.cnt or now < r.firstseen + self.greylist_expire:
|
|
||||||
# in greylist window or active
|
|
||||||
r.lastseen = now
|
|
||||||
r.cnt += 1
|
|
||||||
log.debug('Active greylist(%d): %s',r.cnt,key)
|
|
||||||
else:
|
|
||||||
# passed greylist window
|
|
||||||
log.debug('Late greylist: %s',key)
|
|
||||||
r = Record(timeinc)
|
|
||||||
dbp[key] = r
|
|
||||||
except:
|
|
||||||
r = Record(timeinc)
|
|
||||||
dbp[key] = r
|
|
||||||
dbp.sync()
|
|
||||||
finally:
|
|
||||||
self.lock.release()
|
|
||||||
return r.cnt
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
self.dbp.close()
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
import time
|
|
||||||
import logging
|
|
||||||
import urllib
|
|
||||||
import sqlite3
|
|
||||||
import thread
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
log = logging.getLogger('milter.greylist')
|
|
||||||
|
|
||||||
_db_lock = thread.allocate_lock()
|
|
||||||
|
|
||||||
class Greylist(object):
|
|
||||||
|
|
||||||
def __init__(self,dbname,grey_time=10,grey_expire=4,grey_retain=36):
|
|
||||||
self.ignoreLastByte = False
|
|
||||||
self.greylist_time = grey_time * 60 # minutes
|
|
||||||
self.greylist_expire = grey_expire * 3600 # hours
|
|
||||||
self.greylist_retain = grey_retain * 24 * 3600 # days
|
|
||||||
self.conn = sqlite3.connect(dbname)
|
|
||||||
self.conn.row_factory = sqlite3.Row
|
|
||||||
try:
|
|
||||||
self.conn.execute('''create table greylist(
|
|
||||||
ip text , sender text, recipient text,
|
|
||||||
firstseen timestamp, lastseen timestamp, cnt integer, umis text,
|
|
||||||
primary key (ip,sender,recipient))''')
|
|
||||||
except: pass
|
|
||||||
|
|
||||||
def clean(self,timeinc=0):
|
|
||||||
"Delete records past the retention limit."
|
|
||||||
now = time.time() + timeinc - self.greylist_retain
|
|
||||||
cur = self.conn.cursor()
|
|
||||||
try:
|
|
||||||
cur.execute('delete from greylist where lastseen < ?',(now,))
|
|
||||||
cnt = cur.rowcount
|
|
||||||
self.conn.commit()
|
|
||||||
finally: cur.close()
|
|
||||||
return cnt
|
|
||||||
|
|
||||||
def check(self,ip,sender,recipient,timeinc=0):
|
|
||||||
"Return number of allowed messages for greylist triple."
|
|
||||||
_db_lock.acquire()
|
|
||||||
cur = self.conn.execute('begin immediate')
|
|
||||||
try:
|
|
||||||
cur.execute('''select firstseen,lastseen,cnt,umis from greylist where
|
|
||||||
ip=? and sender=? and recipient=?''',(ip,sender,recipient))
|
|
||||||
r = cur.fetchone()
|
|
||||||
now = time.time() + timeinc
|
|
||||||
cnt = 0
|
|
||||||
if not r:
|
|
||||||
cur.execute('''insert into
|
|
||||||
greylist(ip,sender,recipient,firstseen,lastseen,cnt,umis)
|
|
||||||
values(?,?,?,?,?,?,?)''', (ip,sender,recipient,now,now,0,None))
|
|
||||||
elif now > r['lastseen'] + self.greylist_retain:
|
|
||||||
# expired
|
|
||||||
log.debug('Expired greylist: %s:%s:%s',ip,sender,recipient)
|
|
||||||
cur.execute('''update greylist set firstseen=?,lastseen=?,cnt=?,umis=?
|
|
||||||
where ip=? and sender=? and recipient=?''',
|
|
||||||
(now,now,0,None,ip,sender,recipient))
|
|
||||||
elif now < r['firstseen'] + self.greylist_time + 5:
|
|
||||||
# still greylisted
|
|
||||||
log.debug('Early greylist: %s:%s:%s',ip,sender,recipient)
|
|
||||||
#r = Record()
|
|
||||||
cur.execute('''update greylist set lastseen=?
|
|
||||||
where ip=? and sender=? and recipient=?''',
|
|
||||||
(now,ip,sender,recipient))
|
|
||||||
elif r['cnt'] or now < r['firstseen'] + self.greylist_expire:
|
|
||||||
# in greylist window or active
|
|
||||||
cnt = r['cnt'] + 1
|
|
||||||
cur.execute('''update greylist set lastseen=?,cnt=?
|
|
||||||
where ip=? and sender=? and recipient=?''',
|
|
||||||
(now,cnt,ip,sender,recipient))
|
|
||||||
log.debug('Active greylist(%d): %s:%s:%s',cnt,ip,sender,recipient)
|
|
||||||
else:
|
|
||||||
# passed greylist window
|
|
||||||
log.debug('Late greylist: %s:%s:%s',ip,sender,recipient)
|
|
||||||
cur.execute('''update greylist set firstseen=?,lastseen=?,cnt=?,umis=?
|
|
||||||
where ip=? and sender=? and recipient=?''',
|
|
||||||
(now,now,0,None,ip,sender,recipient))
|
|
||||||
self.conn.commit()
|
|
||||||
finally:
|
|
||||||
cur.close()
|
|
||||||
_db_lock.release()
|
|
||||||
return cnt
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
self.conn.close()
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
# Author: Stuart D. Gathman <stuart@bmsi.com>
|
|
||||||
# Copyright 2001 Business Management Systems, Inc.
|
|
||||||
# This code is under the GNU General Public License. See COPYING for details.
|
|
||||||
|
|
||||||
import os
|
|
||||||
from time import sleep
|
|
||||||
|
|
||||||
class PLock(object):
|
|
||||||
"A simple /etc/passwd style lock,update,rename protocol for updating files."
|
|
||||||
def __init__(self,basename):
|
|
||||||
self.basename = basename
|
|
||||||
self.fp = None
|
|
||||||
|
|
||||||
def lock(self,lockname=None,mode=0660,strict_perms=False):
|
|
||||||
"Start an update transaction. Return FILE to write new version."
|
|
||||||
self.unlock()
|
|
||||||
if not lockname:
|
|
||||||
lockname = self.basename + '.lock'
|
|
||||||
self.lockname = lockname
|
|
||||||
try:
|
|
||||||
st = os.stat(self.basename)
|
|
||||||
mode |= st.st_mode
|
|
||||||
except OSError: pass
|
|
||||||
u = os.umask(0002)
|
|
||||||
try:
|
|
||||||
fd = os.open(lockname,os.O_WRONLY+os.O_CREAT+os.O_EXCL,mode)
|
|
||||||
finally:
|
|
||||||
os.umask(u)
|
|
||||||
self.fp = os.fdopen(fd,'w')
|
|
||||||
try:
|
|
||||||
os.chown(self.lockname,-1,st.st_gid)
|
|
||||||
except:
|
|
||||||
if strict_perms:
|
|
||||||
self.unlock()
|
|
||||||
raise
|
|
||||||
return self.fp
|
|
||||||
|
|
||||||
def wlock(self,lockname=None):
|
|
||||||
"Wait until lock is free, then start an update transaction."
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
return self.lock(lockname)
|
|
||||||
except OSError:
|
|
||||||
sleep(2)
|
|
||||||
|
|
||||||
def commit(self,backname=None):
|
|
||||||
"Commit update transaction with optional backup file."
|
|
||||||
if not self.fp:
|
|
||||||
raise IOError,"File not locked"
|
|
||||||
self.fp.close()
|
|
||||||
self.fp = None
|
|
||||||
if backname:
|
|
||||||
try:
|
|
||||||
os.remove(backname)
|
|
||||||
except OSError: pass
|
|
||||||
os.link(self.basename,backname)
|
|
||||||
os.rename(self.lockname,self.basename)
|
|
||||||
|
|
||||||
def unlock(self):
|
|
||||||
"Cancel update transaction."
|
|
||||||
if self.fp:
|
|
||||||
try:
|
|
||||||
self.fp.close()
|
|
||||||
except: pass
|
|
||||||
self.fp = None
|
|
||||||
os.remove(self.lockname)
|
|
||||||
-117
@@ -1,117 +0,0 @@
|
|||||||
"""Pure Python IP6 parsing and formatting
|
|
||||||
|
|
||||||
Copyright (c) 2006 Stuart Gathman <stuart@bmsi.com>
|
|
||||||
|
|
||||||
This module is free software, and you may redistribute it and/or modify
|
|
||||||
it under the same terms as Python itself, so long as this copyright message
|
|
||||||
and disclaimer are retained in their original form.
|
|
||||||
"""
|
|
||||||
import struct
|
|
||||||
#from spf import RE_IP4
|
|
||||||
import re
|
|
||||||
PAT_IP4 = r'\.'.join([r'(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])']*4)
|
|
||||||
RE_IP4 = re.compile(PAT_IP4+'$')
|
|
||||||
|
|
||||||
def inet_ntop(s):
|
|
||||||
"""
|
|
||||||
Convert ip6 address to standard hex notation.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
>>> inet_ntop(struct.pack("!HHHHHHHH",0,0,0,0,0,0xFFFF,0x0102,0x0304))
|
|
||||||
'::FFFF:1.2.3.4'
|
|
||||||
|
|
||||||
>>> inet_ntop(struct.pack("!HHHHHHHH",0x1234,0x5678,0,0,0,0,0x0102,0x0304))
|
|
||||||
'1234:5678::102:304'
|
|
||||||
|
|
||||||
>>> inet_ntop(struct.pack("!HHHHHHHH",0,0,0,0x1234,0x5678,0,0x0102,0x0304))
|
|
||||||
'::1234:5678:0:102:304'
|
|
||||||
|
|
||||||
>>> inet_ntop(struct.pack("!HHHHHHHH",0x1234,0x5678,0,0x0102,0x0304,0,0,0))
|
|
||||||
'1234:5678:0:102:304::'
|
|
||||||
|
|
||||||
>>> inet_ntop(struct.pack("!HHHHHHHH",0,0,0,0,0,0,0,0))
|
|
||||||
'::'
|
|
||||||
"""
|
|
||||||
# convert to 8 words
|
|
||||||
a = struct.unpack("!HHHHHHHH",s)
|
|
||||||
n = (0,0,0,0,0,0,0,0) # null ip6
|
|
||||||
if a == n: return '::'
|
|
||||||
# check for ip4 mapped
|
|
||||||
if a[:5] == (0,0,0,0,0) and a[5] in (0,0xFFFF):
|
|
||||||
ip4 = '.'.join([str(i) for i in struct.unpack("!BBBB",s[12:])])
|
|
||||||
if a[5]:
|
|
||||||
return "::FFFF:" + ip4
|
|
||||||
return "::" + ip4
|
|
||||||
# find index of longest sequence of 0
|
|
||||||
for l in (7,6,5,4,3,2,1):
|
|
||||||
e = n[:l]
|
|
||||||
for i in range(9-l):
|
|
||||||
if a[i:i+l] == e:
|
|
||||||
if i == 0:
|
|
||||||
return ':'+':%x'*(8-l) % a[l:]
|
|
||||||
if i == 8 - l:
|
|
||||||
return '%x:'*(8-l) % a[:-l] + ':'
|
|
||||||
return '%x:'*i % a[:i] + ':%x'*(8-l-i) % a[i+l:]
|
|
||||||
return "%x:%x:%x:%x:%x:%x:%x:%x" % a
|
|
||||||
|
|
||||||
def inet_pton(p):
|
|
||||||
"""
|
|
||||||
Convert ip6 standard hex notation to ip6 address.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
>>> struct.unpack('!HHHHHHHH',inet_pton('::'))
|
|
||||||
(0, 0, 0, 0, 0, 0, 0, 0)
|
|
||||||
|
|
||||||
>>> struct.unpack('!HHHHHHHH',inet_pton('::1234'))
|
|
||||||
(0, 0, 0, 0, 0, 0, 0, 4660)
|
|
||||||
|
|
||||||
>>> struct.unpack('!HHHHHHHH',inet_pton('1234::'))
|
|
||||||
(4660, 0, 0, 0, 0, 0, 0, 0)
|
|
||||||
|
|
||||||
>>> struct.unpack('!HHHHHHHH',inet_pton('1234::5678'))
|
|
||||||
(4660, 0, 0, 0, 0, 0, 0, 22136)
|
|
||||||
|
|
||||||
>>> struct.unpack('!HHHHHHHH',inet_pton('::FFFF:1.2.3.4'))
|
|
||||||
(0, 0, 0, 0, 0, 65535, 258, 772)
|
|
||||||
|
|
||||||
>>> struct.unpack('!HHHHHHHH',inet_pton('1.2.3.4'))
|
|
||||||
(0, 0, 0, 0, 0, 65535, 258, 772)
|
|
||||||
|
|
||||||
>>> try: inet_pton('::1.2.3.4.5')
|
|
||||||
... except ValueError,x: print x
|
|
||||||
::1.2.3.4.5
|
|
||||||
"""
|
|
||||||
if p == '::':
|
|
||||||
return '\0'*16
|
|
||||||
s = p
|
|
||||||
m = RE_IP4.search(s)
|
|
||||||
try:
|
|
||||||
if m:
|
|
||||||
pos = m.start()
|
|
||||||
ip4 = [int(i) for i in s[pos:].split('.')]
|
|
||||||
if not pos:
|
|
||||||
return struct.pack('!QLBBBB',0,65535,*ip4)
|
|
||||||
s = s[:pos]+'%x%02x:%x%02x'%tuple(ip4)
|
|
||||||
a = s.split('::')
|
|
||||||
if len(a) == 2:
|
|
||||||
l,r = a
|
|
||||||
if not l:
|
|
||||||
r = r.split(':')
|
|
||||||
return struct.pack('!HHHHHHHH',
|
|
||||||
*[0]*(8-len(r)) + [int(s,16) for s in r])
|
|
||||||
if not r:
|
|
||||||
l = l.split(':')
|
|
||||||
return struct.pack('!HHHHHHHH',
|
|
||||||
*[int(s,16) for s in l] + [0]*(8-len(l)))
|
|
||||||
l = l.split(':')
|
|
||||||
r = r.split(':')
|
|
||||||
return struct.pack('!HHHHHHHH',
|
|
||||||
*[int(s,16) for s in l] + [0]*(8-len(l)-len(r))
|
|
||||||
+ [int(s,16) for s in r])
|
|
||||||
if len(a) == 1:
|
|
||||||
return struct.pack('!HHHHHHHH',
|
|
||||||
*[int(s,16) for s in a[0].split(':')])
|
|
||||||
except ValueError: pass
|
|
||||||
raise ValueError,p
|
|
||||||
-192
@@ -1,192 +0,0 @@
|
|||||||
## @package Milter.test
|
|
||||||
# A test framework for milters
|
|
||||||
|
|
||||||
import rfc822
|
|
||||||
import StringIO
|
|
||||||
import Milter
|
|
||||||
|
|
||||||
Milter.NOREPLY = Milter.CONTINUE
|
|
||||||
|
|
||||||
## Test mixin for unit testing milter applications.
|
|
||||||
# This mixin overrides many Milter.MilterBase methods
|
|
||||||
# with stub versions that simply record what was done.
|
|
||||||
# @since 0.9.8
|
|
||||||
class TestBase(object):
|
|
||||||
|
|
||||||
def __init__(self,logfile='test/milter.log'):
|
|
||||||
self._protocol = 0
|
|
||||||
self.logfp = open(logfile,"a")
|
|
||||||
## List of recipients deleted
|
|
||||||
self._delrcpt = []
|
|
||||||
## List of recipients added
|
|
||||||
self._addrcpt = []
|
|
||||||
## Macros defined
|
|
||||||
self._macros = { }
|
|
||||||
## The message body.
|
|
||||||
self._body = None
|
|
||||||
## True if the milter replaced the message body.
|
|
||||||
self._bodyreplaced = False
|
|
||||||
## True if the milter changed any headers.
|
|
||||||
self._headerschanged = False
|
|
||||||
## Reply codes and messages set by milter
|
|
||||||
self._reply = None
|
|
||||||
## The rfc822 message object for the current email being fed to the milter.
|
|
||||||
self._msg = None
|
|
||||||
self._symlist = [ None, None, None, None, None, None, None ]
|
|
||||||
|
|
||||||
def log(self,*msg):
|
|
||||||
for i in msg: print >>self.logfp, i,
|
|
||||||
print >>self.logfp
|
|
||||||
|
|
||||||
## Set a macro value.
|
|
||||||
# These are retrieved by the milter with getsymval.
|
|
||||||
# @param name the macro name, as passed to getsymval
|
|
||||||
# @param val the macro value
|
|
||||||
def setsymval(self,name,val):
|
|
||||||
self._macros[name] = val
|
|
||||||
|
|
||||||
def getsymval(self,name):
|
|
||||||
# FIXME: track stage, and use _symlist
|
|
||||||
return self._macros.get(name,'')
|
|
||||||
|
|
||||||
def replacebody(self,chunk):
|
|
||||||
if self._body:
|
|
||||||
self._body.write(chunk)
|
|
||||||
self._bodyreplaced = True
|
|
||||||
else:
|
|
||||||
raise IOError,"replacebody not called from eom()"
|
|
||||||
|
|
||||||
# FIXME: rfc822 indexing does not really reflect the way chg/add header
|
|
||||||
# work for a milter
|
|
||||||
def chgheader(self,field,idx,value):
|
|
||||||
if not self._body:
|
|
||||||
raise IOError,"chgheader not called from eom()"
|
|
||||||
self.log('chgheader: %s[%d]=%s' % (field,idx,value))
|
|
||||||
if value == '':
|
|
||||||
del self._msg[field]
|
|
||||||
else:
|
|
||||||
self._msg[field] = value
|
|
||||||
self._headerschanged = True
|
|
||||||
|
|
||||||
def addheader(self,field,value,idx=-1):
|
|
||||||
if not self._body:
|
|
||||||
raise IOError,"addheader not called from eom()"
|
|
||||||
self.log('addheader: %s=%s' % (field,value))
|
|
||||||
self._msg[field] = value
|
|
||||||
self._headerschanged = True
|
|
||||||
|
|
||||||
def delrcpt(self,rcpt):
|
|
||||||
if not self._body:
|
|
||||||
raise IOError,"delrcpt not called from eom()"
|
|
||||||
self._delrcpt.append(rcpt)
|
|
||||||
|
|
||||||
def addrcpt(self,rcpt):
|
|
||||||
if not self._body:
|
|
||||||
raise IOError,"addrcpt not called from eom()"
|
|
||||||
self._addrcpt.append(rcpt)
|
|
||||||
|
|
||||||
## Save the reply codes and messages in self._reply.
|
|
||||||
def setreply(self,rcode,xcode,*msg):
|
|
||||||
self._reply = (rcode,xcode) + msg
|
|
||||||
|
|
||||||
def setsymlist(self,stage,macros):
|
|
||||||
if not self._actions & SETSYMLIST: raise DisabledAction("SETSYMLIST")
|
|
||||||
# not used yet, but just for grins we save the data
|
|
||||||
a = []
|
|
||||||
for m in macros:
|
|
||||||
try:
|
|
||||||
m = m.encode('utf8')
|
|
||||||
except: pass
|
|
||||||
try:
|
|
||||||
m = m.split(' ')
|
|
||||||
except: pass
|
|
||||||
a += m
|
|
||||||
self._symlist[stage] = set(a)
|
|
||||||
|
|
||||||
## Feed a file like object to the milter. Calls envfrom, envrcpt for
|
|
||||||
# each recipient, header for each header field, body for each body
|
|
||||||
# block, and finally eom. A return code from the milter other than
|
|
||||||
# CONTINUE returns immediately with that return code.
|
|
||||||
#
|
|
||||||
# This is a convenience method, a test could invoke the callbacks
|
|
||||||
# in sequence on its own - and for some complex tests, this may
|
|
||||||
# be necessary.
|
|
||||||
# @param fp the file with rfc2822 message stream
|
|
||||||
# @param sender the MAIL FROM
|
|
||||||
# @param rcpt RCPT TO - additional recipients may follow
|
|
||||||
def feedFile(self,fp,sender="spam@adv.com",rcpt="victim@lamb.com",*rcpts):
|
|
||||||
self._body = None
|
|
||||||
self._bodyreplaced = False
|
|
||||||
self._headerschanged = False
|
|
||||||
self._reply = None
|
|
||||||
msg = rfc822.Message(fp)
|
|
||||||
rc = self.envfrom('<%s>'%sender)
|
|
||||||
if rc != Milter.CONTINUE: return rc
|
|
||||||
for rcpt in (rcpt,) + rcpts:
|
|
||||||
rc = self.envrcpt('<%s>'%rcpt)
|
|
||||||
if rc != Milter.CONTINUE: return rc
|
|
||||||
line = None
|
|
||||||
for h in msg.headers:
|
|
||||||
if h[:1].isspace():
|
|
||||||
line = line + h
|
|
||||||
continue
|
|
||||||
if not line:
|
|
||||||
line = h
|
|
||||||
continue
|
|
||||||
s = line.split(': ',1)
|
|
||||||
if len(s) > 1: val = s[1].strip()
|
|
||||||
else: val = ''
|
|
||||||
rc = self.header(s[0],val)
|
|
||||||
if rc != Milter.CONTINUE: return rc
|
|
||||||
line = h
|
|
||||||
if line:
|
|
||||||
s = line.split(': ',1)
|
|
||||||
rc = self.header(s[0],s[1])
|
|
||||||
if rc != Milter.CONTINUE: return rc
|
|
||||||
rc = self.eoh()
|
|
||||||
if rc != Milter.CONTINUE: return rc
|
|
||||||
while 1:
|
|
||||||
buf = fp.read(8192)
|
|
||||||
if len(buf) == 0: break
|
|
||||||
rc = self.body(buf)
|
|
||||||
if rc != Milter.CONTINUE: return rc
|
|
||||||
self._msg = msg
|
|
||||||
self._body = StringIO.StringIO()
|
|
||||||
rc = self.eom()
|
|
||||||
if self._bodyreplaced:
|
|
||||||
body = self._body.getvalue()
|
|
||||||
else:
|
|
||||||
msg.rewindbody()
|
|
||||||
body = msg.fp.read()
|
|
||||||
self._body = StringIO.StringIO()
|
|
||||||
self._body.writelines(msg.headers)
|
|
||||||
self._body.write('\n')
|
|
||||||
self._body.write(body)
|
|
||||||
return rc
|
|
||||||
|
|
||||||
## Feed an email contained in a file to the milter.
|
|
||||||
# This is a convenience method that invokes @link #feedFile feedFile @endlink.
|
|
||||||
# @param sender MAIL FROM
|
|
||||||
# @param rcpts RCPT TO, multiple recipients may be supplied
|
|
||||||
def feedMsg(self,fname,sender="spam@adv.com",*rcpts):
|
|
||||||
with open('test/'+fname,'r') as fp:
|
|
||||||
return self.feedFile(fp,sender,*rcpts)
|
|
||||||
|
|
||||||
## Call the connect and helo callbacks.
|
|
||||||
# The helo callback is not called if connect does not return CONTINUE.
|
|
||||||
# @param host the hostname passed to the connect callback
|
|
||||||
# @param helo the hostname passed to the helo callback
|
|
||||||
# @param ip the IP address passed to the connect callback
|
|
||||||
def connect(self,host='localhost',helo='spamrelay',ip='1.2.3.4'):
|
|
||||||
self._body = None
|
|
||||||
self._bodyreplaced = False
|
|
||||||
opts = [ Milter.CURR_ACTS,~0,0,0 ]
|
|
||||||
rc = self.negotiate(opts)
|
|
||||||
rc = super(TestBase,self).connect(host,1,(ip,1234))
|
|
||||||
if rc != Milter.CONTINUE:
|
|
||||||
self.close()
|
|
||||||
return rc
|
|
||||||
rc = self.hello(helo)
|
|
||||||
if rc != Milter.CONTINUE:
|
|
||||||
self.close()
|
|
||||||
return rc
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
# Author: Stuart D. Gathman <stuart@bmsi.com>
|
|
||||||
# Copyright 2005 Business Management Systems, Inc.
|
|
||||||
# This code is under the GNU General Public License. See COPYING for details.
|
|
||||||
|
|
||||||
# The localpart of SMTP return addresses is often signed. The format
|
|
||||||
# of the signing is application specific and doesn't concern us -
|
|
||||||
# except that we wish to extract some sort of fixed string from
|
|
||||||
# the variable signature which represents the "source" of the message.
|
|
||||||
|
|
||||||
def unsign(s):
|
|
||||||
"""Attempt to unsign localpart and return original email.
|
|
||||||
No attempt is made to verify the signature.
|
|
||||||
>>> unsign('SRS0=8Y3CZ=3U=jsconnor.com=bills@bmsi.com')
|
|
||||||
'bills@jsconnor.com'
|
|
||||||
"""
|
|
||||||
# not implemented yet
|
|
||||||
return s
|
|
||||||
-202
@@ -1,202 +0,0 @@
|
|||||||
## @package Milter.utils
|
|
||||||
# Miscellaneous functions.
|
|
||||||
#
|
|
||||||
|
|
||||||
import re
|
|
||||||
import struct
|
|
||||||
import socket
|
|
||||||
import email.Errors
|
|
||||||
from fnmatch import fnmatchcase
|
|
||||||
from email.Header import decode_header
|
|
||||||
#import email.Utils
|
|
||||||
import rfc822
|
|
||||||
|
|
||||||
PAT_IP4 = r'\.'.join([r'(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])']*4)
|
|
||||||
ip4re = re.compile(PAT_IP4+'$')
|
|
||||||
ip6re = re.compile( '(?:%(hex4)s:){6}%(ls32)s$'
|
|
||||||
'|::(?:%(hex4)s:){5}%(ls32)s$'
|
|
||||||
'|(?:%(hex4)s)?::(?:%(hex4)s:){4}%(ls32)s$'
|
|
||||||
'|(?:(?:%(hex4)s:){0,1}%(hex4)s)?::(?:%(hex4)s:){3}%(ls32)s$'
|
|
||||||
'|(?:(?:%(hex4)s:){0,2}%(hex4)s)?::(?:%(hex4)s:){2}%(ls32)s$'
|
|
||||||
'|(?:(?:%(hex4)s:){0,3}%(hex4)s)?::%(hex4)s:%(ls32)s$'
|
|
||||||
'|(?:(?:%(hex4)s:){0,4}%(hex4)s)?::%(ls32)s$'
|
|
||||||
'|(?:(?:%(hex4)s:){0,5}%(hex4)s)?::%(hex4)s$'
|
|
||||||
'|(?:(?:%(hex4)s:){0,6}%(hex4)s)?::$'
|
|
||||||
% {
|
|
||||||
'ls32': r'(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|%s)'%PAT_IP4,
|
|
||||||
'hex4': r'[0-9a-f]{1,4}'
|
|
||||||
}, re.IGNORECASE)
|
|
||||||
|
|
||||||
# from spf.py
|
|
||||||
def addr2bin(s):
|
|
||||||
"""Convert a string IPv4 address into an unsigned integer."""
|
|
||||||
if s.find(':') >= 0:
|
|
||||||
try:
|
|
||||||
return bin2long6(inet_pton(s))
|
|
||||||
except:
|
|
||||||
raise socket.error("Invalid IP6 address: "+s)
|
|
||||||
try:
|
|
||||||
return struct.unpack("!L", socket.inet_aton(s))[0]
|
|
||||||
except socket.error:
|
|
||||||
raise socket.error("Invalid IP4 address: "+s)
|
|
||||||
|
|
||||||
def bin2long6(s):
|
|
||||||
"""Convert binary IP6 address into an unsigned Python long integer."""
|
|
||||||
h, l = struct.unpack("!QQ", s)
|
|
||||||
return h << 64 | l
|
|
||||||
|
|
||||||
if hasattr(socket,'has_ipv6') and socket.has_ipv6:
|
|
||||||
def inet_ntop(s):
|
|
||||||
return socket.inet_ntop(socket.AF_INET6,s)
|
|
||||||
def inet_pton(s):
|
|
||||||
return socket.inet_pton(socket.AF_INET6,s.strip())
|
|
||||||
else:
|
|
||||||
from pyip6 import inet_ntop, inet_pton
|
|
||||||
|
|
||||||
MASK = 0xFFFFFFFFL
|
|
||||||
MASK6 = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFL
|
|
||||||
|
|
||||||
def cidr(i,n,mask=MASK):
|
|
||||||
return ~(mask >> n) & mask & i
|
|
||||||
|
|
||||||
def iniplist(ipaddr,iplist):
|
|
||||||
"""Return whether ip is in cidr list
|
|
||||||
>>> iniplist('66.179.26.146',['127.0.0.1','66.179.26.128/26'])
|
|
||||||
True
|
|
||||||
>>> iniplist('127.0.0.1',['127.0.0.1','66.179.26.128/26'])
|
|
||||||
True
|
|
||||||
>>> iniplist('192.168.0.45',['192.168.0.*'])
|
|
||||||
True
|
|
||||||
>>> iniplist('2001:610:779:0:223:6cff:fe9a:9cf3',['127.0.0.1','172.20.1.0/24','2001:610:779::/48'])
|
|
||||||
True
|
|
||||||
>>> iniplist('2G01:610:779:0:223:6cff:fe9a:9cf3',['127.0.0.1','172.20.1.0/24','2001:610:779::/48'])
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
ValueError: Invalid ip syntax:2G01:610:779:0:223:6cff:fe9a:9cf3
|
|
||||||
"""
|
|
||||||
if ip4re.match(ipaddr):
|
|
||||||
ipnum = addr2bin(ipaddr)
|
|
||||||
elif ip6re.match(ipaddr):
|
|
||||||
ipnum = bin2long6(inet_pton(ipaddr))
|
|
||||||
else:
|
|
||||||
raise ValueError('Invalid ip syntax:'+ipaddr)
|
|
||||||
for pat in iplist:
|
|
||||||
p = pat.split('/',1)
|
|
||||||
if ip4re.match(p[0]):
|
|
||||||
if len(p) > 1:
|
|
||||||
n = int(p[1])
|
|
||||||
else:
|
|
||||||
n = 32
|
|
||||||
if cidr(addr2bin(p[0]),n) == cidr(ipnum,n):
|
|
||||||
return True
|
|
||||||
elif ip6re.match(p[0]):
|
|
||||||
if len(p) > 1:
|
|
||||||
n = int(p[1])
|
|
||||||
else:
|
|
||||||
n = 128
|
|
||||||
if cidr(bin2long6(inet_pton(p[0])),n,MASK6) == cidr(ipnum,n,MASK6):
|
|
||||||
return True
|
|
||||||
elif fnmatchcase(ipaddr,pat):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
## Split email into Fullname and address.
|
|
||||||
# This replaces <code>email.Utils.parseaddr</code> but fixes
|
|
||||||
# some <a href="http://bugs.python.org/issue1025395">tricky test cases</a>.
|
|
||||||
#
|
|
||||||
def parseaddr(t):
|
|
||||||
"""Split email into Fullname and address.
|
|
||||||
|
|
||||||
>>> parseaddr('user@example.com')
|
|
||||||
('', 'user@example.com')
|
|
||||||
>>> parseaddr('"Full Name" <foo@example.com>')
|
|
||||||
('Full Name', 'foo@example.com')
|
|
||||||
>>> parseaddr('spam@spammer.com <foo@example.com>')
|
|
||||||
('spam@spammer.com', 'foo@example.com')
|
|
||||||
>>> parseaddr('God@heaven <@hop1.org,@hop2.net:jeff@spec.org>')
|
|
||||||
('God@heaven', 'jeff@spec.org')
|
|
||||||
>>> parseaddr('Real Name ((comment)) <addr...@example.com>')
|
|
||||||
('Real Name', 'addr...@example.com')
|
|
||||||
>>> parseaddr('a(WRONG)@b')
|
|
||||||
('WRONG', 'a@b')
|
|
||||||
"""
|
|
||||||
#return email.Utils.parseaddr(t)
|
|
||||||
res = rfc822.parseaddr(t)
|
|
||||||
# dirty fix for some broken cases
|
|
||||||
if not res[0]:
|
|
||||||
pos = t.find('<')
|
|
||||||
if pos > 0 and t[-1] == '>':
|
|
||||||
addrspec = t[pos+1:-1]
|
|
||||||
pos1 = addrspec.rfind(':')
|
|
||||||
if pos1 > 0:
|
|
||||||
addrspec = addrspec[pos1+1:]
|
|
||||||
return rfc822.parseaddr('"%s" <%s>' % (t[:pos].strip(),addrspec))
|
|
||||||
if not res[1]:
|
|
||||||
pos = t.find('<')
|
|
||||||
if pos > 0 and t[-1] == '>':
|
|
||||||
addrspec = t[pos+1:-1]
|
|
||||||
pos1 = addrspec.rfind(':')
|
|
||||||
if pos1 > 0:
|
|
||||||
addrspec = addrspec[pos1+1:]
|
|
||||||
return rfc822.parseaddr('%s<%s>' % (t[:pos].strip(),addrspec))
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
def parse_addr(t):
|
|
||||||
"""Split email into user,domain.
|
|
||||||
|
|
||||||
>>> parse_addr('user@example.com')
|
|
||||||
['user', 'example.com']
|
|
||||||
>>> parse_addr('"user@example.com"')
|
|
||||||
['user@example.com']
|
|
||||||
>>> parse_addr('"user@bar"@example.com')
|
|
||||||
['user@bar', 'example.com']
|
|
||||||
>>> parse_addr('foo')
|
|
||||||
['foo']
|
|
||||||
>>> parse_addr('@mx.example.com:user@example.com')
|
|
||||||
['user', 'example.com']
|
|
||||||
>>> parse_addr('@user@example.com')
|
|
||||||
['@user', 'example.com']
|
|
||||||
"""
|
|
||||||
if t.startswith('<') and t.endswith('>'): t = t[1:-1]
|
|
||||||
if t.startswith('"'):
|
|
||||||
if t.endswith('"'): return [t[1:-1]]
|
|
||||||
pos = t.find('"@')
|
|
||||||
if pos > 0: return [t[1:pos],t[pos+2:]]
|
|
||||||
if t.startswith('@'):
|
|
||||||
try: t = t.split(':',1)[1]
|
|
||||||
except IndexError: pass
|
|
||||||
return t.rsplit('@',1)
|
|
||||||
|
|
||||||
## Decode headers gratuitously encoded to hide the content.
|
|
||||||
# Spammers often encode headers to obscure the content from
|
|
||||||
# spam filters. This function decodes gratuitously encoded
|
|
||||||
# headers.
|
|
||||||
# @param val the raw header value
|
|
||||||
# @return the decoded value or the original raw value
|
|
||||||
|
|
||||||
def parse_header(val):
|
|
||||||
"""Decode headers gratuitously encoded to hide the content.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
h = decode_header(val)
|
|
||||||
if not len(h) or (not h[0][1] and len(h) == 1): return val
|
|
||||||
u = []
|
|
||||||
for s,enc in h:
|
|
||||||
if enc:
|
|
||||||
try:
|
|
||||||
u.append(unicode(s,enc,'replace'))
|
|
||||||
except LookupError:
|
|
||||||
u.append(unicode(s))
|
|
||||||
else:
|
|
||||||
u.append(unicode(s))
|
|
||||||
u = ''.join(u)
|
|
||||||
for enc in ('us-ascii','iso-8859-1','utf8'):
|
|
||||||
try:
|
|
||||||
return u.encode(enc)
|
|
||||||
except UnicodeError: continue
|
|
||||||
except UnicodeDecodeError: pass
|
|
||||||
except LookupError: pass
|
|
||||||
except ValueError: pass
|
|
||||||
except email.Errors.HeaderParseError: pass
|
|
||||||
return val
|
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
Here is a history of user visible changes to Python milter.
|
||||||
|
|
||||||
|
0.7.2 Return unknown for invalid ip address in mechanism
|
||||||
|
Recognize dynamic PTR names, and don't count them as authentication.
|
||||||
|
Three strikes and yer out rule.
|
||||||
|
Block softfail by default when no PTR or HELO
|
||||||
|
Return unknown for null mechanism
|
||||||
|
Try best guess on HELO also
|
||||||
|
Expand setreply for common errors
|
||||||
|
make rhsbl.m4 hack available for sendmail.mc
|
||||||
|
0.7.1 Handle modifying mislabeled multipart messages without an exception
|
||||||
|
Support setbacklog, setmlreply
|
||||||
|
Allow multi-recipient CBV
|
||||||
|
Return TEMPFAIL for SPF softfail
|
||||||
|
0.7.0 SPF check hello name
|
||||||
|
Move pythonsock to /var/run/milter
|
||||||
|
Move milter.cfg to /etc/mail/pymilter.cfg
|
||||||
|
Check M$ style XML CID records by converting to SPF
|
||||||
|
Recognize, but never match ip6 - until we properly support it.
|
||||||
|
Option to reject when no PTR and no SPF
|
||||||
|
0.6.9 Reject invalid SRS immediately for benefit of callback verifiers
|
||||||
|
Fix include bug in spf.py
|
||||||
|
Fix check_header bug
|
||||||
|
Fix setup.py to work with python < 2.2.3, thanks to Eric S. Johansson
|
||||||
|
Test driver for SPF test suite. Fix bugs and add features to
|
||||||
|
pass most of test suite.
|
||||||
|
Use best_guess() and get_header() in bms.py for SPF support
|
||||||
|
0.6.8 Defang message/rfc822 content_type with boundary
|
||||||
|
Support SPF delegation
|
||||||
|
Reject neutral SPF result for selected domains
|
||||||
|
Support SPF default (best_guess)
|
||||||
|
Don't report "spoofed" unless rcpt looks like SRS
|
||||||
|
Check for bounce with multiple rcpts
|
||||||
|
Make dspam see Received-SPF headers
|
||||||
|
Fix sysv init for Redhat 9 and other single ps line per process systems
|
||||||
|
0.6.7 Fix failure to remove explicit unix socket thanks to Alexander again.
|
||||||
|
Support SRS forgery detection.
|
||||||
|
Detect thread resource starvation in Milter.py.
|
||||||
|
Decode obfuscated subject headers.
|
||||||
|
0.6.6 Another memory leak plugged by Alexander Kourakos.
|
||||||
|
Support SPF checking: http://spf.pobox.com
|
||||||
|
Hello blacklist
|
||||||
|
RPM compiled for python2.3 and sendmail-8.12
|
||||||
|
0.6.5 Plug memory leak in wrap_connect thanks to Alexander Kourakos.
|
||||||
|
Support progress notification.
|
||||||
|
Log Received header for trusted relay.
|
||||||
|
Support wildcard user for smart alias.
|
||||||
|
0.6.4 Exempt entire domains.
|
||||||
|
Tweak SMTP error codes reported.
|
||||||
|
Suppress traceback for Dspam lock timeouts.
|
||||||
|
Dspam internal mail for dspam users.
|
||||||
|
Match hostname for internal connection test, even if no ipaddr.
|
||||||
|
Fix for not saving defang of false positive triggered rejecting it
|
||||||
|
as a virus from self.
|
||||||
|
Size limit for dspam to work around dspam-2.6.5.2 bug.
|
||||||
|
(dspam-2.8 still showstopper buggy for libdspam API.)
|
||||||
|
Whitelist for dspam.
|
||||||
|
Reject list for dspam (REJECT rather than quarantine SCREENed
|
||||||
|
spam for listed domains).
|
||||||
|
Report dspam header changes to sendmail, fix headerChange
|
||||||
|
to handle deleting absent header.
|
||||||
|
dspam feature requires pydspam-1.1.5
|
||||||
|
0.6.3 dspam screening (with pydspam-1.1.4)
|
||||||
|
Don't write "defang" file for false positive feedback
|
||||||
|
0.6.2 Work around email package bug in get_filename().
|
||||||
|
add dspam_exempt list to milter.cfg
|
||||||
|
REJECT messages with missing MIME boundaries (almost always spam)
|
||||||
|
DISCARD messages which any dspam user flags as spam
|
||||||
|
start.sh was calling python instead of python2 on Linux
|
||||||
|
0.6.1 Work with python-2.2.3
|
||||||
|
Integrate full dspam application
|
||||||
|
0.6.0 Use email package in python-2.2.2
|
||||||
|
0.5.6 Include dspam interface for Bayesian filtering
|
||||||
|
0.5.5 Allow passing None to setreply and chgheader thanks to George Graf.
|
||||||
|
Experimental IPv6 support thanks to Deron Meranda.
|
||||||
|
Allow removing callbacks by passing None to set_XXX_callback.
|
||||||
|
Recognize internal connections in bms.py.
|
||||||
|
Give users a clue when rejecting banned subjects.
|
||||||
|
0.5.4 Wiretap redirection feature, smart alias feature, QUARANTINE support
|
||||||
|
0.5.3 Tweak to run under 2.2 in production
|
||||||
|
0.5.2 Fix and add to unit test another parsing failure.
|
||||||
|
0.5.1 Properly handle modifications to rfc822 attachments.
|
||||||
|
Handle encoded rfc822 attachments.
|
||||||
|
0.5.0 Use config file so users don't have to keep syncing the
|
||||||
|
bms.py script. Keep bms.py marked as %config for a while
|
||||||
|
to avoid wiping out their customizations just yet.
|
||||||
|
0.4.5 Work with sgmlop package to speed up HTML parsing.
|
||||||
|
Reduce various local hacks to config variables.
|
||||||
|
0.4.4 Bug fixes for HTML encoding.
|
||||||
|
0.4.3 Handle quoted-printable HTML attachments. Remove entire
|
||||||
|
attachment when HTML can't be parsed.
|
||||||
|
0.4.2 Parse HTML attachments to remove <script ...>...</script>.
|
||||||
|
Klez virus uses malformed MIME part separators to prevent
|
||||||
|
the multifile module and other virus scanners from seeing its
|
||||||
|
HTML attachment (which contains Javascript and VBScript). Outhouse
|
||||||
|
happily accepts and executes the malformed attachments, but
|
||||||
|
we still kill the Klez virus because we:
|
||||||
|
Defang attachment when any Content-Type attribute ends with
|
||||||
|
a banned extension - one of the Outhouse bugs exploited by the
|
||||||
|
Klez virus. Outhouse really, really stinks . . .
|
||||||
|
0.4.1 Bug fix from Jason Erikson for NULL hostaddr in connect callback.
|
||||||
|
0.4.0 New check_attachments(msg,check) function in mime module allows
|
||||||
|
filtering based on attachment contents. Distribution now includes
|
||||||
|
bms.py, an example milter used in production - including use of the
|
||||||
|
new check_attachments(msg,check) API.
|
||||||
|
Report hostname in WARNING.TXT.
|
||||||
|
More parameter list bug fixes.
|
||||||
|
|
||||||
|
0.3.10 Parse quotes in parameter lists to handle embedded ';'.
|
||||||
|
Move test data to subdirectory, write non-junit output to
|
||||||
|
log file in test subdirectory.
|
||||||
|
0.3.9 Handle non-multipart messages with executable content in sample.py,
|
||||||
|
add more extensions to banned list.
|
||||||
|
0.3.8 Handle malformed Content-Type in mime.py. Test viruses have
|
||||||
|
been deactivated by deleting most of the viral code.
|
||||||
|
0.3.7 Put back hint on running sample.py. Add .bat as banned extension.
|
||||||
|
More sample spam filtering logic.
|
||||||
|
0.3.6 Ran through pychecker-0.8.5. Most systems will name the sendmail
|
||||||
|
user library (used by the milter extension module) 'libsm', but AIX
|
||||||
|
still needs to call it 'libsmutil' because there is a system library
|
||||||
|
called 'libsm'.
|
||||||
|
0.3.5 Enhanced logging. Fix bug in sample milter where headers were
|
||||||
|
included in body when removing a virus.
|
||||||
|
0.3.4 Tested distribution on RH6.2 and updated sample.py and docs.
|
||||||
|
Tested with gcc-2.95.2, python-2.1.1, sendmail-8.11.6-2.6.x
|
||||||
|
The RH6.2 spec file to enable libmilter for sendmail-8.11.6
|
||||||
|
can be obtained from http://www.bmsi.com/linux/sendmail-rhmilter.spec
|
||||||
|
The SRPM can be obtained from http://www.redhat.com
|
||||||
|
|
||||||
|
0.3.3 Remove reference to sa_len - not supported by linux.
|
||||||
|
|
||||||
|
0.3.2 Rename and add more hints to the sample milter.
|
||||||
|
|
||||||
|
0.3.1 Pass a more useful hostaddr to the connect callback.
|
||||||
|
|
||||||
|
0.3 Interface now uses a milterContext extension object instead of
|
||||||
|
an index. A PyThreadContext is now created for each milterContext so that
|
||||||
|
"simultaneously" processing multiple messages at once (as often happens
|
||||||
|
on a busy server) actually works.
|
||||||
|
|
||||||
|
Many milter methods are now object methods of the milterContext
|
||||||
|
extension object. No compatibility API is provided for this change due
|
||||||
|
to the limited user base at this stage. The setname method has been removed,
|
||||||
|
and the name is now passed to register.
|
||||||
|
|
||||||
|
A simple class to provide an OO wrapper to the milter API is
|
||||||
|
provided.
|
||||||
|
|
||||||
|
A simple class to parse multipart mime messages into parts and replace
|
||||||
|
selected parts is provided. The sample filter will eventually use the mimelib
|
||||||
|
package instead, but mimelib currently requires reading the entire message
|
||||||
|
into memory.
|
||||||
|
|
||||||
|
A sample filter that replaces attachments with naughty extensions
|
||||||
|
with a warning message is provided.
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
Abstract
|
||||||
|
--------
|
||||||
|
|
||||||
|
This is a python extension module to enable python scripts to attach to
|
||||||
|
Sendmail's libmilter API, enabling filtering of messages as they arrive.
|
||||||
|
Since it's a script, you can do anything you want to the message - screen
|
||||||
|
out viruses, collect statistics, add or modify headers, etc. You can, at
|
||||||
|
any point, tell Sendmail to reject, discard, or accept the message.
|
||||||
|
|
||||||
|
|
||||||
|
Requirements
|
||||||
|
------------
|
||||||
|
|
||||||
|
This python milter extension: http://www.bmsi.com/python/milter.html
|
||||||
|
Python: http://www.python.org
|
||||||
|
Sendmail: http://www.sendmail.org
|
||||||
|
NB: From Sendmail's libmilter/README:
|
||||||
|
|
||||||
|
libmilter requires pthread support in the operating system. Moreover, it
|
||||||
|
requires that the library functions it uses are thread safe; which is true
|
||||||
|
for the operating systems libmilter has been developed and tested on. On
|
||||||
|
some operating systems this requires special compile time options (e.g.,
|
||||||
|
not just -pthread). libmilter is currently known to work on (modulo
|
||||||
|
problems in the pthread support of some specific versions):
|
||||||
|
|
||||||
|
FreeBSD 3.x, 4.x
|
||||||
|
SunOS 5.x (x >= 5)
|
||||||
|
AIX 4.3.x
|
||||||
|
HP UX 11.x
|
||||||
|
Linux (recent versions/distributions)
|
||||||
|
OpenBSD
|
||||||
|
AIX 4.1.5
|
||||||
|
|
||||||
|
libmilter is currently not supported on:
|
||||||
|
|
||||||
|
IRIX 6.x
|
||||||
|
Ultrix
|
||||||
|
|
||||||
|
Quick Installation
|
||||||
|
------------------
|
||||||
|
|
||||||
|
1. Build and install Sendmail, enabling libmilter (see libmilter/README).
|
||||||
|
2. Build and install Python, enabling threading.
|
||||||
|
3. Install this module: python setup.py --help
|
||||||
|
4. Add these two lines to sendmail.cf:
|
||||||
|
|
||||||
|
O InputMailFilters=pythonfilter
|
||||||
|
Xpythonfilter, S=local:/home/username/pythonsock
|
||||||
|
|
||||||
|
5. Run the sample.py example milter with: python sample.py
|
||||||
|
Note that milters should almost certainly not run as root.
|
||||||
|
|
||||||
|
That's it. Incoming mail will cause the milter to print some things, and
|
||||||
|
some email will be rejected (see the "header" method). Edit and play. See
|
||||||
|
bms.py for an example milter used in production.
|
||||||
|
|
||||||
|
|
||||||
|
Not-so-quick Installation
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
First install Sendmail. Make sure you read libmilter/README in the Sendmail
|
||||||
|
source directory, and make sure you enable libmilter before you build. The
|
||||||
|
8.11 series had libmilter marked as FFR (For Future Release); 8.12
|
||||||
|
officially
|
||||||
|
supports libmilter, but it's still not built by default.
|
||||||
|
|
||||||
|
Install Python, and enable threading in Modules/Setup.
|
||||||
|
|
||||||
|
Install this miltermodule package; DistUtils Automatic Installation:
|
||||||
|
|
||||||
|
$ python setup.py --help
|
||||||
|
|
||||||
|
For versions of python prior to 2.0, you will need to download distutils
|
||||||
|
separately or build manually. You will need to download unittest
|
||||||
|
separately to run the test programs. The bdist_rpm distutils option seems
|
||||||
|
not to work for python 2.0; upgrade to at least 2.1.1.
|
||||||
|
|
||||||
|
Now that everything is installed, we need to tell sendmail that we're going
|
||||||
|
to filter incoming email. Add lines similar to the following to
|
||||||
|
sendmail.cf:
|
||||||
|
|
||||||
|
O InputMailFilters=pythonfilter
|
||||||
|
Xpythonfilter, S=local:/home/username/pythonsock
|
||||||
|
|
||||||
|
The "O" line tells sendmail which filters to use in what order; here we're
|
||||||
|
telling sendmail to use the filter named "pythonfilter".
|
||||||
|
|
||||||
|
The next line, the "X" line (for "eXternal"), lists that filter along with
|
||||||
|
some options associated with it. In this case, we have the "S" option, which
|
||||||
|
names the socket that sendmail will use to communicate with this particular
|
||||||
|
milter. This milter's socket is a unix-domain socket in the filesystem.
|
||||||
|
See libmilter/README for the definitive list of options.
|
||||||
|
NB: The name is specified in two places: here, in sendmail's cf file, and
|
||||||
|
in the milter itself. Make sure the two match.
|
||||||
|
NB: The above lines can be added in your .mc file with this line:
|
||||||
|
|
||||||
|
INPUT_MAIL_FILTER(`pythonfilter', `S=local:/home/username/pythonsock')
|
||||||
|
|
||||||
|
For versions of sendmail prior to 8.12, you will need to enable
|
||||||
|
_FFR_MILTER for the cf macros. For example,
|
||||||
|
|
||||||
|
m4 -D_FFR_MILTER ../m4/cf.m4 myconfig.mc > myconfig.cf
|
||||||
|
|
||||||
|
|
||||||
|
RedHat 6.2 Notes
|
||||||
|
----------------
|
||||||
|
|
||||||
|
The Redhat 6.2 sendmail RPM does not enable milter. You can obtain a
|
||||||
|
modified spec file at
|
||||||
|
|
||||||
|
http://www.bmsi.com/linux/rh62/sendmail-rhmilter.spec
|
||||||
|
|
||||||
|
use it to rebuild the Redhat 7.2 SRPM. The RH6.2 SRPM does not have
|
||||||
|
recent sendmail security patches.
|
||||||
|
|
||||||
|
RedHat 7.2 Notes
|
||||||
|
----------------
|
||||||
|
|
||||||
|
The Redhat 7.2 sendmail RPM enables milter in sendmail - but does not include
|
||||||
|
the headers needed for compiling a milter. You can obtain a modified spec
|
||||||
|
file with a sendmail-devel package that includes the needed static libraries
|
||||||
|
and headers at
|
||||||
|
|
||||||
|
http://www.bmsi.com/linux/sendmail-rh72.spec
|
||||||
|
|
||||||
|
IPv6 Notes
|
||||||
|
----------
|
||||||
|
|
||||||
|
IPv6 is still experimental.
|
||||||
|
|
||||||
|
The IPv6 protocol is supported if your operation system supports it
|
||||||
|
and if sendmail was compiled with IPv6 support. To determine if your
|
||||||
|
sendmail supports IPv6, run "sendmail -d0" and check for the NETINET6
|
||||||
|
compilation option. To compile sendmail with IPv6 support, add this
|
||||||
|
declaration to your site.config.m4 before building it:
|
||||||
|
|
||||||
|
APPENDDEF(`confENVDEF', `-DNETINET6=1')
|
||||||
|
|
||||||
|
IPv6 support can show up in two places; the communications socket
|
||||||
|
between the milter and sendmail processes and in the host address
|
||||||
|
argument to the connect() callback method.
|
||||||
|
|
||||||
|
For sendmail to be able to accept IPv6 SMTP sessions, you must
|
||||||
|
configure the daemon to listen on an IPv6 port. Furthermore if you
|
||||||
|
want to allow both IPv4 and IPv6 connections, some operating systems
|
||||||
|
will require that each listens to different port numbers. For an
|
||||||
|
IPv6-only setup, your sendmail configuration should contain a line
|
||||||
|
similar to (first line is for sendmail.mc, second is sendmail.cf):
|
||||||
|
|
||||||
|
DAEMON_OPTIONS(`Name=MTA-v6, Family=inet6, Modify=C, Port=25')
|
||||||
|
O DaemonPortOptions=Name=MTA-v6, Family=inet6, Modify=C, Port=25
|
||||||
|
|
||||||
|
To allow sendmail and the milter process to communicate with each
|
||||||
|
other over IPv6, you may use the "inet6" socket name prefix, as in:
|
||||||
|
|
||||||
|
Xpythonfilter, S=inet6:1234@fec0:0:0:7::5c
|
||||||
|
|
||||||
|
The connect() callback method in the milter class will pass the
|
||||||
|
IPv6-specific information in the 'hostaddr' argument as a tuple. Note
|
||||||
|
that the type of this value is dependent upon the protocol family, and
|
||||||
|
is not compatible with IPv4 connections. Therefore you should always
|
||||||
|
check the family argument before attempting to use the hostaddr
|
||||||
|
argument. A quick example showing this follows:
|
||||||
|
|
||||||
|
import socket
|
||||||
|
...
|
||||||
|
class ipv6awareMilter(Milter.Milter):
|
||||||
|
...
|
||||||
|
def connect(self,hostname,family,hostaddr):
|
||||||
|
if family==socket.AF_INET:
|
||||||
|
ipaddress, port = hostaddr
|
||||||
|
elif family==socket.AF_INET6:
|
||||||
|
ip6address, port, flowinfo, scopeid = hostaddr
|
||||||
|
elif family==socket.AF_UNIX:
|
||||||
|
socketpath = hostaddr
|
||||||
|
|
||||||
|
The hostname argument is always safe to use without interpreting the
|
||||||
|
protocol family. For IPv6 connections for which the hostname can not
|
||||||
|
be determined the hostname will appear similar to the string
|
||||||
|
"[IPv6:::1]" with the corresponding hostaddr[0] being "::1". Refer to
|
||||||
|
RFC 2553 for information on interpreting and using the flowinfo and
|
||||||
|
scopeid socket attributes, both of which are integers.
|
||||||
|
|
||||||
|
Authors
|
||||||
|
-------
|
||||||
|
|
||||||
|
Jim Niemira (urmane@urmane.org) wrote the original C module and some quick
|
||||||
|
and dirty python to use it. Stuart D. Gathman (stuart@bmsi.com) took that
|
||||||
|
kludge and added threading and context objects to it, wrote a proper OO
|
||||||
|
wrapper (Milter.py) that handles attachments, did lots of testing, packaged
|
||||||
|
it with distutils, and generally transformed it from a quick hack to a
|
||||||
|
real, usable Python extension.
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
Defer TEMPERROR in SPF evaluation - give precedence to security
|
||||||
|
(only defer for PASS mechanisms).
|
||||||
|
|
||||||
|
Allow multiple recipients for MAIL FROM: <> by default.
|
||||||
|
|
||||||
|
Option to add Received-SPF header, but never reject on SPF.
|
||||||
|
|
||||||
|
Option to configure banned extension list for mime.py. Default to empty.
|
||||||
|
|
||||||
|
Create null config that does nothing - except maybe add Received-SPF
|
||||||
|
headers. Many admins would like to turn features on one at a time.
|
||||||
|
|
||||||
|
Checking in mime.py;
|
||||||
|
/bms/cvs/milter/mime.py,v <-- mime.py
|
||||||
|
new revision: 1.56; previous revision: 1.55
|
||||||
|
done
|
||||||
|
Checking in spf.py;
|
||||||
|
/bms/cvs/milter/spf.py,v <-- spf.py
|
||||||
|
new revision: 1.18; previous revision: 1.17
|
||||||
|
done
|
||||||
|
Checking in testmime.py;
|
||||||
|
/bms/cvs/milter/testmime.py,v <-- testmime.py
|
||||||
|
new revision: 1.19; previous revision: 1.18
|
||||||
|
|
||||||
|
Auto whitelist based on outgoing email - perhaps with magic subject
|
||||||
|
or recipient prefix.
|
||||||
|
|
||||||
|
Can't output messages with malformed rfc822 attachments.
|
||||||
|
|
||||||
|
Use python exceptions in SPF to cleanly handle unknown and error results.
|
||||||
|
|
||||||
|
Example malformed SPF:
|
||||||
|
onvunvuvvx.usafisnews.org text "v=spf1 mx ptr ip4:207.44.199.970 -all"
|
||||||
|
|
||||||
|
Move milter,Milter,mime,spf modules to pymilter
|
||||||
|
milter package will have bms.py application
|
||||||
|
|
||||||
|
Support SMTP AUTH and disable SPF checks when connection is authorized.
|
||||||
|
Web admin interface
|
||||||
|
Check valid domains allowed by internal senders to detect PCs infected
|
||||||
|
with spam trojans.
|
||||||
|
Do CBV (callback verification) for mail with no published SPF record.
|
||||||
|
message log for automated stats and blacklisting
|
||||||
|
Skip dspam when SPF pass?
|
||||||
|
Report 551 with rcpt on SPF fail?
|
||||||
|
check spam keywords with character classes, e.g.
|
||||||
|
{a}=[a@ãä], {i}=[i1í], {e}=[eë], {o}=[o0ö]
|
||||||
|
|
||||||
|
Implement RRS - a backdoor for non-SRS forwarders. User lists non-SRS
|
||||||
|
forwarder accounts, and a util provides a special local alias for the
|
||||||
|
user to give to the forwarder. Alias only works for mail from that
|
||||||
|
forwarder. Milter gets forwarder domain from alias and uses it to
|
||||||
|
SPF check forwarder.
|
||||||
|
|
||||||
|
Another special dspam user, 'honeypot', can be listed in innoculations.
|
||||||
|
All email to those addresses is treated as known spam.
|
||||||
|
|
||||||
|
Framework for modular Python milter components within a single VM.
|
||||||
|
Python milters can be already be composed through sendmail by running each in
|
||||||
|
a separate process. However, a significant amount of memory is wasted
|
||||||
|
for each additional Python VM, and communication between milters
|
||||||
|
is cumbersome (e.g., adding mail headers, writing external files).
|
||||||
|
|
||||||
|
Backup copies for outgoing/incoming mail.
|
||||||
|
|
||||||
|
Allow multiple wiretap groups, each with its own destination. Perhaps
|
||||||
|
also copy incoming wiretap mail, even though sendmail alias works perfectly
|
||||||
|
for the purpose, to avoid having to change two configs for a wiretap.
|
||||||
|
|
||||||
|
Provide a way to reload milter.cfg without stopping/restarting milter.
|
||||||
|
|
||||||
|
Allow selected Windows extensions for specific domains via milter.cfg
|
||||||
|
|
||||||
|
Fix setup.py so that _FFR_QUARANTINE is automatically defined when
|
||||||
|
available in libmilter.
|
||||||
|
|
||||||
|
Keep separate ismodified flag for headers and body. This is important
|
||||||
|
when rejecting outgoing mail with viruses removed (so as not to
|
||||||
|
embarrass yourself), and also removing Received headers with hidepath.
|
||||||
|
|
||||||
|
Wrap smfi_setbacklog(int) - but it is only available in sendmail >= 8.12.3,
|
||||||
|
so how can we detect whether to wrap it?
|
||||||
|
|
||||||
|
Need a test module to feed sample messages to a milter though a live
|
||||||
|
sendmail and SMTP. The mockup currently used is probably not very accurate,
|
||||||
|
and doesn't test the threading code.
|
||||||
|
|
||||||
+153
@@ -0,0 +1,153 @@
|
|||||||
|
#!/usr/bin/python2.3
|
||||||
|
|
||||||
|
# Convert a MS Caller-ID entry (XML) to a SPF entry
|
||||||
|
#
|
||||||
|
# (c) 2004 by Ernesto Baschny
|
||||||
|
# (c) 2004 Python version by Stuart Gathman
|
||||||
|
#
|
||||||
|
# Date: 2004-02-25
|
||||||
|
# Version: 1.0
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./cid2spf.pl "<ep xmlns='http://ms.net/1'>...</ep>"
|
||||||
|
#
|
||||||
|
# Note that the 'include' directives will also have to be checked and
|
||||||
|
# "translated". Future versions of this script might be able to get a
|
||||||
|
# domain name as an argument and "crawl" the DNS for the necessary
|
||||||
|
# information.
|
||||||
|
#
|
||||||
|
# A complete reverse translation (SPF -> CID) might be impossible, since
|
||||||
|
# there are no way to handle:
|
||||||
|
# - PTR and EXISTS mechanism
|
||||||
|
# - MX mechanism with an different domain as argument
|
||||||
|
# - macros
|
||||||
|
#
|
||||||
|
# References:
|
||||||
|
# http://www.microsoft.com/mscorp/twc/privacy/spam_callerid.mspx
|
||||||
|
# http://spf.pobox.com/
|
||||||
|
#
|
||||||
|
# Known bugs:
|
||||||
|
# - Currently it won't handle the exclusions provided in the A and R
|
||||||
|
# tags (prefix '!'). They will show up "as-is" in the SPF record
|
||||||
|
# - I really haven't read the MS-CID specs in-depth, so there are probably
|
||||||
|
# other bugs too :)
|
||||||
|
#
|
||||||
|
# Ernesto Baschny <ernst@baschny.de>
|
||||||
|
#
|
||||||
|
|
||||||
|
import xml.sax
|
||||||
|
import spf
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
class CIDParser(xml.sax.ContentHandler):
|
||||||
|
"Convert a MS Caller-ID entry (XML) to a SPF entry"
|
||||||
|
|
||||||
|
def __init__(self,q=None):
|
||||||
|
self.spf = []
|
||||||
|
self.action = '-all'
|
||||||
|
self.has_servers = None
|
||||||
|
self.spf_entry = None
|
||||||
|
if q:
|
||||||
|
self.spf_query = q
|
||||||
|
else:
|
||||||
|
self.spf_query = spf.query(i='127.0.0.1', s='localhost', h='unknown')
|
||||||
|
|
||||||
|
def startElement(self,tag,attr):
|
||||||
|
if tag == 'm':
|
||||||
|
if self.has_servers != None and not self.has_servers:
|
||||||
|
raise ValueError(
|
||||||
|
"Declared <noMailServers\> and later <m>, this CID entry is not valid."
|
||||||
|
)
|
||||||
|
self.has_servers = True
|
||||||
|
elif tag == 'noMailServers':
|
||||||
|
if self.has_servers:
|
||||||
|
raise ValueError(
|
||||||
|
"Declared <m> and later <noMailServers\>, this CID entry is not valid."
|
||||||
|
)
|
||||||
|
self.has_servers = False
|
||||||
|
elif tag == 'ep':
|
||||||
|
if attr.has_key('testing') and attr.getValue('testing') == 'true':
|
||||||
|
# A CID with 'testing' found:
|
||||||
|
# From the MS-specs:
|
||||||
|
# "Documents in which such attribute is present with a true
|
||||||
|
# value SHOULD be entirely ignored (one should act as if the
|
||||||
|
# document were absent)"
|
||||||
|
# From the SPF-specs:
|
||||||
|
# "Neutral (?): The SPF client MUST proceed as if a domain did
|
||||||
|
# not publish SPF data."
|
||||||
|
# So we set SPF action to "neutral":
|
||||||
|
self.action = '?all'
|
||||||
|
elif tag == 'mx':
|
||||||
|
# The empty MX-tag, same as SPF's MX-mechanism
|
||||||
|
self.spf.append('mx')
|
||||||
|
self.tag = tag
|
||||||
|
|
||||||
|
def characters(self,text):
|
||||||
|
tag = self.tag
|
||||||
|
# Remove starting and trailing spaces from text:
|
||||||
|
text = text.strip()
|
||||||
|
|
||||||
|
if tag == 'a' or tag == 'r':
|
||||||
|
# The A and R tags from MS-CID are both handled by the
|
||||||
|
# ipv4/6-mechanisms from SPF:
|
||||||
|
if text.find(':') < 0:
|
||||||
|
mechanism = 'ip4'
|
||||||
|
else:
|
||||||
|
mechanism = 'ip6'
|
||||||
|
self.spf.append(mechanism + ':' + text)
|
||||||
|
elif tag == 'indirect':
|
||||||
|
# MS-CID's indirect is "sort of" the include from SPF:
|
||||||
|
# Not really true, because the <indirect> tag from MS-CID also
|
||||||
|
# provides a fallback in case the included domain doesn't provide
|
||||||
|
# _ep-records: The inbound MX-servers of the included domains
|
||||||
|
# are added to the list of allowed outgoing mailservers for the
|
||||||
|
# domain that declared the _ep-record with the <indirect> tag.
|
||||||
|
# In SPF you would use the 'mx:domain' to handle this, but this
|
||||||
|
# wouldn't depend on referred domain having or not SPF-records.
|
||||||
|
cid_xml = self.cid_txt(text)
|
||||||
|
if cid_xml:
|
||||||
|
p = CIDParser()
|
||||||
|
xml.sax.parseString(cid_xml,p)
|
||||||
|
if p.has_servers != False:
|
||||||
|
self.spf += p.spf
|
||||||
|
else:
|
||||||
|
self.spf.append('mx:' + text)
|
||||||
|
|
||||||
|
def cid_txt(self,domain):
|
||||||
|
q = self.spf_query
|
||||||
|
domain='_ep.' + domain
|
||||||
|
a = q.dns_txt(domain)
|
||||||
|
if not a: return None
|
||||||
|
if a[0].lower().startswith('<ep ') and a[-1].lower().endswith('</ep>'):
|
||||||
|
return ''.join(a)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def endElement(self,tag):
|
||||||
|
if tag == 'ep':
|
||||||
|
# This is the end... assemble what we've got
|
||||||
|
spf_entry = ['v=spf1']
|
||||||
|
if self.has_servers != False:
|
||||||
|
spf_entry += self.spf
|
||||||
|
spf_entry.append(self.action)
|
||||||
|
self.spf_entry = ' '.join(spf_entry)
|
||||||
|
|
||||||
|
def spf_txt(self,cid_xml):
|
||||||
|
if not cid_xml.startswith('<'):
|
||||||
|
cid_xml = self.cid_txt(cid_xml)
|
||||||
|
if not cid_xml: return None
|
||||||
|
# Parse the beast. Any XML-problem will be reported by xlm.sax
|
||||||
|
self.spf_entry = None
|
||||||
|
xml.sax.parseString(cid_xml,self)
|
||||||
|
return self.spf_entry
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import sys
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print >>sys.stderr, \
|
||||||
|
"""Usage: %s "<ep xmlns='http://ms.net/1'>...</ep>" """ % sys.argv[0]
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
cid_xml = sys.argv[1]
|
||||||
|
|
||||||
|
p = CIDParser()
|
||||||
|
print p.spf_txt(cid_xml)
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Python Milter FAQ</title>
|
||||||
|
</head><body>
|
||||||
|
|
||||||
|
<h1> Python Milter <a name=faq>FAQ</a> </h1>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<h3> Compiling Python Milter </h3>
|
||||||
|
<li> Q. I have installed sendmail from source, but Python milter won't
|
||||||
|
compile.
|
||||||
|
<p> A. Even though libmilter is officially supported in sendmail-8.12,
|
||||||
|
you need to build and install it in separate steps. Take a look
|
||||||
|
at the <a href="/aix/sendmail12.spec">RPM spec file</a> for sendmail-8.12.
|
||||||
|
The %prep section shows you how to create
|
||||||
|
a site.config.m4 that enables MILTER. The %build section shows you how
|
||||||
|
to build libmilter in a separate invocation of make. The %install section
|
||||||
|
shows you how to install libmilter with a separate invocation of make.
|
||||||
|
<p>
|
||||||
|
|
||||||
|
<li> Q. Why is mfapi.h not found when I try to compile Python milter on
|
||||||
|
RedHat 7.2?
|
||||||
|
<p> A. RedHat forgot to include the header in the RPM. See the
|
||||||
|
<a href="milter.html#rh72">RedHat 7.2 requirements</a>.
|
||||||
|
<p>
|
||||||
|
|
||||||
|
<h3> Running Python Milter </h3>
|
||||||
|
|
||||||
|
<li> Q. The sample.py milter prints a message, then just sits there.
|
||||||
|
<pre>
|
||||||
|
To use this with sendmail, add the following to sendmail.cf:
|
||||||
|
|
||||||
|
O InputMailFilters=pythonfilter
|
||||||
|
Xpythonfilter, S=local:inet:1030@localhost
|
||||||
|
|
||||||
|
See the sendmail README for libmilter.
|
||||||
|
sample milter startup
|
||||||
|
</pre>
|
||||||
|
<p> A. You need to tell sendmail to connect to your milter. The
|
||||||
|
sample milter tells you what to add to your sendmail.cf to tell
|
||||||
|
sendmail to use the milter. You can also add an INPUT_MAIL_FILTER
|
||||||
|
macro to your sendmail.mc file and rebuild sendmail.cf - see the sendmail
|
||||||
|
README for milters.
|
||||||
|
<p>
|
||||||
|
|
||||||
|
<li> Q. I've configured sendmail properly, but still nothing happens
|
||||||
|
when I send myself mail!
|
||||||
|
<p> A. Sendmail only milters SMTP mail. Local mail is not miltered.
|
||||||
|
You can pipe a raw message through sendmail to test your milter:
|
||||||
|
<pre>
|
||||||
|
$ cat rawtextmsg | sendmail myname@my.full.domain
|
||||||
|
</pre>
|
||||||
|
Now check your milter log.
|
||||||
|
<p>
|
||||||
|
|
||||||
|
<li> Q. Why do I get this ImportError exception?
|
||||||
|
<pre>
|
||||||
|
File "mime.py", line 370, in ?
|
||||||
|
from sgmllib import declstringlit, declname
|
||||||
|
ImportError: cannot import name declstringlit
|
||||||
|
</pre>
|
||||||
|
<p> A. <code>declstringlit</code> is not provided by sgmllib in all versions
|
||||||
|
of python. For instance, python-2.2 does not have it. Upgrade to
|
||||||
|
milter-0.4.5 or later to remove this dependency.
|
||||||
|
<p>
|
||||||
|
|
||||||
|
<li> Q. Why do I get <code>milter.error: cannot add recipient</code>?
|
||||||
|
<pre>
|
||||||
|
</pre>
|
||||||
|
<p> A. You must tell libmilter how you might mutate the message with
|
||||||
|
<code>set_flags()</code> before calling <code>runmilter()</code>. For
|
||||||
|
instance, <code>Milter.set_flags(Milter.ADDRCPT)</code>. You must add together
|
||||||
|
all of <code>ADDHDRS, CHGBODY, ADDRCPT, DELRCPT, CHGHDRS</code> that apply.
|
||||||
|
<p>
|
||||||
|
|
||||||
|
<li> Q. Why does sendmail sometimes print something like:
|
||||||
|
"...write(D) returned -1, expected 5: Broken pipe"
|
||||||
|
in the sendmail log?
|
||||||
|
<p> A. Libmilter expects "rcpt to" shortly after getting "mail from".
|
||||||
|
"Shortly" is defined by the timeout parameter you passed to
|
||||||
|
<code>Milter.runmilter()
|
||||||
|
</code> or <code>milter.settimeout()</code>. If the timeout is 10 seconds,
|
||||||
|
and looking up the first recipient in DNS takes more than
|
||||||
|
10 seconds, libmilter will give up and break the connection.
|
||||||
|
<code>Milter.runmilter()</code> defaulted to 10 seconds in 0.3.4. In 0.3.5
|
||||||
|
it will keep the libmilter default of 2 hours.
|
||||||
|
<p>
|
||||||
|
|
||||||
|
<li> Q. Why does milter block messages with big5 encoding? What if I
|
||||||
|
want to receive them?
|
||||||
|
<p> A. sample.py is a sample. It is supposed to be easily modified
|
||||||
|
for your specific needs. We will of course continue to move generic
|
||||||
|
code out of the sample as the project evolves. Think of sample.py as
|
||||||
|
an active config file.
|
||||||
|
<p>
|
||||||
|
|
||||||
|
<li> Q. Why does sendmail coredump with milters on OpenBSD?
|
||||||
|
<p> A. Sendmail has a problem with unix sockets on OpenBSD. Use
|
||||||
|
an internet domain socket instead. For example, in <code>sendmail.cf</code> use
|
||||||
|
<pre>
|
||||||
|
Xpythonfilter, S=inet:1234@localhost
|
||||||
|
</pre>
|
||||||
|
and change sample.py accordingly.
|
||||||
|
<p>
|
||||||
|
|
||||||
|
<li> Q. How can I change the bounce message for an invalid recipient?
|
||||||
|
I can only change the recipient in the eom callback, but the eom callback
|
||||||
|
is never called when the recipient is invalid!
|
||||||
|
<p> A. Configure sendmail to use virtusertable, and send all unknown
|
||||||
|
addresses to /dev/null. For example,
|
||||||
|
<h4>/etc/mail/virtusertable</h4>
|
||||||
|
<pre>
|
||||||
|
@mycorp.com dev-null
|
||||||
|
dan@mycorp.com dan
|
||||||
|
sally@mycorp.com sally
|
||||||
|
</pre>
|
||||||
|
<h4>/etc/aliases</h4>
|
||||||
|
<pre>
|
||||||
|
dev-null: /dev/null
|
||||||
|
</pre>
|
||||||
|
Now your milter will get to the eom callback, and can change the
|
||||||
|
envelope recipient at will. Thanks to Dredd at
|
||||||
|
<a href=http://www.milter.org/>milter.org</a> for this solution.
|
||||||
|
<p>
|
||||||
|
|
||||||
|
<li> Q. I am having trouble with the setreply method. It always outputs
|
||||||
|
"milter.error: cannot set reply".
|
||||||
|
<p> A. Check the sendmail log for errors. If sendmail is getting
|
||||||
|
milter timeouts, then your milter is taking too long and sendmail gave
|
||||||
|
up waiting. You can adjust the timeouts in your sendmail config. Here
|
||||||
|
is a milter declaration for sendmail.cf with all timeouts specified:
|
||||||
|
<pre>
|
||||||
|
Xpythonfilter, S=local:/var/log/milter/pythonsock, F=T, T=C:5m;S:20s;R:60s;E:5m
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<a name="spf">
|
||||||
|
<li> Q. So how do I use the SPF support? The sample.py milter doesn't seem
|
||||||
|
to use it.
|
||||||
|
<p> A. The bms.py milter supports spf. The RedHat RPMs will set almost
|
||||||
|
everything up for you. For other systems:
|
||||||
|
<ol type=i>
|
||||||
|
<li> Arrange to run bms.py in the background (as a service perhaps) and
|
||||||
|
redirect output and errors to a logfile. For instance, on AIX you'll want
|
||||||
|
to use SRC (System Resource Controller).
|
||||||
|
<li> Copy milter.cfg to the directory you run bms.py in, and edit it. The
|
||||||
|
comments should explain the options.
|
||||||
|
<li> Start bms.py in the background as arranged.
|
||||||
|
<li> Add Xpythonfilter to sendmail.cf or add an INPUT_MAIL_FILTER to
|
||||||
|
sendmail.mc. Regen sendmail.cf if you use sendmail.mc and restart
|
||||||
|
sendmail.
|
||||||
|
<li> Arrange to rotate log files and remove old defang files in
|
||||||
|
<code>tempdir</code>. The RedHat RPM uses <code>logrotate</code> for
|
||||||
|
logfiles and a simple cron script using <code>find</code> to clean
|
||||||
|
<code>tempdir</code>.
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
</ol>
|
||||||
|
</html>
|
||||||
+144
@@ -0,0 +1,144 @@
|
|||||||
|
[milter]
|
||||||
|
# the socket used to communicate with sendmail. Must match sendmail.cf
|
||||||
|
;socket=/var/run/milter/pythonsock
|
||||||
|
# where to save original copies of defanged and failed messages
|
||||||
|
tempdir = /var/log/milter/save
|
||||||
|
# how long to wait for a response from sendmail before giving up
|
||||||
|
;timeout=600
|
||||||
|
log_headers = 0
|
||||||
|
# connection ips and hostnames are matched against this glob style list
|
||||||
|
# to recognize internal senders
|
||||||
|
;internal_connect = 192.168.*.*
|
||||||
|
|
||||||
|
# mail that is not an internal_connect and claims to be from an
|
||||||
|
# internal domain is rejected. You should enable SPF instead if you can.
|
||||||
|
# SPF is much more comprehensive and flexible.
|
||||||
|
;internal_domains = mycorp.com
|
||||||
|
|
||||||
|
# connections from a trusted relay can trust the first Received header
|
||||||
|
# SPF checks are bypassed for internal connections and trusted relays.
|
||||||
|
;trusted_relay = 1.2.3.4, 66.12.34.56
|
||||||
|
|
||||||
|
# reject external senders with hello names no legit external sender would use
|
||||||
|
# SPF will do this also, but listing your own domain and mailserver here
|
||||||
|
# will save some DNS lookups when rejecting certain viruses.
|
||||||
|
;hello_blacklist = mycorp.com, 66.12.34.56
|
||||||
|
|
||||||
|
# features intended to filter or block incoming mail
|
||||||
|
;[defang]
|
||||||
|
# do virus scanning on attached messages also
|
||||||
|
scan_rfc822 = 1
|
||||||
|
# Comment out scripts in HTML attachments. Can be CPU intensive.
|
||||||
|
scan_html = 0
|
||||||
|
# reject messages with asian fonts because we can't read them
|
||||||
|
block_chinese = 1
|
||||||
|
# list users who hate forwarded mail
|
||||||
|
;block_forward = egghead@mycorp.com, busybee@mycorp.com
|
||||||
|
# Reject mail for domains mentioned unless user is mentioned here also
|
||||||
|
;check_user = joe@mycorp.com, mary@mycorp.com, file:bigcorp.com
|
||||||
|
# reject mail with these case insensitive strings in the subject
|
||||||
|
porn_words = penis, breast, pussy, horse cock, porn, xenical, diet pill, d1ck,
|
||||||
|
vi*gra, vi-a-gra, viag, tits, p0rn, hunza, horny, sexy, c0ck, xanaax,
|
||||||
|
p-e-n-i-s, hydrocodone, vicodin, xanax, vicod1n, x@nax, diazepam,
|
||||||
|
v1@gra, xan@x, cialis, ci@lis, frëe, xãnax, valíum, vãlium, via-gra,
|
||||||
|
x@n3x, vicod3n, penís, c0d1n, phentermine, en1arge, dip1oma, v1codin,
|
||||||
|
valium, rolex, sexual
|
||||||
|
# reject mail with these case sensitive strings in the subject
|
||||||
|
spam_words = $$$, !!!, XXX, FREE, HGH
|
||||||
|
|
||||||
|
# See http://bmsi.com/python/pysrs.html for details
|
||||||
|
[srs]
|
||||||
|
config=/etc/mail/pysrs.cfg
|
||||||
|
# SRS options can be set here also, but must match the sendmail plugin
|
||||||
|
;secret="shhhh!"
|
||||||
|
;maxage=21
|
||||||
|
;hashlength=4
|
||||||
|
;database=/var/log/milter/srsdata
|
||||||
|
;fwdomain = mydomain.com
|
||||||
|
# turn this on after a grace period to reject spoofed DSNs
|
||||||
|
reject_spoofed = 0
|
||||||
|
|
||||||
|
# See http://spf.pobox.com for more info on SPF.
|
||||||
|
[spf]
|
||||||
|
# namespace where SPF records can be supplied for domains without one
|
||||||
|
# records are searched for under _spf.domain.com
|
||||||
|
;delegate = domain.com
|
||||||
|
# domains where a neutral SPF result should cause mail to be rejected
|
||||||
|
;reject_neutral = aol.com
|
||||||
|
# use a default (v=spf1 a/24 mx/24 ptr) when no SPF records are published
|
||||||
|
;best_guess = 0
|
||||||
|
# reject senders that have neither PTR nor SPF records
|
||||||
|
;reject_noptr = 0
|
||||||
|
# always accept softfail from these domains
|
||||||
|
;accept_softfail = bounces.amazon.com
|
||||||
|
|
||||||
|
# features intended to clean up outgoing mail
|
||||||
|
[scrub]
|
||||||
|
# domains that block visible private nodes
|
||||||
|
;hide_path = jcpenney.com
|
||||||
|
# reject, don't just replace with warning, viruses from these domains
|
||||||
|
;reject_virus_from = mycorp.com
|
||||||
|
|
||||||
|
# features intended for spying on users and coworkers
|
||||||
|
[wiretap]
|
||||||
|
blind = 1
|
||||||
|
#
|
||||||
|
# wiretap lets you surreptitiously monitor a users outgoing email
|
||||||
|
# (sendmail aliases let you monitor incoming mail)
|
||||||
|
#
|
||||||
|
;users = disloyal@bigcorp.com, bigmouth@bigcorp.com
|
||||||
|
;dest = spy@bigcorp.com
|
||||||
|
# discard outgoing mail without alerting sender
|
||||||
|
# can be used in conjunction with wiretap to censor outgoing mail
|
||||||
|
;discard_users = canned@bigcorp.com
|
||||||
|
#
|
||||||
|
# smart aliases trigger on both sender and recipient
|
||||||
|
#
|
||||||
|
;smart_alias = copycust,walter
|
||||||
|
# mail from client@clientcorp.com to sue@bigcorp.com is redirected to
|
||||||
|
# local alias copycust
|
||||||
|
;copycust = client@clientcorp.com,sue@bigcorp.com
|
||||||
|
# mail from cust@othercorp.com to walter@bigcorp.com is redirected to
|
||||||
|
# boss@bigcorp.com
|
||||||
|
;walter = cust@othercorp.com,walter@bigcorp.com,boss@bigcorp.com
|
||||||
|
# additional copies can be added
|
||||||
|
;walter1 = cust@othercorp.com,walter@bigcorp.com,boss@bigcorp.com,
|
||||||
|
; walter@bigcorp.com
|
||||||
|
|
||||||
|
# See http://bmsi.com/python/dspam.html
|
||||||
|
[dspam]
|
||||||
|
# Select a well moderated dspam dictionary to reject spammy headers.
|
||||||
|
# To filter on the entire message, use the full setup below.
|
||||||
|
# only EXTERNAL messages are dspam filtered
|
||||||
|
;dspam_dict=/var/lib/dspam/moderator.dict
|
||||||
|
|
||||||
|
# Opt-opt recipients from dspam screening and header triage
|
||||||
|
;dspam_exempt=getitall@mycorp.com
|
||||||
|
# Do not scan mail (ostensibly) from these senders
|
||||||
|
;dspam_whitelist=getitall@sender.com
|
||||||
|
# Reject spam to these domains instead of quarantining it.
|
||||||
|
;dspam_reject=othercorp.com
|
||||||
|
# Scan internal mail - often a good source of stats on legit mail.
|
||||||
|
;dspam_internal=1
|
||||||
|
|
||||||
|
# directory for dspam user quarantine, signature db, and dictionaries
|
||||||
|
# defining this activates the dspam application
|
||||||
|
# dspam and dspam-python must be installed
|
||||||
|
;dspam_userdir=/var/lib/dspam
|
||||||
|
# do not dspam messages larger than this
|
||||||
|
;dspam_sizelimit=180000
|
||||||
|
|
||||||
|
# Map email addresses and aliases to dspam users
|
||||||
|
;dspam_users=david,goliath,spam,falsepositive
|
||||||
|
;david=david@foocorp.com,david.yelnetz@foocorp.com,david@bar.foocorp.com
|
||||||
|
;goliath=giant@foocorp.com,goliath.philistine@foocorp.com
|
||||||
|
# address to forward spam to. milter will process these and not deliver
|
||||||
|
;spam=spam@foocorp.com
|
||||||
|
# address to forward false positives to. milter will process and not deliver
|
||||||
|
;falsepositive=ham@foocorp.com
|
||||||
|
# the dspam_screener is a list of dspam users who screen mail for all
|
||||||
|
# recipients who are not dspam_users. Spam goes to the screeners quarantine,
|
||||||
|
# and the original recipients are saved so that false positives can be properly
|
||||||
|
# delivered.
|
||||||
|
;dspam_screener=david,goliath
|
||||||
|
# The dspam CGI can also be used: logins must match dspam users
|
||||||
+778
@@ -0,0 +1,778 @@
|
|||||||
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||||
|
<title>Python Milters</title>
|
||||||
|
</head><body>
|
||||||
|
|
||||||
|
<P ALIGN="CENTER"><A HREF="http://www.anybrowser.org/campaign/">
|
||||||
|
<IMG SRC="/art/brain1.gif"
|
||||||
|
ALT="Viewable With Any Browser" BORDER="0"></A>
|
||||||
|
|
||||||
|
<img src="/art/banner_4.gif" width="468" height="60" border="0"
|
||||||
|
usemap="#banner_4" alt="Your vote?">
|
||||||
|
<map name="banner_4">
|
||||||
|
<area shape="rect" coords="330,25,426,59"
|
||||||
|
href="http://education-survey.org/" alt="I Disagree">
|
||||||
|
<area shape="rect" coords="234,28,304,57" href="http://www.honestEd.com/" alt="I Agree">
|
||||||
|
</map>
|
||||||
|
|
||||||
|
</P>
|
||||||
|
<h1 align=center>Sendmail Milters in Python</h1>
|
||||||
|
<h4 align=center>by <a href="mailto:%75%72%6D%61%6E%65%40%6E%65%75%72%61l%61%63%63%65%73%73%2E%63%6F%6D">Jim Niemira</a>
|
||||||
|
and <a href="mailto:%73%74%75%61%72%74%40%62%6D%73%69%2E%63%6F%6D">
|
||||||
|
Stuart D. Gathman</a><br>
|
||||||
|
This web page is written by Stuart D. Gathman<br>and<br>sponsored by
|
||||||
|
<a href="http://www.bmsi.com">Business Management Systems, Inc.</a> <br>
|
||||||
|
Last updated Jan 05, 2005</h4>
|
||||||
|
|
||||||
|
See the <a href="faq.html">FAQ</a> | <a href="#download">Download now</a> |
|
||||||
|
<a href="/mailman/listinfo/pymilter">Subscribe to mailing list</a> |
|
||||||
|
<a href="#overview">Overview</a>
|
||||||
|
<p>
|
||||||
|
<a href="//www.python.org">
|
||||||
|
<img src="python55.gif" align=left alt="A Python"></a>
|
||||||
|
<a href="//www.sendmail.org/">Sendmail</a> introduced a
|
||||||
|
<a href="http://www.milter.org/milter_api/api.html"> new API</a> beginning with version 8.10 -
|
||||||
|
libmilter. The milter module for <a href="//www.python.org">Python</a>
|
||||||
|
provides a python interface to libmilter that exploits all its features.
|
||||||
|
<p>
|
||||||
|
Sendmail 8.12 officially releases libmilter.
|
||||||
|
Version 8.12 seems to be more robust, and includes new privilege
|
||||||
|
separation features to enhance security.
|
||||||
|
I recommend upgrading.
|
||||||
|
|
||||||
|
<h2> Recent Changes </h2>
|
||||||
|
|
||||||
|
Release 0.7.2 tightens the authentication screws with a "3 strikes and
|
||||||
|
your out" policy. A sender must have a valid PTR, HELO, or SPF record
|
||||||
|
to send email. Specific senders can be whitelisted using the
|
||||||
|
"delegate" option in the spf configuration section by adding a
|
||||||
|
default SPF record for them. The PTR and HELO are required
|
||||||
|
by RFC anyway, so this is not an unreasonable requirement.
|
||||||
|
There is now a coherent policy for an SPF softfail result. A softfail
|
||||||
|
is accepted if there is a valid PTR or HELO, or if the domain
|
||||||
|
is listed in the "accept_softfail" option of the spf configuration section.
|
||||||
|
A neutral result is accepted by default if there is a valid PTR or
|
||||||
|
HELO, (and the SPF record was not guessed), unless the domain is listed in the
|
||||||
|
"reject_neutral" option. Common forms of PTR records for dynamic IPs are
|
||||||
|
recognized, and do not count as a valid PTR. This does not prevent anyone
|
||||||
|
from sending mail from a dynamic IP - they just need to configure a
|
||||||
|
valid HELO name or publish an SPF record.
|
||||||
|
<p>
|
||||||
|
As SPF adoption continues to rise, forged spam is not getting through. So
|
||||||
|
spammers are publishing their SPF records as predicted. The 0.7.2 RPM
|
||||||
|
now provides the <code>rhsbl</code> sendmail hack so that spammer domains
|
||||||
|
can be blacklisted. With the RPM installed, add a line like the following
|
||||||
|
to your <code>sendmail.mc</code>.
|
||||||
|
<pre>
|
||||||
|
HACK(rhsbl,`blackholes.example.com',"550 Rejected: " $&{RHS} " has been spamming our customers.")dnl
|
||||||
|
</pre>
|
||||||
|
<p>
|
||||||
|
Of course, spammers are now starting to register
|
||||||
|
throwaway domains. The next thing we need is a custom DNS server,
|
||||||
|
in Python, that
|
||||||
|
can recognize patterns. For instance, one spammer registers ded304.com,
|
||||||
|
ded305.com, ded306.com, etc. We also need the custom DNS server to
|
||||||
|
let SPF classic clients check SES (which will be part of pysrs).
|
||||||
|
The <a href="http://twistedmatrix.com/products/twisted">Twisted Python</a>
|
||||||
|
framework provides a custom DNS server - but I
|
||||||
|
would like a smaller implementation for our use.
|
||||||
|
<p>
|
||||||
|
The RPM for release 0.7.0 moves the config file and socket locations to
|
||||||
|
/etc/mail and /var/run/milter respectively. We now parse Microsoft CID records
|
||||||
|
- but only hotmail.com uses them. They seem to have applied for a patent on
|
||||||
|
the brilliant idea of examining the mail headers to see who the message is
|
||||||
|
from. We aren't doing that here, so not to worry - but I am not a lawyer, so
|
||||||
|
if you are worried, change spf.py around line 626 to return None instead of
|
||||||
|
calling CIDParser(). There is a new option to reject mail with no PTR
|
||||||
|
and no SPF.
|
||||||
|
<p>
|
||||||
|
Microsoft is pushing an anti-opensource license for their pending patent
|
||||||
|
along with their sender-ID proposal before the IETF.
|
||||||
|
It is royalty free - but requires anyone distributing a binary they've
|
||||||
|
compiled from source to sign a license agreement. The Apache Software
|
||||||
|
Foundation <a
|
||||||
|
href="http://www.apache.org/foundation/docs/sender-id-position.html"> explains
|
||||||
|
the problem with sender-ID</a>, and Debian <a
|
||||||
|
href="http://www.debian.org/News/2004/20040904">concurs</a>. Since
|
||||||
|
the <a href="http://download.microsoft.com/download/4/3/9/439b024b-09fd-44ee-8ff0-10e834004c36/senderid_FAQ.PDF">Microsoft license</a> is
|
||||||
|
<a href="http://www.circleid.com/article/732_0_1_0_C/">incompatible with free
|
||||||
|
software in general</a> and the <a
|
||||||
|
href="http://www.imc.org/ietf-mxcomp/mail-archive/msg03678.html">GPL in
|
||||||
|
particular</a>, Python milter will not be able to implement sender-ID in its
|
||||||
|
current form. This was, no doubt, Microsoft's intent all along.
|
||||||
|
<p>
|
||||||
|
Sender-ID attempts to do for RFC2822 headers what SPF does for RFC2821 headers.
|
||||||
|
Unlike SPF, it has never been tried, and is encumbered by a stupid patent. I
|
||||||
|
recommend ignoring it and continuing to implement and improve SPF until a
|
||||||
|
working and unencumbered proposal for RFC2822 headers surfaces.
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="http://spf.pobox.com">
|
||||||
|
<img src="SPF.gif" align=left alt="SPF logo"></a>
|
||||||
|
Release 0.6.6 adds support for <a href="http://spf.pobox.com/">SPF</a>,
|
||||||
|
a protocol to prevent forging of the envelope from address.
|
||||||
|
SPF support requires <a href="http://pydns.sourceforge.net/">pydns</a>.
|
||||||
|
The included spf.py module is an updated version of the original 1.6
|
||||||
|
version at <a href="http://www.wayforward.net/spf/">wayforward.net</a>.
|
||||||
|
The updated version tracks the draft RFC and test suite.
|
||||||
|
<p>
|
||||||
|
The FAQ addresses <a href="faq.html#spf">how to get started with SPF</a>.
|
||||||
|
<p>
|
||||||
|
Release 0.6.1 adds a full milter based dspam application.
|
||||||
|
<p>
|
||||||
|
I have selected the <a href="http://www.nuclearelephant.com/projects/dspam/">
|
||||||
|
dspam bayes filter project</a> and <a href="dspam.html">
|
||||||
|
packaged it for python</a>.
|
||||||
|
Release 0.6.0 offers a simple application of dspam I call "header triage",
|
||||||
|
which rejects messages with spammy headers.
|
||||||
|
To use header triage, you must have <a href="dspam.html">DSPAM</a> installed,
|
||||||
|
and select a dictionary that is well moderated by someone who gets
|
||||||
|
lots of spam. That dictionary can be used to block spam that is
|
||||||
|
obvious from the headers (e.g. X-Mailer and Subject) before it ties
|
||||||
|
up any more resources. I have yet to see any false positives from this
|
||||||
|
approach (check the milter log), but if there are, the sender will
|
||||||
|
get a REJECT with the message "Your message looks spammy."
|
||||||
|
|
||||||
|
<h2> Enough Already! </h2>
|
||||||
|
|
||||||
|
Nearly a dozen people have emailed me begging for a feature to copy
|
||||||
|
outgoing and/or incoming mail to a backup directory by user. Ok, it
|
||||||
|
looks like this is a most requested feature for 0.5.6. In the meantime,
|
||||||
|
here are some things to consider:
|
||||||
|
<ul>
|
||||||
|
<li> If you want to equivalent of a Bcc added to each message, this
|
||||||
|
is very easy to do in the python code for bms.py. See below.
|
||||||
|
<li> If you want to copy to a file in a directory (thus avoiding having to
|
||||||
|
set up aliases), this is slightly more involved. The bms.py milter already
|
||||||
|
copies the message to a temporary file for use in replacing the message body
|
||||||
|
when banned attachments are found. You have to open a file, and copy the
|
||||||
|
Mesage object to it in eom().
|
||||||
|
<li> Finally, you are probably aware that most email clients already
|
||||||
|
keep a copy of outgoing mail? Presumably there is a good reason for
|
||||||
|
keeping another copy on the server.
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
To Bcc a message, call <code>self.add_recipient(rcpt)</code> in envfrom after
|
||||||
|
determining whether you want to copy (e.g. whether the sender is local). For
|
||||||
|
example,
|
||||||
|
<pre>
|
||||||
|
def envfrom(...
|
||||||
|
...
|
||||||
|
if len(t) == 2:
|
||||||
|
self.rejectvirus = t[1] in reject_virus_from
|
||||||
|
if t[0] in wiretap_users.get(t[1],()):
|
||||||
|
self.add_recipient(wiretap_dest)
|
||||||
|
if t[1] == 'mydomain.com':
|
||||||
|
self.add_recipient('<copy-%s>' % t[0])
|
||||||
|
...
|
||||||
|
</pre>
|
||||||
|
<p>
|
||||||
|
To make this a generic feature requires thinking about how the configuration
|
||||||
|
would look. Feel free to make specific suggestions about config file
|
||||||
|
entries. Be sure to handle both Bcc and file copies, and designating what
|
||||||
|
mail should be copied. How should "outgoing" be defined? Implementing it is
|
||||||
|
easy once the configuration is designed.
|
||||||
|
|
||||||
|
<h3><a name=overview>Overview</a></h3>
|
||||||
|
|
||||||
|
This package provides a robust toolkit for Python <a
|
||||||
|
href="#milter">milters</a>, and the beginnings of a general purpose mail
|
||||||
|
filtering system written in Python.
|
||||||
|
<p>
|
||||||
|
At the lowest level, the 'milter' module provides a thin wrapper around the
|
||||||
|
<a href="http://www.milter.org/milter_api/api.html">
|
||||||
|
sendmail libmilter API</a>. This API lets you register callbacks for
|
||||||
|
a number of events in the
|
||||||
|
<a href="http://www.cs.concordia.ca/~group/fig/public/email/relay/milter+ruleset-checks.html">process of sendmail receiving a message via SMTP</a>.
|
||||||
|
These events include the initial connection from a MTA,
|
||||||
|
the envelope sender and recipients, the top level mail headers, and
|
||||||
|
the message body. There are options to mangle all of these components
|
||||||
|
of the message as it passes through the milter.
|
||||||
|
<p>
|
||||||
|
At the next level, the 'Milter' module (note the case difference) provides a
|
||||||
|
Python friendly object oriented wrapper for the low level API. To use the
|
||||||
|
Milter module, an application registers a 'factory' to create an object
|
||||||
|
for each connection from a MTA to sendmail. These connection objects
|
||||||
|
must provide methods corresponding to the libmilter callback events.
|
||||||
|
<p>
|
||||||
|
Each event method returns a code to tell sendmail whether to proceed
|
||||||
|
with processing the message. This is a big advantage of milters over
|
||||||
|
other mail filtering systems. Unwanted mail can be stopped in its
|
||||||
|
tracks at the earliest possible point.
|
||||||
|
<p>
|
||||||
|
The Milter.Milter class provides default implementations for event
|
||||||
|
methods that
|
||||||
|
do nothing, and also provides wrappers for the libmilter methods to mutate
|
||||||
|
the message.
|
||||||
|
<p>
|
||||||
|
The 'spf' module provides an implementation of <a href="http://spf.pobox.com">
|
||||||
|
SPF</a> useful for detecting email forgery.
|
||||||
|
<p>
|
||||||
|
The 'mime' module provides a wrapper for the Python email package that
|
||||||
|
fixes some bugs, and simplifies modifying selected parts of a MIME message.
|
||||||
|
<p>
|
||||||
|
Finally, the bms.py application is both a sample of how to use the
|
||||||
|
Milter and spf modules, and the beginnings of a general purpose SPAM filtering,
|
||||||
|
wiretapping, SPF checking, and Win32 virus protecting milter. It can
|
||||||
|
make use of the <a href="pysrs.html">pysrs</a> package when available for
|
||||||
|
SRS/SES checking and the <a href="dspam.html">pydspam</a> package for Bayesian
|
||||||
|
content filtering. SPF checking
|
||||||
|
requires <a href="http://pydns.sourceforge.net/">
|
||||||
|
pydns</a>. Configuration documentation is currently included as comments
|
||||||
|
in the <a href="milter.cfg">sample config file</a> for the bms.py milter.
|
||||||
|
|
||||||
|
<h3><a name=download>Downloading</a></h3>
|
||||||
|
|
||||||
|
The latest stable release is <a href="#stable">0.7.2</a>. A stable
|
||||||
|
release is one which has been installed (and working correctly) on
|
||||||
|
production systems long enough to convince me that it is stable. As
|
||||||
|
the package gains more features and complexity, stable will mean no
|
||||||
|
bug reports from outside users either.
|
||||||
|
<p>
|
||||||
|
The latest version is 0.7.2-2. See the <a href=NEWS>Change Log</a>.
|
||||||
|
PLEASE NOTE - if you are using the modules, but not the bms milter application,
|
||||||
|
then ignore the RPMs and milter.spec. Use 'python setup.py bdist_rpm' to
|
||||||
|
build source and binary rpms that do not include the milter application.
|
||||||
|
<p>
|
||||||
|
I want to split the bms milter application to a new project once I figure
|
||||||
|
out the renaming. The current plan is to rename 'milter' to 'pymilter', which
|
||||||
|
will have the Python modules. The bms milter application will still be named
|
||||||
|
'milter' and depend on pymilter (so that my installs won't notice anything).
|
||||||
|
<p>
|
||||||
|
<a name="stable"><b>Stable</b></a>
|
||||||
|
<a href="http://bmsi.com/python/milter-0.7.2.tar.gz">
|
||||||
|
milter-0.7.2.tar.gz</a> Three strikes and your out policy. Some SPF fixes.
|
||||||
|
Recognizes PTR records for dynamic IPs.
|
||||||
|
<br>
|
||||||
|
<a href="http://bmsi.com/linux/rh72/milter-0.7.2-2.i386.rpm">
|
||||||
|
milter-0.7.2-2.i386.rpm</a> Binary RPM for Redhat 7.x, now requires
|
||||||
|
sendmail-8.12 and <a href="http://www.python.org/2.3.3/rpms.html">
|
||||||
|
python2.3</a>.
|
||||||
|
<br>
|
||||||
|
<a href="http://bmsi.com/linux/rh9/milter-0.7.2-2rh9.i386.rpm">
|
||||||
|
milter-0.7.2-2rh9.i386.rpm</a> Binary RPM for Redhat 9, now requires
|
||||||
|
sendmail-8.12 and <a href="http://www.python.org/2.3.3/rpms.html">
|
||||||
|
python2.3</a>.
|
||||||
|
<br>
|
||||||
|
<a href="http://bmsi.com/linux/rh9/milter-0.7.2-2.src.rpm">
|
||||||
|
milter-0.7.2-2.src.rpm</a> Source RPM for Redhat 9,7.x.
|
||||||
|
<p>
|
||||||
|
<a href="http://bmsi.com/python/milter-0.7.1.tar.gz">
|
||||||
|
milter-0.7.1.tar.gz</a> Support setmlreply, handle some more exceptions
|
||||||
|
for malformed spam. Compiling pymilter with sendmail-8.12.10, requires
|
||||||
|
sendmail-devel with _FFR_MULTILINE set. The binary will work with older
|
||||||
|
sendmails. The _FFR_MULTILINE option only affects libmilter.a.
|
||||||
|
<br>
|
||||||
|
<a href="http://bmsi.com/linux/rh72/milter-0.7.1-1.i386.rpm">
|
||||||
|
milter-0.7.1-1.i386.rpm</a> Binary RPM for Redhat 7.x, now requires
|
||||||
|
sendmail-8.12 and <a href="http://www.python.org/2.3.3/rpms.html">
|
||||||
|
python2.3</a>.
|
||||||
|
<br>
|
||||||
|
<a href="http://bmsi.com/linux/rh9/milter-0.7.1-1.src.rpm">
|
||||||
|
milter-0.7.1-1.src.rpm</a> Source RPM for Redhat 9,7.x.
|
||||||
|
<p>
|
||||||
|
<a href="http://bmsi.com/python/milter-0.7.0.tar.gz">
|
||||||
|
milter-0.7.0.tar.gz</a> Move config file and default socket location.
|
||||||
|
Parse M$ CID records.
|
||||||
|
<br>
|
||||||
|
<a href="http://bmsi.com/linux/rh72/milter-0.7.0-1.i386.rpm">
|
||||||
|
milter-0.7.0-1.i386.rpm</a> Binary RPM for Redhat 7.x, now requires
|
||||||
|
sendmail-8.12 and <a href="http://www.python.org/2.3.3/rpms.html">
|
||||||
|
python2.3</a>.
|
||||||
|
<br>
|
||||||
|
<a href="http://bmsi.com/linux/rh9/milter-0.7.0-1rh9.i386.rpm">
|
||||||
|
milter-0.7.0-1rh9.i386.rpm</a> Binary RPM for Redhat 9, requires
|
||||||
|
sendmail-8.12 and <a href="http://www.python.org/2.3.3/rpms.html">
|
||||||
|
python2.3</a>.
|
||||||
|
<br>
|
||||||
|
<a href="http://bmsi.com/aix/milter-0.7.0-1.ppc.rpm">
|
||||||
|
milter-0.7.0-1.ppc.rpm</a> Binary RPM for AIX, requires sendmail-8.13.1.
|
||||||
|
<br>
|
||||||
|
<a href="http://bmsi.com/linux/rh9/milter-0.7.0-1.src.rpm">
|
||||||
|
milter-0.7.0-1.src.rpm</a> Source RPM for Redhat 9,7.x.
|
||||||
|
<p>
|
||||||
|
<a href="http://bmsi.com/python/milter-0.6.9.tar.gz">
|
||||||
|
milter-0.6.9.tar.gz</a> Add SPF test suite driver, and validate
|
||||||
|
spf.py against test suite. Add best_guess and get_header to spf.py.
|
||||||
|
Libmilter timeout option in config.
|
||||||
|
<br>
|
||||||
|
<a href="http://bmsi.com/linux/rh72/milter-0.6.9-1.i386.rpm">
|
||||||
|
milter-0.6.9-1.i386.rpm</a> Binary RPM for Redhat 7.x, now requires
|
||||||
|
sendmail-8.12 and <a href="http://www.python.org/2.3.3/rpms.html">
|
||||||
|
python2.3</a>.
|
||||||
|
<br>
|
||||||
|
<a href="http://bmsi.com/linux/rh9/milter-0.6.9-1.src.rpm">
|
||||||
|
milter-0.6.9-1.src.rpm</a> Source RPM for Redhat 9,7.x.
|
||||||
|
<p>
|
||||||
|
<a href="http://bmsi.com/python/milter-0.6.8.tar.gz">
|
||||||
|
milter-0.6.8.tar.gz</a> Include Received-SPF headers in Dspam analysis.
|
||||||
|
Fix sysv init for Redhat 9 and later. Reject bounces with multiple
|
||||||
|
recipients.
|
||||||
|
<br>
|
||||||
|
<a href="http://bmsi.com/python/milter-0.6.8.patch">milter-0.6.8.patch</a>
|
||||||
|
Last minutes fixes from production testing.
|
||||||
|
<p>
|
||||||
|
<a href="http://bmsi.com/linux/rh72/milter-0.6.8-3.i386.rpm">
|
||||||
|
milter-0.6.8-3.i386.rpm</a> Binary RPM for Redhat 7.x, now requires
|
||||||
|
sendmail-8.12 and <a href="http://www.python.org/2.3.3/rpms.html">
|
||||||
|
python2.3</a>.
|
||||||
|
<br>
|
||||||
|
<a href="http://bmsi.com/linux/rh9/milter-0.6.8-3.src.rpm">
|
||||||
|
milter-0.6.8-3.src.rpm</a> Source RPM for Redhat 9,7.x.
|
||||||
|
<p>
|
||||||
|
<a href="http://bmsi.com/python/milter-0.6.7.tar.gz">
|
||||||
|
milter-0.6.7.tar.gz</a> Explicit local socket bug,
|
||||||
|
<a href="http://spf.pobox.com/srs.html">SRS</a> forgery detection,
|
||||||
|
thread resource starvation detection.
|
||||||
|
SRS support requires <a href="http://bmsi.com/python/pysrs.html">pysrs</a>.
|
||||||
|
<p>
|
||||||
|
<a href="http://bmsi.com/linux/rh72/milter-0.6.7-3.i386.rpm">
|
||||||
|
milter-0.6.7-3.i386.rpm</a> Binary RPM for Redhat 7.x, now requires
|
||||||
|
sendmail-8.12 and <a href="http://www.python.org/2.3.3/rpms.html">
|
||||||
|
python2.3</a>.
|
||||||
|
<br>
|
||||||
|
<a href="http://bmsi.com/linux/rh72/milter-0.6.7-3.src.rpm">
|
||||||
|
milter-0.6.7-3.src.rpm</a> Source RPM for Redhat 7.x.
|
||||||
|
Release 0.6.7-3 patches:
|
||||||
|
<ul>
|
||||||
|
<li> Defang message/rfc822 content_type with boundary
|
||||||
|
<li> Support SPF delegation
|
||||||
|
<li> Reject neutral SPF result for selected domains
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
<a href="http://bmsi.com/python/milter-0.6.6.tar.gz">
|
||||||
|
milter-0.6.6.tar.gz</a> Plug another memory leak,
|
||||||
|
<a href="http://spf.pobox.com/">SPF</a> support, hello blacklist.
|
||||||
|
SPF support requires <a href="http://pydns.sourceforge.net/">pydns</a>.
|
||||||
|
NOTE - the spf.py module included is modified from the official 1.6
|
||||||
|
version at <a href="http://www.wayforward.net/spf/">wayforward.net</a>.
|
||||||
|
I neglected to add the CVS log. The changes are expanded result codes
|
||||||
|
and tolerating common method misspellings in SPF records. I have notified the
|
||||||
|
author, but haven't heard back. At some point, the RPM will
|
||||||
|
include the official pyspf tarball and apply patches.
|
||||||
|
<p>
|
||||||
|
<a href="http://bmsi.com/linux/rh72/milter-0.6.6-2.i386.rpm">
|
||||||
|
milter-0.6.6-2.i386.rpm</a> Binary RPM for Redhat 7.x, now requires
|
||||||
|
sendmail-8.12 and <a href="http://www.python.org/2.3.3/rpms.html">
|
||||||
|
python2.3</a>. Release 2 fixes sysv init script bug for python2.3.
|
||||||
|
<br>
|
||||||
|
<a href="http://bmsi.com/linux/rh72/milter-0.6.6-2.src.rpm">
|
||||||
|
milter-0.6.6-2.src.rpm</a> Source RPM for Redhat 7.x
|
||||||
|
<p>
|
||||||
|
<a href="http://bmsi.com/python/milter-0.6.5.tar.gz">
|
||||||
|
milter-0.6.5.tar.gz</a> Plug memory leak, progress reporting, trusted relay.
|
||||||
|
Redhat RPM now requires sendmail-8.12.
|
||||||
|
<p>
|
||||||
|
<a href="http://bmsi.com/linux/rh72/milter-0.6.5-2.i386.rpm">
|
||||||
|
milter-0.6.5-2.i386.rpm</a> Binary RPM for Redhat 7.x
|
||||||
|
<br>
|
||||||
|
<a href="http://bmsi.com/linux/rh72/milter-0.6.5-2.src.rpm">
|
||||||
|
milter-0.6.5-2.src.rpm</a> Source RPM for Redhat 7.x
|
||||||
|
<p>
|
||||||
|
<a href="http://bmsi.com/python/milter-0.6.4.tar.gz">
|
||||||
|
milter-0.6.4.tar.gz</a> Numerous Dspam fixes. Requires
|
||||||
|
<a href="dspam.html">pydspam-1.1.5</a> and
|
||||||
|
<a href="/libdspam/dspam.html">dspam-2.6.5.2</a>
|
||||||
|
for Dspam features. The dspam-python RPM has been replaced by pydspam.
|
||||||
|
<p>
|
||||||
|
<a href="http://bmsi.com/linux/rh72/milter-0.6.4-1.i386.rpm">
|
||||||
|
milter-0.6.4-1.i386.rpm</a> Binary RPM for Redhat 7.x
|
||||||
|
<p>
|
||||||
|
<a href="http://bmsi.com/python/milter-0.6.3.1.tar.gz">
|
||||||
|
milter-0.6.3.1.tar.gz</a> New dspam SCREENER feature with pydspam-1.1.4.
|
||||||
|
Don't save a defang copy of false positives. Fixed an oops from last fix,
|
||||||
|
rejecting false positives. BUG: sendmail-8.11 doesn't invoke milter
|
||||||
|
when sending mail via sendmail from command line (8.12 works). Therefore,
|
||||||
|
the supplied falsepositive script for milter based dspam doesn't work
|
||||||
|
with stock RedHat 7.x. I am writing a HOWTO for configuring milter
|
||||||
|
based dspam that will address this (and a fix in the next version).
|
||||||
|
<p>
|
||||||
|
<a href="http://bmsi.com/linux/rh72/milter-0.6.3-1.i386.rpm">
|
||||||
|
milter-0.6.3-1.i386.rpm</a> Binary RPM for Redhat 7.x
|
||||||
|
<p>
|
||||||
|
<a href="http://bmsi.com/python/milter-0.6.2.tar.gz">
|
||||||
|
milter-0.6.2.tar.gz</a> work around email.Message.get_filename bug,
|
||||||
|
dspam_exempt list, REJECT messages with missing MIME boundaries (which
|
||||||
|
are almost always spam),
|
||||||
|
DISCARD messages which any dspam user flags as spam,
|
||||||
|
start.sh was calling python instead of python2 on Linux.
|
||||||
|
<p>
|
||||||
|
<a href="http://bmsi.com/linux/rh72/milter-0.6.2-1.src.rpm">
|
||||||
|
milter-0.6.2-1.src.rpm</a> Source RPM for Redhat 7.x (and likely
|
||||||
|
higher versions)
|
||||||
|
<p>
|
||||||
|
<a href="http://bmsi.com/python/milter-0.6.1.tar.gz">
|
||||||
|
milter-0.6.1.tar.gz</a> dspam milter application, python-2.2.3 support.
|
||||||
|
<p>
|
||||||
|
You must have <a href=dspam.html>dspam and dspam-python</a> loaded for
|
||||||
|
the dspam feature to work. Brief instructions for configuring are
|
||||||
|
in the default config file. This is working at a customer, but I'm
|
||||||
|
sure a few more iterations will be required to make setup as smooth
|
||||||
|
as possible.
|
||||||
|
<p>
|
||||||
|
NOTE: Outlook destroys dspam tags when forwarding mail (while converting
|
||||||
|
HTML to text). Perhaps some config option will turn this abominable
|
||||||
|
"feature" off. Working around this by making dspam tags visble on
|
||||||
|
HTML mail is ugly. My suggestion is to not use Outlook, for this and
|
||||||
|
many other reasons - especially security. Any other suggestions for
|
||||||
|
those married to Microsoft are welcome. The DSPAM LDA works around this
|
||||||
|
by making the tags visible in HTML attachments. This is ugly, and
|
||||||
|
occasionally corrupts attachments.
|
||||||
|
<p>
|
||||||
|
We have to supply workarounds for bugs in the email module (reported
|
||||||
|
to sourceforge). The workarounds reference some internal variables
|
||||||
|
which change with python versions.
|
||||||
|
<p>
|
||||||
|
<a href="http://bmsi.com/linux/rh72/milter-0.6.1-1.i386.rpm">
|
||||||
|
milter-0.6.1-1.i386.rpm</a> Binary RPM for Redhat 7.x
|
||||||
|
<p>
|
||||||
|
<a href="http://bmsi.com/linux/rh72/milter-0.6.1-1.src.rpm">
|
||||||
|
milter-0.6.1-1.src.rpm</a> Source RPM for Redhat 7.x (and likely
|
||||||
|
higher versions)
|
||||||
|
<p>
|
||||||
|
<a href="http://bmsi.com/python/milter-0.6.0.tar.gz">
|
||||||
|
milter-0.6.0.tar.gz</a> simple dspam pre-filtering, use email module,
|
||||||
|
requires python >= 2.2.2.
|
||||||
|
<ul>
|
||||||
|
<li> The milter.so module from 0.5.4
|
||||||
|
is needed to run this release on AIX. Haven't tracked this down yet.
|
||||||
|
<li> The patches to fix the email packages in mime.py don't work
|
||||||
|
on python-2.2.3. The email package is still broken in 2.3, and patches
|
||||||
|
required for that will likely be different still.
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="http://bmsi.com/linux/rh72/milter-0.6.0-1.i386.rpm">
|
||||||
|
milter-0.6.0-1.i386.rpm</a> Binary RPM for Redhat 7.x
|
||||||
|
<p>
|
||||||
|
<a href="http://bmsi.com/linux/rh72/milter-0.6.0-1.src.rpm">
|
||||||
|
milter-0.6.0-1.src.rpm</a> Source RPM for Redhat 7.x (and likely
|
||||||
|
higher versions)
|
||||||
|
<p>
|
||||||
|
<a href="http://www.bmsi.com/python/milter-0.5.5.tar.gz">
|
||||||
|
milter-0.5.5.tar.gz</a> IPV6 support, passing None to set_XXX_callback,
|
||||||
|
set_reply, chg_header, detect internal connections. Note, this release
|
||||||
|
did not work on AIX4.1.5, probably due to IPV6 support breaking something.
|
||||||
|
The milter.so module from 0.5.4 can be installed to use this release
|
||||||
|
with AIX.
|
||||||
|
<p>
|
||||||
|
<a href="http://www.bmsi.com/python/milter-0.5.4.tar.gz">
|
||||||
|
milter-0.5.4.tar.gz</a> wiretap, smart alias features, quarantine support.
|
||||||
|
<p>
|
||||||
|
The name of the production "sample" milter "bms.py" now
|
||||||
|
stands for "Basic Milter System" until someone suggests a better name.
|
||||||
|
The test coverage is rather
|
||||||
|
sparse at present.
|
||||||
|
Please <a href="mailto:%73%74%75%61%72%74%40%62%6D%73%69%2E%63%6F%6D">email</a> with proposals for what
|
||||||
|
to name the milter application.
|
||||||
|
<h4>NOTES</h4>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Quarantine support requires that you define _FFR_QUARANTINE
|
||||||
|
when compiling miltermodule.c. I am not sure how to make setup.py
|
||||||
|
do that for you iff sendmail was actually compiled with _FFR_QUARANTINE.
|
||||||
|
<li>
|
||||||
|
While 0.6.0 will use the new email package in Python-2.2, that
|
||||||
|
package seems to be buggy in Python-2.2.1. The list example in the docs
|
||||||
|
doesn't find all MIME parts. Update: Python-2.2.2 has fixed the email
|
||||||
|
package. It can now parse my test cases.
|
||||||
|
<li>
|
||||||
|
Preliminary testing with python-2.2 shows that most things work after
|
||||||
|
adding <code>self.readahead = ""</code> to <code>mimepart.seek</code>.
|
||||||
|
Python-2.2 <code>multifile</code> reads one less newline per section than
|
||||||
|
2.1. I'm not not sure which is correct. After adding some calls to
|
||||||
|
<code>rstrip()</code> in testmime.py, all milter modules pass unit testing
|
||||||
|
with python-2.2. Python-2.2 patches have been released since 0.5.3.
|
||||||
|
<li>
|
||||||
|
sgmlop-1.1a3 has a memory leak (at least Python milter has a
|
||||||
|
memory leak when using sgmlop instead of sgmllib). Do not make Python
|
||||||
|
milter use sgmlop-1.1a2 or a3 in a production
|
||||||
|
system unless you can restart your milter periodically. The amount
|
||||||
|
of memory leaked seems roughly proportional to the amount of HTML
|
||||||
|
parsed.
|
||||||
|
<li>
|
||||||
|
There are a number of ways that malformed MIME attachments
|
||||||
|
can cause a python traceback. Uncaught exceptions cause a 415
|
||||||
|
error to be returned to sendmail. So far, all the malformed messages
|
||||||
|
I've investigated have been SPAM - so good riddance. I would prefer,
|
||||||
|
however, that the mime handling libraries were more precise. Beginning
|
||||||
|
with 0.5.1, bms.py will save messages that cause a traceback during
|
||||||
|
scanning in the tempfile directory with a ".fail" extension. This
|
||||||
|
makes it easier to get samples of mail that causes parsing problems
|
||||||
|
for incorporation into the unit tests.
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
<a href="http://www.bmsi.com/python/milter-0.5.2.tar.gz">
|
||||||
|
milter-0.5.2.tar.gz</a> Fix and unittest another HTML parsing bug.<br>
|
||||||
|
<a href="http://www.bmsi.com/python/milter-0.5.1.tar.gz">
|
||||||
|
milter-0.5.1.tar.gz</a> Handle encoded rfc822 attachments.<br>
|
||||||
|
<a href="http://www.bmsi.com/python/milter-0.5.0.tar.gz">
|
||||||
|
milter-0.5.0.tar.gz</a> Use a config file so users don't have to
|
||||||
|
keep syncing with bms.py. <br>
|
||||||
|
<a href="http://www.bmsi.com/python/milter-0.4.5.tar.gz">
|
||||||
|
milter-0.4.5.tar.gz</a> Work with sgmlop. Reduce local hacks to config variables.
|
||||||
|
<p>
|
||||||
|
Python milter is under GPL. The authors can probably be convinced to
|
||||||
|
change this to LGPL.
|
||||||
|
|
||||||
|
<h3>What is a <a name="milter">milter</a>?</h3>
|
||||||
|
|
||||||
|
Milters can run on the same machine as sendmail, or another machine. The
|
||||||
|
milter can even run with a different operating system or processor than
|
||||||
|
sendmail.
|
||||||
|
Sendmail talks to the milter via a local or internet socket.
|
||||||
|
Sendmail keeps the
|
||||||
|
milter informed of events as it processes a mail connection. At any
|
||||||
|
point, the milter can cut the conversation short by telling sendmail
|
||||||
|
to ACCEPT, REJECT, or DISCARD the message. After receiving a complete
|
||||||
|
message from sendmail, the milter can again REJECT or DISCARD it, but it
|
||||||
|
can also ACCEPT it with changes to the headers or body.
|
||||||
|
|
||||||
|
<h3> What can you do with a milter? </h3>
|
||||||
|
|
||||||
|
<menu>
|
||||||
|
<li> A milter can DISCARD or REJECT spam based based on algorithms scripted
|
||||||
|
in python rather than sendmail's cryptic "cf" language.
|
||||||
|
<li> A milter can alter or remove attachments from mail that are poisonous to
|
||||||
|
Windows.
|
||||||
|
<li> A milter can scan for viruses and clean them when detected.
|
||||||
|
<li> A milter scans outgoing as well as incoming mail.
|
||||||
|
<li> A milter can add and delete recipients to forward or secretly
|
||||||
|
copy mail.
|
||||||
|
<li> For more ideas, check the <a href="//www.milter.org">Milter Web Page</a>.
|
||||||
|
</menu>
|
||||||
|
|
||||||
|
<a href="http://www.milter.org/milter_api/api.html">
|
||||||
|
Documentation</a> for the C API is provided with sendmail. Miltermodule
|
||||||
|
provides a thin python wrapper for the C API. Milter.py provides a simple
|
||||||
|
OO wrapper on top of that.
|
||||||
|
<p>
|
||||||
|
The Python milter package includes a sample milter that replaces dangerous
|
||||||
|
attachments with a warning message, discards mail addressed to
|
||||||
|
MAILER-DAEMON, and demonstrates several SPAM abatement strategies.
|
||||||
|
The MimeMessage class to do this used to be based on the
|
||||||
|
<code>mimetools</code> and <code>multifile</code> standard python packages.
|
||||||
|
As of milter version 0.6.0, it is based on the email standard
|
||||||
|
python packages, which were derived from the
|
||||||
|
<a href="http://sourceforge.net/projects/mimelib">mimelib</a> project.
|
||||||
|
The MimeMessage class patches several bugs in the email package,
|
||||||
|
and provides some backward compatibility.
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The "defang" function of the sample milter was inspired by
|
||||||
|
<a href="http://www.roaringpenguin.com/mimedefang/">MIMEDefang</a>,
|
||||||
|
a Perl milter with flexible attachment processing options. The latest
|
||||||
|
version of MIMEDefang uses an apache style process pool to avoid reloading
|
||||||
|
the Perl interpreter for each message. This makes it fast enough for
|
||||||
|
production without using Perl threading.
|
||||||
|
<p>
|
||||||
|
<a href="http://sourceforge.net/projects/mailchecker">mailchecker</a> is
|
||||||
|
a Python project to provide flexible attachment processing for mail. I
|
||||||
|
will be looking at plugging mailchecker into a milter.
|
||||||
|
<p>
|
||||||
|
<a href="http://software.libertine.org/tmda/">TMDA</a> is a Python project
|
||||||
|
to require confirmation the first time someone tries to send to your
|
||||||
|
mailbox. This would be a nice feature to have in a milter.
|
||||||
|
<p>
|
||||||
|
There is also a <a href="http://www.milter.org/">Milter community website</a>
|
||||||
|
where milter software and gory details of the API are discussed.
|
||||||
|
|
||||||
|
<h3> Is a milter written in python efficient? </h3>
|
||||||
|
|
||||||
|
The python milter process is multi-threaded and startup cost is incurred
|
||||||
|
only once. This is much more efficient than some implementations that
|
||||||
|
start a new interpreter for each connection. Testing in a production
|
||||||
|
environment did not use a significant percentage of the CPU. Furthermore,
|
||||||
|
python is easily extended in C for any step requiring expensive CPU
|
||||||
|
processing.
|
||||||
|
<p>
|
||||||
|
For example, the HTML parsing feature to remove scripts from HTML attachments
|
||||||
|
is rather CPU intensive in pure python. Using the C replacement for sgmllib
|
||||||
|
greatly speeds things up.
|
||||||
|
|
||||||
|
<h3> Goals </h3>
|
||||||
|
|
||||||
|
<menu>
|
||||||
|
<li> Implement RRS - a backdoor for non-SRS forwarders. User lists non-SRS
|
||||||
|
forwarder accounts (perhaps in <code>~/.forwarders</code>), and a util
|
||||||
|
provides a special local alias for the user to give to the forwarder.
|
||||||
|
Alias only works for mail from that forwarder. Milter gets forwarder
|
||||||
|
domain from alias and uses it to SPF check forwarder. Requires
|
||||||
|
milter to have read access to <code>~/.forwarders</code> or else
|
||||||
|
a way for user to submit entries to milter database.
|
||||||
|
<li> The bms.py milter has too many features. Create a framework where
|
||||||
|
numerous small feature modules can be plugged together in the
|
||||||
|
configuration.
|
||||||
|
<li> Create a pure python substitute for miltermodule and libmilter that
|
||||||
|
implements the <a
|
||||||
|
href="http://www.duh.org/cvsweb.cgi/~checkout~/pmilter/doc/milter-protocol.txt?rev=1">
|
||||||
|
libmilter protocol</a> in python.
|
||||||
|
<li> Find or write a faster implementation of sgmllib. The
|
||||||
|
<a href="http://www.effbot.org/zone/sgmlop-index.htm">sgmlop package</a>
|
||||||
|
is not very compatible with
|
||||||
|
<a href="http://www.python.org/doc/2.1.3/lib/module-sgmllib.html">
|
||||||
|
Python-2.1 sgmllib</a>, but it is a start, and is supported in
|
||||||
|
milter-0.4.5 or later.
|
||||||
|
<li> Implement all or most of the features of
|
||||||
|
<a href="http://www.roaringpenguin.com/mimedefang/">MIMEDefang</a>.
|
||||||
|
<li> Follow the official <a href="http://www.python.org/peps/pep-0008.html">
|
||||||
|
Python coding standards</a> more closely.
|
||||||
|
<li> Make unit test code more like other python modules.
|
||||||
|
</menu>
|
||||||
|
|
||||||
|
<h3> Confirmed Installations </h3>
|
||||||
|
|
||||||
|
Please <a href="mailto:%73%74%75%61%72%74%40%62%6D%73%69%2E%63%6F%6D">email</a>
|
||||||
|
me if you successfully install milter on a system not mentioned below.
|
||||||
|
<p>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Operating System</th> <th>Compiler</th> <th>Python</th> <th>Sendmail</th>
|
||||||
|
<th>milter</th>
|
||||||
|
<tr>
|
||||||
|
<td>Mandrake 8.0</td><td>gcc-3.0.1</td><td>2.1.1</td><td>8.12.0</td>
|
||||||
|
<td>0.3.3</td><tr>
|
||||||
|
<td>Mandrake 8.0</td><td>gcc-2.96</td><td>2.0</td><td>8.11.2</td>
|
||||||
|
<td>0.3.6</td><tr>
|
||||||
|
<td>RedHat 6.2</td><td>egcs-1.1.2</td><td>2.2.2</td><td>8.11.6</td>
|
||||||
|
<td>0.5.4</td><tr>
|
||||||
|
<td>RedHat 7.1</td><td>gcc-2.96</td><td>?</td><td>8.12.1</td>
|
||||||
|
<td>0.3.5</td><tr>
|
||||||
|
<td>RedHat 7.3</td><td>gcc-2.96</td><td>2.2.2</td><td>8.11.6</td>
|
||||||
|
<td>0.5.5</td><tr>
|
||||||
|
<td>RedHat 7.3</td><td>gcc-2.96</td><td>2.3.3</td><td>8.13.1</td>
|
||||||
|
<td>0.7.2</td><tr>
|
||||||
|
<td>RedHat 8.0</td><td>gcc-3.2</td><td>2.2.1</td><td>8.12.6</td>
|
||||||
|
<td>0.5.2</td><tr>
|
||||||
|
<td>Debian Linux</td><td>gcc-2.95.2</td><td>2.1.1</td><td>8.12.0</td>
|
||||||
|
<td>0.3.7</td><tr>
|
||||||
|
<td>Debian Linux</td><td>gcc-3.2.2</td><td>2.2.2</td><td>8.12.7</td>
|
||||||
|
<td>0.5.4</td><tr>
|
||||||
|
<td>AIX-4.1.5</td><td>gcc-2.95.2</td><td>2.1.1</td><td>8.11.5</td>
|
||||||
|
<td>0.3.3</td><tr>
|
||||||
|
<td>AIX-4.1.5</td><td>gcc-2.95.2</td><td>2.1.1</td><td>8.12.1</td>
|
||||||
|
<td>0.3.4</td><tr>
|
||||||
|
<td>AIX-4.1.5</td><td>gcc-2.95.2</td><td>2.1.3</td><td>8.12.3</td>
|
||||||
|
<td>0.4.2</td><tr>
|
||||||
|
<td>AIX-4.1.5</td><td>gcc-2.95.2</td><td>2.2.3</td><td>8.13.1</td>
|
||||||
|
<td>0.7.1</td><tr>
|
||||||
|
<td>Slackware 7.1</td><td>?</td><td>?</td><td>8.12.1</td>
|
||||||
|
<td>0.3.8</td><tr>
|
||||||
|
<td>Slackware 9.0</td><td>gcc-3.2.2</td><td>2.2.3</td><td>8.12.9</td>
|
||||||
|
<td>0.5.4</td><tr>
|
||||||
|
<td>OpenBSD</td><td>?</td><td>2.3.3?</td><td>8.13.1?</td>
|
||||||
|
<td>0.7.2</td><tr>
|
||||||
|
<td>SuSE 7.3</td><td>gcc-2.95.3</td><td>2.1.1</td><td>8.12.2</td>
|
||||||
|
<td>0.3.9</td><tr>
|
||||||
|
<td>FreeBSD</td><td>gcc-2.95.3</td><td>2.2.1</td><td>8.12.3</td>
|
||||||
|
<td>0.4.0</td><tr>
|
||||||
|
<td>FreeBSD</td><td>gcc-2.95.3</td><td>2.2.2</td><td>?</td>
|
||||||
|
<td>0.5.5</td><tr>
|
||||||
|
<td>FreeBSD 4.4</td><td>gcc-2.95.3</td><td>?</td><td>8.12.10</td>
|
||||||
|
<td>0.6.6</td><tr>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3> Requirements </h3>
|
||||||
|
|
||||||
|
<menu>
|
||||||
|
<li> While the miltermodule will work with python 1.5, you probably
|
||||||
|
want to use python 2.0 or better. The python code uses a number of
|
||||||
|
python 2 features.
|
||||||
|
<li> Python must be configured with thread support. This is because
|
||||||
|
sendmail's libmilter requires thread support.
|
||||||
|
<li> You must compile sendmail with libmilter enabled. In versions of
|
||||||
|
sendmail prior to 8.12 libmilter is marked FFR (For Future Release) and
|
||||||
|
is not installed by default.
|
||||||
|
Sendmail 8.12 still does not enable libmilter by default. You must
|
||||||
|
explicitly select the "MILTER" option when compiling.
|
||||||
|
<li> Python milter has been tested against sendmail-8.11 and sendmail-8.12.
|
||||||
|
<li> Python milter must be compiled for the specific version of sendmail
|
||||||
|
it will run with. (Since the result is dynamically loaded, there could
|
||||||
|
conceivably be multiple versions available and selected at startup - but
|
||||||
|
that will have to wait.) This situation may only exist for sendmail
|
||||||
|
versions prior to 8.12. The protocol seems designed for backward
|
||||||
|
compatibility - and 8.12 is the first official milter release.
|
||||||
|
<li> Mea Culpa! After reading the Python Style guide, I realize that
|
||||||
|
my Python code is not up to snuff. Apparently mixed tabs and spaces
|
||||||
|
are anathema to those using Windows editors, where tabs can be expanded using
|
||||||
|
any arbitrary algorithm. Other than that, my
|
||||||
|
intuition matched Guido's pretty well - although I like to indent by 2
|
||||||
|
rather than 4. I will arrange to have tabs expanded to spaces when
|
||||||
|
exporting new versions. Until then, beware!
|
||||||
|
</menu>
|
||||||
|
|
||||||
|
<h3> <a name="aix4"> AIX 4.1.5 Requirements </a> </h3>
|
||||||
|
To create sendmail RPMs for AIX, you can download my AIX 4.1.5 spec files
|
||||||
|
for <a href="/aix/sendmail.spec">sendmail-8.11.5</a>
|
||||||
|
or <a href="/aix/sendmail12.spec">sendmail-8.12.3</a>. If you have
|
||||||
|
not already set it up, I use a <a href="/aix/aix.spec">dummy RPM package</a>
|
||||||
|
to represent the stuff that comes with AIX. You might also want
|
||||||
|
my <a href="/aix/python.spec">python-2.1.1</a> spec file for AIX. It
|
||||||
|
does not include Tk or curses modules, sorry. If y'all trust me, you can
|
||||||
|
download rpms for AIX 4.x from my <a href="/aix">AIX RPM directory</a>.
|
||||||
|
<p>
|
||||||
|
Sendmail-8.12 renames
|
||||||
|
libsmutil.a to libsm.a. Unfortunately, libsm.a is an important AIX system
|
||||||
|
shared library. Therefore, I rename libsm.a back to libsmutil.a for
|
||||||
|
AIX. This presents a problem for setup.py.
|
||||||
|
|
||||||
|
<h3> <a name="rh72"> RedHat 7.2 Requirements </a> </h3>
|
||||||
|
|
||||||
|
If you are running Redhat 7.2, the distributed version of sendmail
|
||||||
|
now enables libmilter by default. RedHat 7.2 bundles
|
||||||
|
the development libraries with the main sendmail package, so
|
||||||
|
there is no sendmail-devel package. However, they forgot to include the
|
||||||
|
headers! So you'll have to get the SRPM and modify it. I suggest
|
||||||
|
moving the static libs to a devel package and adding the headers. If
|
||||||
|
this is too much trouble, you can get the <a href="mfapi.h">mfapi.h</a>
|
||||||
|
header for sendmail-8.6.11 from here and manually install it as
|
||||||
|
<code>/usr/include/libmilter/mfapi.h</code>.
|
||||||
|
<p>
|
||||||
|
If you do modify the SRPM, I suggest renaming libsmutil.a
|
||||||
|
to libsm.a - just like sendmail-8.12 will. If you manually install
|
||||||
|
mfapi.h or don't rename libsmutil.a, you'll
|
||||||
|
need to force <code>libs = ["milter", "smutil"]</code> in setup.py.
|
||||||
|
<p>
|
||||||
|
If you have installed python2, and want
|
||||||
|
python-milter to use python2, add <code>python=python2</code> to setup.cfg
|
||||||
|
and build with <code>python2 setup.py bdist_rpm</code>.
|
||||||
|
|
||||||
|
<h3> <a name="rh62"> Redhat 6.2 Requirements </a> </h3>
|
||||||
|
|
||||||
|
If you are running Redhat 6.2, the distributed version of sendmail
|
||||||
|
does not enable libmilter. You can download the Redhat 7.2 sendmail.spec
|
||||||
|
modified to compile on RedHat 6.2:
|
||||||
|
<a href="http://www.bmsi.com/linux/rh62/sendmail-rhmilter.spec">
|
||||||
|
sendmail-rhmilter.spec</a>. The <a
|
||||||
|
href="ftp://updates.redhat.com/7.0/en/os/SRPMS/sendmail-8.11.6-1.7.0.src.rpm">
|
||||||
|
SRPM for sendmail-8.11.6</a> is available from
|
||||||
|
<a href="http://www.redhat.com">Redhat</a> under
|
||||||
|
<a href="http://www.redhat.com/support/errata/RHSA-2001-106.html">
|
||||||
|
Errata for RH6.2</a>. But that doesn't include the latest security
|
||||||
|
patches since RH6.2 is no longer supported.
|
||||||
|
<p>
|
||||||
|
If y'all trust me, you can pick up source and binary sendmail RPMs for RH6.2
|
||||||
|
from my <a href="http://www.bmsi.com/linux/rh62">linux downloads</a> directory.
|
||||||
|
The lastest RPMs were built by taking a RH7.2 SRPMS and removing some
|
||||||
|
RPM features from the spec file that RH6.2 doesn't support, then
|
||||||
|
recompiling on RH6.2. You can check this by installing the RH7.2 SRPM,
|
||||||
|
then diffing my sendmail.spec with theirs. Then run
|
||||||
|
"rpm -bb sendmail-rhmilter.spec" when you are satisfied.
|
||||||
|
<p>
|
||||||
|
If you have installed python2, and want
|
||||||
|
python-milter to use python2, add <code>python=python2</code> to setup.cfg
|
||||||
|
and build with <code>python2 setup.py bdist_rpm</code>.
|
||||||
|
You'll need to install the sendmail-devel package to compile milter.
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<p>
|
||||||
|
<a href="http://validator.w3.org/check/referer">
|
||||||
|
<img border=0 src="/vh32.png" alt=" [ Valid HTML 3.2! ] " height=31 width=88></a>
|
||||||
|
<a href="http://www.redhat.com">
|
||||||
|
<img src="/art/powered_by.gif" width="88" height="31" alt=" [ Powered By Red Hat Linux ] " border="0"></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</body></html>
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# milter This shell script takes care of starting and stopping milter.
|
||||||
|
#
|
||||||
|
# chkconfig: 2345 80 30
|
||||||
|
# description: Milter is a process that filters messages sent through sendmail.
|
||||||
|
# processname: milter
|
||||||
|
# config: /var/log/milter/bms.py
|
||||||
|
# pidfile: /var/run/milter/milter.pid
|
||||||
|
|
||||||
|
python="python2.3"
|
||||||
|
|
||||||
|
pidof() {
|
||||||
|
set - ""
|
||||||
|
if set - `ps -e -o pid,cmd | grep "${python} bms.py"` &&
|
||||||
|
[ "$2" != "grep" ]; then
|
||||||
|
echo $1
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Source function library.
|
||||||
|
. /etc/rc.d/init.d/functions
|
||||||
|
|
||||||
|
[ -x /var/log/milter/start.sh ] || exit 0
|
||||||
|
|
||||||
|
RETVAL=0
|
||||||
|
prog="milter"
|
||||||
|
|
||||||
|
start() {
|
||||||
|
# Start daemons.
|
||||||
|
|
||||||
|
echo -n "Starting $prog: "
|
||||||
|
daemon --check milter --user mail /var/log/milter/start.sh
|
||||||
|
RETVAL=$?
|
||||||
|
echo
|
||||||
|
[ $RETVAL -eq 0 ] && touch /var/lock/subsys/milter
|
||||||
|
return $RETVAL
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
# Stop daemons.
|
||||||
|
echo -n "Shutting down $prog: "
|
||||||
|
killproc milter
|
||||||
|
RETVAL=$?
|
||||||
|
echo
|
||||||
|
[ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/milter
|
||||||
|
return $RETVAL
|
||||||
|
}
|
||||||
|
|
||||||
|
# See how we were called.
|
||||||
|
case "$1" in
|
||||||
|
start)
|
||||||
|
start
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
stop
|
||||||
|
;;
|
||||||
|
restart|reload)
|
||||||
|
stop
|
||||||
|
start
|
||||||
|
RETVAL=$?
|
||||||
|
;;
|
||||||
|
condrestart)
|
||||||
|
if [ -f /var/lock/subsys/milter ]; then
|
||||||
|
stop
|
||||||
|
start
|
||||||
|
RETVAL=$?
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
status milter
|
||||||
|
RETVAL=$?
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $0 {start|stop|restart|condrestart|status}"
|
||||||
|
exit 1
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit $RETVAL
|
||||||
Executable
+81
@@ -0,0 +1,81 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# milter This shell script takes care of starting and stopping milter.
|
||||||
|
#
|
||||||
|
# chkconfig: 2345 80 30
|
||||||
|
# description: Milter is a process that filters messages sent through sendmail.
|
||||||
|
# processname: milter
|
||||||
|
# config: /var/log/milter/bms.py
|
||||||
|
# pidfile: /var/run/milter/milter.pid
|
||||||
|
|
||||||
|
python="python2.3"
|
||||||
|
|
||||||
|
pidof() {
|
||||||
|
set - ""
|
||||||
|
if set - `ps -e -o pid,wchan,cmd | grep "rt_sig ${python} bms.py"` &&
|
||||||
|
[ "$3" != "grep" ]; then
|
||||||
|
echo $1
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Source function library.
|
||||||
|
. /etc/rc.d/init.d/functions
|
||||||
|
|
||||||
|
[ -x /var/log/milter/start.sh ] || exit 0
|
||||||
|
|
||||||
|
RETVAL=0
|
||||||
|
prog="milter"
|
||||||
|
|
||||||
|
start() {
|
||||||
|
# Start daemons.
|
||||||
|
|
||||||
|
echo -n "Starting $prog: "
|
||||||
|
daemon --check milter --user mail /var/log/milter/start.sh
|
||||||
|
RETVAL=$?
|
||||||
|
echo
|
||||||
|
[ $RETVAL -eq 0 ] && touch /var/lock/subsys/milter
|
||||||
|
return $RETVAL
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
# Stop daemons.
|
||||||
|
echo -n "Shutting down $prog: "
|
||||||
|
killproc milter
|
||||||
|
RETVAL=$?
|
||||||
|
echo
|
||||||
|
[ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/milter
|
||||||
|
return $RETVAL
|
||||||
|
}
|
||||||
|
|
||||||
|
# See how we were called.
|
||||||
|
case "$1" in
|
||||||
|
start)
|
||||||
|
start
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
stop
|
||||||
|
;;
|
||||||
|
restart|reload)
|
||||||
|
stop
|
||||||
|
start
|
||||||
|
RETVAL=$?
|
||||||
|
;;
|
||||||
|
condrestart)
|
||||||
|
if [ -f /var/lock/subsys/milter ]; then
|
||||||
|
stop
|
||||||
|
start
|
||||||
|
RETVAL=$?
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
status milter
|
||||||
|
RETVAL=$?
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $0 {start|stop|restart|condrestart|status}"
|
||||||
|
exit 1
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit $RETVAL
|
||||||
+225
@@ -0,0 +1,225 @@
|
|||||||
|
%define name milter
|
||||||
|
%define version 0.8.0
|
||||||
|
%define release 2.EL3
|
||||||
|
# Redhat 7.x and earlier (multiple ps lines per thread)
|
||||||
|
#define sysvinit milter.rc7
|
||||||
|
# RH9, other systems (single ps line per process)
|
||||||
|
%define sysvinit milter.rc
|
||||||
|
%ifos Linux
|
||||||
|
%define python python2.4
|
||||||
|
%else
|
||||||
|
%define python python
|
||||||
|
%endif
|
||||||
|
|
||||||
|
Summary: Python interface to sendmail milter API
|
||||||
|
Name: %{name}
|
||||||
|
Version: %{version}
|
||||||
|
Release: %{release}
|
||||||
|
Source: %{name}-%{version}.tar.gz
|
||||||
|
#Patch: %{name}-%{version}.patch
|
||||||
|
Copyright: GPL
|
||||||
|
Group: Development/Libraries
|
||||||
|
BuildRoot: %{_tmppath}/%{name}-buildroot
|
||||||
|
Prefix: %{_prefix}
|
||||||
|
Vendor: Stuart D. Gathman <stuart@bmsi.com>
|
||||||
|
Packager: Stuart D. Gathman <stuart@bmsi.com>
|
||||||
|
Url: http://www.bmsi.com/python/milter.html
|
||||||
|
Requires: %{python} >= 2.4, sendmail >= 8.12.10
|
||||||
|
%ifnos aix4.1
|
||||||
|
Requires: chkconfig
|
||||||
|
%endif
|
||||||
|
BuildRequires: %{python}-devel >= 2.2.2, sendmail-devel >= 8.12.10
|
||||||
|
|
||||||
|
%description
|
||||||
|
This is a python extension module to enable python scripts to
|
||||||
|
attach to sendmail's libmilter functionality. Additional python
|
||||||
|
modules provide for navigating and modifying MIME parts.
|
||||||
|
|
||||||
|
%prep
|
||||||
|
%setup
|
||||||
|
#%patch -p1
|
||||||
|
|
||||||
|
%build
|
||||||
|
env CFLAGS="$RPM_OPT_FLAGS" %{python} setup.py build
|
||||||
|
|
||||||
|
%install
|
||||||
|
rm -rf $RPM_BUILD_ROOT
|
||||||
|
%{python} setup.py install --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES
|
||||||
|
mkdir -p $RPM_BUILD_ROOT/var/log/milter
|
||||||
|
mkdir -p $RPM_BUILD_ROOT/etc/mail
|
||||||
|
mkdir $RPM_BUILD_ROOT/var/log/milter/save
|
||||||
|
cp bms.py $RPM_BUILD_ROOT/var/log/milter
|
||||||
|
cp milter.cfg $RPM_BUILD_ROOT/etc/mail/pymilter.cfg
|
||||||
|
|
||||||
|
# logfile rotation
|
||||||
|
mkdir -p $RPM_BUILD_ROOT/etc/logrotate.d
|
||||||
|
cat >$RPM_BUILD_ROOT/etc/logrotate.d/milter <<'EOF'
|
||||||
|
/var/log/milter/milter.log {
|
||||||
|
copytruncate
|
||||||
|
compress
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# purge saved defanged message copies
|
||||||
|
mkdir -p $RPM_BUILD_ROOT/etc/cron.daily
|
||||||
|
%ifos aix4.1
|
||||||
|
R=
|
||||||
|
%else
|
||||||
|
R='-r'
|
||||||
|
%endif
|
||||||
|
cat >$RPM_BUILD_ROOT/etc/cron.daily/milter <<'EOF'
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
find /var/log/milter/save -mtime +7 | xargs $R rm
|
||||||
|
EOF
|
||||||
|
chmod a+x $RPM_BUILD_ROOT/etc/cron.daily/milter
|
||||||
|
|
||||||
|
%ifos aix4.1
|
||||||
|
cat >$RPM_BUILD_ROOT/var/log/milter/start.sh <<'EOF'
|
||||||
|
#!/bin/sh
|
||||||
|
cd /var/log/milter
|
||||||
|
# uncomment to enable sgmlop if installed
|
||||||
|
#export PYTHONPATH=/usr/local/lib/python2.1/site-packages
|
||||||
|
exec /usr/local/bin/python bms.py >>milter.log 2>&1
|
||||||
|
EOF
|
||||||
|
%else
|
||||||
|
cat >$RPM_BUILD_ROOT/var/log/milter/start.sh <<'EOF'
|
||||||
|
#!/bin/sh
|
||||||
|
cd /var/log/milter
|
||||||
|
exec >>milter.log 2>&1
|
||||||
|
%{python} bms.py &
|
||||||
|
echo $! >/var/run/milter/milter.pid
|
||||||
|
EOF
|
||||||
|
mkdir -p $RPM_BUILD_ROOT/etc/rc.d/init.d
|
||||||
|
cp %{sysvinit} $RPM_BUILD_ROOT/etc/rc.d/init.d/milter
|
||||||
|
ed $RPM_BUILD_ROOT/etc/rc.d/init.d/milter <<'EOF'
|
||||||
|
/^python=/
|
||||||
|
c
|
||||||
|
python="%{python}"
|
||||||
|
.
|
||||||
|
w
|
||||||
|
q
|
||||||
|
EOF
|
||||||
|
%endif
|
||||||
|
chmod a+x $RPM_BUILD_ROOT/var/log/milter/start.sh
|
||||||
|
|
||||||
|
mkdir -p $RPM_BUILD_ROOT/var/run/milter
|
||||||
|
mkdir -p $RPM_BUILD_ROOT/usr/share/sendmail-cf/hack
|
||||||
|
cp -p rhsbl.m4 $RPM_BUILD_ROOT/usr/share/sendmail-cf/hack
|
||||||
|
|
||||||
|
%ifos aix4.1
|
||||||
|
%post
|
||||||
|
mkssys -s milter -p /var/log/milter/start.sh -u 25 -S -n 15 -f 9 -G mail || :
|
||||||
|
|
||||||
|
%preun
|
||||||
|
if [ $1 = 0 ]; then
|
||||||
|
rmssys -s milter || :
|
||||||
|
fi
|
||||||
|
%else
|
||||||
|
%post
|
||||||
|
#echo "pythonsock has moved to /var/run/milter, update /etc/mail/sendmail.cf"
|
||||||
|
/sbin/chkconfig --add milter
|
||||||
|
|
||||||
|
%preun
|
||||||
|
if [ $1 = 0 ]; then
|
||||||
|
/sbin/chkconfig --del milter
|
||||||
|
fi
|
||||||
|
%endif
|
||||||
|
|
||||||
|
%clean
|
||||||
|
rm -rf $RPM_BUILD_ROOT
|
||||||
|
|
||||||
|
%files -f INSTALLED_FILES
|
||||||
|
%defattr(-,root,root)
|
||||||
|
%doc README NEWS TODO CREDITS sample.py
|
||||||
|
/etc/logrotate.d/milter
|
||||||
|
/etc/cron.daily/milter
|
||||||
|
%ifos aix4.1
|
||||||
|
%defattr(-,smmsp,mail)
|
||||||
|
%else
|
||||||
|
/etc/rc.d/init.d/milter
|
||||||
|
%defattr(-,mail,mail)
|
||||||
|
%endif
|
||||||
|
%dir /var/log/milter
|
||||||
|
%dir /var/run/milter
|
||||||
|
%dir /var/log/milter/save
|
||||||
|
%config /var/log/milter/start.sh
|
||||||
|
%config /var/log/milter/bms.py
|
||||||
|
%config(noreplace) /etc/mail/pymilter.cfg
|
||||||
|
/usr/share/sendmail-cf/hack/rhsbl.m4
|
||||||
|
|
||||||
|
%changelog
|
||||||
|
* Tue Feb 08 2005 Stuart Gathman <stuart@bmsi.com> 0.7.3-1.EL3
|
||||||
|
- Compile for EL3 and Python4
|
||||||
|
* Mon Aug 30 2004 Stuart Gathman <stuart@bmsi.com> 0.7.2-1
|
||||||
|
- Fix various SPF bugs
|
||||||
|
- Recognize dynamic PTR names, and don't count them as authentication.
|
||||||
|
- Three strikes and yer out rule.
|
||||||
|
- Block softfail by default unless valid PTR or HELO
|
||||||
|
- Return unknown for null mechanism
|
||||||
|
- Return unknown for invalid ip address in mechanism
|
||||||
|
- Try best guess on HELO also
|
||||||
|
- Expand setreply for common errors
|
||||||
|
- make rhsbl.m4 hack available for sendmail.mc
|
||||||
|
* Sun Aug 22 2004 Stuart Gathman <stuart@bmsi.com> 0.7.1-1
|
||||||
|
- Handle modifying mislabeled multipart messages without an exception
|
||||||
|
- Support setbacklog, setmlreply
|
||||||
|
- allow multi-recipient CBV
|
||||||
|
- return TEMPFAIL for SPF softfail
|
||||||
|
* Fri Jul 23 2004 Stuart Gathman <stuart@bmsi.com> 0.7.0-1
|
||||||
|
- SPF check hello name
|
||||||
|
- Move pythonsock to /var/run/milter
|
||||||
|
- Move milter.cfg to /etc/mail/pymilter.cfg
|
||||||
|
- Check M$ style XML CID records by converting to SPF
|
||||||
|
- Recognize, but never match ip6 until we properly support it.
|
||||||
|
- Option to reject when no PTR and no SPF
|
||||||
|
* Fri Apr 09 2004 Stuart Gathman <stuart@bmsi.com> 0.6.9-1
|
||||||
|
- Validate spf.py against test suite, and add Received-SPF support to spf.py
|
||||||
|
- Support best_guess for SPF
|
||||||
|
- Reject numeric hello names
|
||||||
|
- Preserve case of local part in sender
|
||||||
|
- Make libmilter timeout a config option
|
||||||
|
- Fix setup.py to work with python < 2.2.3
|
||||||
|
* Tue Apr 06 2004 Stuart Gathman <stuart@bmsi.com> 0.6.8-3
|
||||||
|
- Reject invalid SRS immediately for benefit of callback verifiers
|
||||||
|
- Fix include bug in spf.py
|
||||||
|
* Tue Apr 06 2004 Stuart Gathman <stuart@bmsi.com> 0.6.8-2
|
||||||
|
- Bug in check_header
|
||||||
|
* Mon Apr 05 2004 Stuart Gathman <stuart@bmsi.com> 0.6.8-1
|
||||||
|
- Don't report spoofed unless rcpt looks like SRS
|
||||||
|
- Check for bounce with multiple rcpts
|
||||||
|
- Make dspam see Received-SPF headers
|
||||||
|
- Make sysv init work with RH9
|
||||||
|
* Thu Mar 25 2004 Stuart Gathman <stuart@bmsi.com> 0.6.7-3
|
||||||
|
- Forgot to make spf_reject_neutral global in bms.py
|
||||||
|
* Wed Mar 24 2004 Stuart Gathman <stuart@bmsi.com> 0.6.7-2
|
||||||
|
- Defang message/rfc822 content_type with boundary
|
||||||
|
- Support SPF delegation
|
||||||
|
- Reject neutral SPF result for selected domains
|
||||||
|
* Tue Mar 23 2004 Stuart Gathman <stuart@bmsi.com> 0.6.7-1
|
||||||
|
- SRS forgery check. Detect thread resource starvation.
|
||||||
|
- Properly remove local socket with explicit type.
|
||||||
|
- Decode obfuscated subject headers.
|
||||||
|
* Wed Mar 11 2004 Stuart Gathman <stuart@bmsi.com> 0.6.6-2
|
||||||
|
- init script bug with python2.3
|
||||||
|
* Wed Mar 10 2004 Stuart Gathman <stuart@bmsi.com> 0.6.6-1
|
||||||
|
- SPF checking, hello blacklist
|
||||||
|
* Mon Mar 08 2004 Stuart Gathman <stuart@bmsi.com> 0.6.5-2
|
||||||
|
- memory leak in envfrom and envrcpt
|
||||||
|
* Mon Mar 01 2004 Stuart Gathman <stuart@bmsi.com> 0.6.5-1
|
||||||
|
- progress notification
|
||||||
|
- memory leak in connect
|
||||||
|
- trusted relay
|
||||||
|
* Thu Feb 19 2004 Stuart Gathman <stuart@bmsi.com> 0.6.4-2
|
||||||
|
- smart alias wildcard patch, compile for sendmail-8.12
|
||||||
|
* Thu Dec 04 2003 Stuart Gathman <stuart@bmsi.com> 0.6.4-1
|
||||||
|
- many fixes for dspam support
|
||||||
|
* Wed Oct 22 2003 Stuart Gathman <stuart@bmsi.com> 0.6.3
|
||||||
|
- dspam SCREEN feature
|
||||||
|
- streamline dspam false positive handling
|
||||||
|
* Mon Sep 01 2003 Stuart Gathman <stuart@bmsi.com> 0.6.1
|
||||||
|
- Full dspam support added
|
||||||
|
* Mon Aug 26 2003 Stuart Gathman <stuart@bmsi.com>
|
||||||
|
- Use New email module
|
||||||
|
* Fri Jun 27 2003 Stuart Gathman <stuart@bmsi.com>
|
||||||
|
- Add dspam module
|
||||||
+1229
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,483 @@
|
|||||||
|
# $Log$
|
||||||
|
# Revision 1.62 2005/02/14 22:31:17 stuart
|
||||||
|
# _parseparam replacement not needed for python2.4
|
||||||
|
#
|
||||||
|
# Revision 1.61 2005/02/12 02:11:11 stuart
|
||||||
|
# Pass unit tests with python2.4.
|
||||||
|
#
|
||||||
|
# Revision 1.60 2005/02/11 18:34:14 stuart
|
||||||
|
# Handle garbage after quote in boundary.
|
||||||
|
#
|
||||||
|
# Revision 1.59 2005/02/10 01:10:59 stuart
|
||||||
|
# Fixed MimeMessage.ismodified()
|
||||||
|
#
|
||||||
|
# Revision 1.58 2005/02/10 00:56:49 stuart
|
||||||
|
# Runs with python2.4. Defang not working correctly - more work needed.
|
||||||
|
#
|
||||||
|
# Revision 1.57 2004/11/20 16:37:52 stuart
|
||||||
|
# fix regex for splitting header and body
|
||||||
|
#
|
||||||
|
# Revision 1.56 2004/11/09 20:33:51 stuart
|
||||||
|
# Recognize more dynamic PTR variations.
|
||||||
|
#
|
||||||
|
# Revision 1.55 2004/10/06 21:39:20 stuart
|
||||||
|
# Handle message attachments with boundary errors by not parsing them
|
||||||
|
# until needed.
|
||||||
|
#
|
||||||
|
# Revision 1.54 2004/08/18 01:59:46 stuart
|
||||||
|
# Handle mislabeled multipart messages
|
||||||
|
#
|
||||||
|
# Revision 1.53 2004/04/24 22:53:20 stuart
|
||||||
|
# Rename some local variables to avoid shadowing builtins
|
||||||
|
#
|
||||||
|
# Revision 1.52 2004/04/24 22:47:13 stuart
|
||||||
|
# Convert header values to str
|
||||||
|
#
|
||||||
|
# Revision 1.51 2004/03/25 03:19:10 stuart
|
||||||
|
# Correctly defang rfc822 attachments when boundary specified with
|
||||||
|
# content-type message/rfc822.
|
||||||
|
#
|
||||||
|
# Revision 1.50 2003/10/15 22:01:00 stuart
|
||||||
|
# Test for and work around email bug with encoded filenames.
|
||||||
|
#
|
||||||
|
# Revision 1.49 2003/09/04 18:48:13 stuart
|
||||||
|
# Support python-2.2.3
|
||||||
|
#
|
||||||
|
# Revision 1.48 2003/09/02 00:27:27 stuart
|
||||||
|
# Should have full milter based dspam support working
|
||||||
|
#
|
||||||
|
# Revision 1.47 2003/08/26 06:08:18 stuart
|
||||||
|
# Use new python boolean since we now require 2.2.2
|
||||||
|
#
|
||||||
|
# Revision 1.46 2003/08/26 05:01:38 stuart
|
||||||
|
# Release 0.6.0
|
||||||
|
#
|
||||||
|
# Revision 1.45 2003/08/26 04:01:24 stuart
|
||||||
|
# Use new email module for parsing mail. Still need mime module to
|
||||||
|
# provide various bug fixes to email module, and maintain some compatibility
|
||||||
|
# with old milter code.
|
||||||
|
#
|
||||||
|
|
||||||
|
# This module provides a "defang" function to replace naughty attachments
|
||||||
|
# with a warning message.
|
||||||
|
|
||||||
|
# Author: Stuart D. Gathman <stuart@bmsi.com>
|
||||||
|
# Copyright 2001 Business Management Systems, Inc.
|
||||||
|
# This code is under GPL. See COPYING for details.
|
||||||
|
|
||||||
|
import StringIO
|
||||||
|
import socket
|
||||||
|
import Milter
|
||||||
|
|
||||||
|
import email
|
||||||
|
import email.Message
|
||||||
|
from email.Message import Message
|
||||||
|
from email.Generator import Generator
|
||||||
|
from email.Utils import quote
|
||||||
|
from email import Utils
|
||||||
|
from email.Parser import Parser
|
||||||
|
from email import Errors
|
||||||
|
|
||||||
|
from types import ListType,StringType
|
||||||
|
|
||||||
|
class MimeGenerator(Generator):
|
||||||
|
def _dispatch(self, msg):
|
||||||
|
# Get the Content-Type: for the message, then try to dispatch to
|
||||||
|
# self._handle_<maintype>_<subtype>(). If there's no handler for the
|
||||||
|
# full MIME type, then dispatch to self._handle_<maintype>(). If
|
||||||
|
# that's missing too, then dispatch to self._writeBody().
|
||||||
|
main = msg.get_content_maintype()
|
||||||
|
if msg.is_multipart() and main.lower() != 'multipart':
|
||||||
|
self._handle_multipart(msg)
|
||||||
|
else:
|
||||||
|
Generator._dispatch(self,msg)
|
||||||
|
|
||||||
|
def unquote(s):
|
||||||
|
"""Remove quotes from a string."""
|
||||||
|
if len(s) > 1:
|
||||||
|
if s.startswith('"'):
|
||||||
|
if s.endswith('"'):
|
||||||
|
s = s[1:-1]
|
||||||
|
else: # remove garbage after trailing quote
|
||||||
|
try: s = s[1:s[1:].index('"')+1]
|
||||||
|
except:
|
||||||
|
return s
|
||||||
|
return s.replace('\\\\', '\\').replace('\\"', '"')
|
||||||
|
if s.startswith('<') and s.endswith('>'):
|
||||||
|
return s[1:-1]
|
||||||
|
return s
|
||||||
|
|
||||||
|
from types import TupleType
|
||||||
|
|
||||||
|
def _unquotevalue(value):
|
||||||
|
if isinstance(value, TupleType):
|
||||||
|
return value[0], value[1], unquote(value[2])
|
||||||
|
else:
|
||||||
|
return unquote(value)
|
||||||
|
|
||||||
|
#email.Message._unquotevalue = _unquotevalue
|
||||||
|
|
||||||
|
from email.Message import _parseparam
|
||||||
|
|
||||||
|
# Enhance email.Message
|
||||||
|
# - Provide a headerchange event for integration with Milter
|
||||||
|
# Headerchange attribute can be assigned a function to be called when
|
||||||
|
# changing headers. The signature is:
|
||||||
|
# headerchange(msg,name,value) -> None
|
||||||
|
# - Track modifications to headers of body or any part independently
|
||||||
|
|
||||||
|
class MimeMessage(Message):
|
||||||
|
"""Version of email.Message.Message compatible with old mime module
|
||||||
|
"""
|
||||||
|
def __init__(self,fp=None,seekable=1):
|
||||||
|
Message.__init__(self)
|
||||||
|
self.headerchange = None
|
||||||
|
self.submsg = None
|
||||||
|
self.modified = False
|
||||||
|
|
||||||
|
def get_param(self, param, failobj=None, header='content-type', unquote=True):
|
||||||
|
val = Message.get_param(self,param,failobj,header,unquote)
|
||||||
|
if val != failobj and param == 'boundary' and unquote:
|
||||||
|
# unquote boundaries an extra time, test case testDefang5
|
||||||
|
return _unquotevalue(val)
|
||||||
|
return val
|
||||||
|
|
||||||
|
getfilename = Message.get_filename
|
||||||
|
ismultipart = Message.is_multipart
|
||||||
|
getheaders = Message.get_all
|
||||||
|
gettype = Message.get_content_type
|
||||||
|
getparam = Message.get_param
|
||||||
|
|
||||||
|
def getparams(self): return self.get_params([])
|
||||||
|
|
||||||
|
def getname(self):
|
||||||
|
return self.get_param('name')
|
||||||
|
|
||||||
|
def getnames(self):
|
||||||
|
"""Return a list of (attr,name) pairs of attributes that IE might
|
||||||
|
interpret as a name - and hence decide to execute this message."""
|
||||||
|
names = []
|
||||||
|
for attr,val in self._get_params_preserve([],'content-type'):
|
||||||
|
if isinstance(val, TupleType):
|
||||||
|
# It's an RFC 2231 encoded parameter
|
||||||
|
newvalue = _unquotevalue(val)
|
||||||
|
if val[0]:
|
||||||
|
val = unicode(newvalue[2], newvalue[0])
|
||||||
|
else:
|
||||||
|
val = unicode(newvalue[2])
|
||||||
|
else:
|
||||||
|
val = _unquotevalue(val.strip())
|
||||||
|
names.append((attr,val))
|
||||||
|
return names + [("filename",self.get_filename())]
|
||||||
|
|
||||||
|
def ismodified(self):
|
||||||
|
"True if this message or a subpart has been modified."
|
||||||
|
if not self.is_multipart():
|
||||||
|
if isinstance(self.submsg,Message):
|
||||||
|
return self.submsg.ismodified()
|
||||||
|
return self.modified
|
||||||
|
if self.modified: return True
|
||||||
|
for i in self.get_payload():
|
||||||
|
if i.ismodified(): return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def dump(self,file,unixfrom=False):
|
||||||
|
"Write this message (and all subparts) to a file"
|
||||||
|
g = MimeGenerator(file)
|
||||||
|
g.flatten(self,unixfrom=unixfrom)
|
||||||
|
|
||||||
|
def as_string(self, unixfrom=False):
|
||||||
|
"Return the entire formatted message as a string."
|
||||||
|
fp = StringIO.StringIO()
|
||||||
|
self.dump(fp,unixfrom=unixfrom)
|
||||||
|
return fp.getvalue()
|
||||||
|
|
||||||
|
def getencoding(self):
|
||||||
|
return self.get('content-transfer-encoding',None)
|
||||||
|
|
||||||
|
# Decode body to stream according to transfer encoding, return encoding name
|
||||||
|
def decode(self,filt):
|
||||||
|
try:
|
||||||
|
filt.write(self.get_payload(decode=True))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return self.getencoding()
|
||||||
|
|
||||||
|
def get_payload_decoded(self):
|
||||||
|
return self.get_payload(decode=True)
|
||||||
|
|
||||||
|
def __setitem__(self, name, value):
|
||||||
|
rc = Message.__setitem__(self,name,value)
|
||||||
|
self.modified = True
|
||||||
|
if self.headerchange: self.headerchange(self,name,str(value))
|
||||||
|
return rc
|
||||||
|
|
||||||
|
def __delitem__(self, name):
|
||||||
|
if self.headerchange: self.headerchange(self,name,None)
|
||||||
|
rc = Message.__delitem__(self,name)
|
||||||
|
self.modified = True
|
||||||
|
return rc
|
||||||
|
|
||||||
|
def get_payload(self,i=None,decode=False):
|
||||||
|
msg = self.submsg
|
||||||
|
if isinstance(msg,Message) and msg.ismodified():
|
||||||
|
self.set_payload([msg])
|
||||||
|
return Message.get_payload(self,i,decode)
|
||||||
|
|
||||||
|
def set_payload(self, val, charset=None):
|
||||||
|
self.modified = True
|
||||||
|
try:
|
||||||
|
val.seek(0)
|
||||||
|
val = val.read()
|
||||||
|
except: pass
|
||||||
|
Message.set_payload(self,val,charset)
|
||||||
|
self.submsg = None
|
||||||
|
|
||||||
|
def get_submsg(self):
|
||||||
|
t = self.get_content_type().lower()
|
||||||
|
if t == 'message/rfc822' or t.startswith('multipart/'):
|
||||||
|
if not self.submsg:
|
||||||
|
txt = self.get_payload()
|
||||||
|
if type(txt) == str:
|
||||||
|
txt = self.get_payload(decode=True)
|
||||||
|
self.submsg = email.message_from_string(txt,MimeMessage)
|
||||||
|
for part in self.submsg.walk():
|
||||||
|
part.modified = False
|
||||||
|
else:
|
||||||
|
self.submsg = txt[0]
|
||||||
|
return self.submsg
|
||||||
|
return None
|
||||||
|
|
||||||
|
def message_from_file(fp):
|
||||||
|
msg = email.message_from_file(fp,MimeMessage)
|
||||||
|
for part in msg.walk():
|
||||||
|
part.modified = False
|
||||||
|
assert not msg.ismodified()
|
||||||
|
return msg
|
||||||
|
|
||||||
|
extlist = ''.join("""
|
||||||
|
ade,adp,asd,asx,asp,bas,bat,chm,cmd,com,cpl,crt,dll,exe,hlp,hta,inf,ins,isp,js,
|
||||||
|
jse,lnk,mdb,mde,msc,msi,msp,mst,ocx,pcd,pif,reg,scr,sct,shs,url,vb,vbe,vbs,wsc,
|
||||||
|
wsf,wsh
|
||||||
|
""".split())
|
||||||
|
bad_extensions = map(lambda x:'.' + x,extlist.split(','))
|
||||||
|
|
||||||
|
def check_ext(name):
|
||||||
|
"Check a name for dangerous Winblows extensions."
|
||||||
|
if not name: return name
|
||||||
|
lname = name.lower()
|
||||||
|
for ext in bad_extensions:
|
||||||
|
if lname.endswith(ext): return name
|
||||||
|
return None
|
||||||
|
|
||||||
|
virus_msg = """This message appeared to contain a virus.
|
||||||
|
It was originally named '%s', and has been removed.
|
||||||
|
A copy of your original message was saved as '%s:%s'.
|
||||||
|
See your administrator.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def check_name(msg,savname=None,ckname=check_ext):
|
||||||
|
"Replace attachment with a warning if its name is suspicious."
|
||||||
|
for key,name in msg.getnames():
|
||||||
|
badname = ckname(name)
|
||||||
|
if badname:
|
||||||
|
hostname = socket.gethostname()
|
||||||
|
msg.set_payload(virus_msg % (badname,hostname,savname))
|
||||||
|
del msg["content-type"]
|
||||||
|
del msg["content-disposition"]
|
||||||
|
del msg["content-transfer-encoding"]
|
||||||
|
name = "WARNING.TXT"
|
||||||
|
msg["Content-Type"] = "text/plain; name="+name
|
||||||
|
break
|
||||||
|
return Milter.CONTINUE
|
||||||
|
|
||||||
|
import email.Iterators
|
||||||
|
|
||||||
|
def check_attachments(msg,check):
|
||||||
|
"""Scan attachments.
|
||||||
|
msg MimeMessage
|
||||||
|
check function(MimeMessage): int
|
||||||
|
Return CONTINUE, REJECT, ACCEPT
|
||||||
|
"""
|
||||||
|
if msg.is_multipart():
|
||||||
|
for i in msg.get_payload():
|
||||||
|
rc = check_attachments(i,check)
|
||||||
|
if rc != Milter.CONTINUE: return rc
|
||||||
|
return Milter.CONTINUE
|
||||||
|
return check(msg)
|
||||||
|
|
||||||
|
# save call context for Python without nested_scopes
|
||||||
|
class _defang:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.scan_html = True
|
||||||
|
|
||||||
|
def _chk_name(self,msg):
|
||||||
|
rc = check_name(msg,self._savname,self._check)
|
||||||
|
if self.scan_html:
|
||||||
|
check_html(msg,self._savname) # remove scripts from HTML
|
||||||
|
if self.scan_rfc822:
|
||||||
|
msg = msg.get_submsg()
|
||||||
|
if isinstance(msg,Message):
|
||||||
|
return check_attachments(msg,self._chk_name)
|
||||||
|
return rc
|
||||||
|
|
||||||
|
def __call__(self,msg,savname=None,check=check_ext,scan_rfc822=True):
|
||||||
|
"""Compatible entry point.
|
||||||
|
Replace all attachments with dangerous names."""
|
||||||
|
self._savname = savname
|
||||||
|
self._check = check
|
||||||
|
self.scan_rfc822 = scan_rfc822
|
||||||
|
check_attachments(msg,self._chk_name)
|
||||||
|
if msg.ismodified():
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
# emulate old defang function
|
||||||
|
defang = _defang()
|
||||||
|
|
||||||
|
import sgmllib
|
||||||
|
|
||||||
|
import re
|
||||||
|
declname = re.compile(r'[a-zA-Z][-_.a-zA-Z0-9]*\s*')
|
||||||
|
declstringlit = re.compile(r'(\'[^\']*\'|"[^"]*")\s*')
|
||||||
|
|
||||||
|
class SGMLFilter(sgmllib.SGMLParser):
|
||||||
|
"""Parse HTML and pass through all constructs unchanged. It is intended for
|
||||||
|
derived classes to implement exceptional processing for selected cases.
|
||||||
|
"""
|
||||||
|
def __init__(self,out):
|
||||||
|
sgmllib.SGMLParser.__init__(self)
|
||||||
|
self.out = out
|
||||||
|
|
||||||
|
def handle_comment(self,comment):
|
||||||
|
self.out.write("<!--%s-->" % comment)
|
||||||
|
|
||||||
|
def unknown_starttag(self,tag,attr):
|
||||||
|
if hasattr(self,"get_starttag_text"):
|
||||||
|
self.out.write(self.get_starttag_text())
|
||||||
|
else:
|
||||||
|
self.out.write("<%s" % tag)
|
||||||
|
for (key,val) in attr:
|
||||||
|
self.out.write(' %s="%s"' % (key,val))
|
||||||
|
self.out.write('>')
|
||||||
|
|
||||||
|
def handle_data(self,data):
|
||||||
|
self.out.write(data)
|
||||||
|
|
||||||
|
def handle_entityref(self,ref):
|
||||||
|
self.out.write("&%s;" % ref)
|
||||||
|
|
||||||
|
def handle_charref(self,ref):
|
||||||
|
self.out.write("&#%s;" % ref)
|
||||||
|
|
||||||
|
def unknown_endtag(self,tag):
|
||||||
|
self.out.write("</%s>" % tag)
|
||||||
|
|
||||||
|
def handle_special(self,data):
|
||||||
|
self.out.write("<!%s>" % data)
|
||||||
|
|
||||||
|
def write(self,buf):
|
||||||
|
"Act like a writer. Why doesn't SGMLParser do this by default?"
|
||||||
|
self.feed(buf)
|
||||||
|
|
||||||
|
# Python-2.1 sgmllib rejects illegal declarations. Since various Microsoft
|
||||||
|
# products accept and output them, we need to pass them through -
|
||||||
|
# at least until we discover that MS will execute them.
|
||||||
|
# sgmlop-1.1 will not use this method, but calls handle_special to
|
||||||
|
# do what we want.
|
||||||
|
def parse_declaration(self, i):
|
||||||
|
rawdata = self.rawdata
|
||||||
|
n = len(rawdata)
|
||||||
|
j = i + 2
|
||||||
|
while j < n:
|
||||||
|
c = rawdata[j]
|
||||||
|
if c == ">":
|
||||||
|
# end of declaration syntax
|
||||||
|
self.handle_special(rawdata[i+2:j])
|
||||||
|
return j + 1
|
||||||
|
if c in "\"'":
|
||||||
|
m = declstringlit.match(rawdata, j)
|
||||||
|
if not m:
|
||||||
|
# incomplete or an error?
|
||||||
|
return -1
|
||||||
|
j = m.end()
|
||||||
|
elif c in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ":
|
||||||
|
m = declname.match(rawdata, j)
|
||||||
|
if not m:
|
||||||
|
# incomplete or an error?
|
||||||
|
return -1
|
||||||
|
j = m.end()
|
||||||
|
else:
|
||||||
|
j += 1
|
||||||
|
# end of buffer between tokens
|
||||||
|
return -1
|
||||||
|
|
||||||
|
class HTMLScriptFilter(SGMLFilter):
|
||||||
|
"Remove scripts from an HTML document."
|
||||||
|
def __init__(self,out):
|
||||||
|
SGMLFilter.__init__(self,out)
|
||||||
|
self.ignoring = 0
|
||||||
|
self.modified = False
|
||||||
|
self.msg = "<!-- WARNING: embedded script removed -->"
|
||||||
|
def start_script(self,unused):
|
||||||
|
self.ignoring += 1
|
||||||
|
self.modified = True
|
||||||
|
self.out.write(self.msg)
|
||||||
|
def end_script(self):
|
||||||
|
self.ignoring -= 1
|
||||||
|
def handle_data(self,data):
|
||||||
|
if not self.ignoring: SGMLFilter.handle_data(self,data)
|
||||||
|
def handle_comment(self,comment):
|
||||||
|
if not self.ignoring: SGMLFilter.handle_comment(self,comment)
|
||||||
|
|
||||||
|
def check_html(msg,savname=None):
|
||||||
|
"Remove scripts from HTML attachments."
|
||||||
|
msgtype = msg.get_content_type().lower()
|
||||||
|
# check for more MSIE braindamage
|
||||||
|
if msgtype == 'application/octet-stream':
|
||||||
|
for (attr,name) in msg.getnames():
|
||||||
|
if name and name.lower().endswith(".htm"):
|
||||||
|
msgtype = 'text/html'
|
||||||
|
if msgtype == 'text/html':
|
||||||
|
out = StringIO.StringIO()
|
||||||
|
htmlfilter = HTMLScriptFilter(out)
|
||||||
|
try:
|
||||||
|
htmlfilter.write(msg.get_payload(decode=True))
|
||||||
|
htmlfilter.close()
|
||||||
|
#except sgmllib.SGMLParseError:
|
||||||
|
except:
|
||||||
|
#mimetools.copyliteral(msg.get_payload(),open('debug.out','w')
|
||||||
|
htmlfilter.close()
|
||||||
|
hostname = socket.gethostname()
|
||||||
|
msg.set_payload(
|
||||||
|
"An HTML attachment could not be parsed. The original is saved as '%s:%s'"
|
||||||
|
% (hostname,savname))
|
||||||
|
del msg["content-type"]
|
||||||
|
del msg["content-disposition"]
|
||||||
|
del msg["content-transfer-encoding"]
|
||||||
|
name = "WARNING.TXT"
|
||||||
|
msg["Content-Type"] = "text/plain; name="+name
|
||||||
|
return Milter.CONTINUE
|
||||||
|
if htmlfilter.modified:
|
||||||
|
msg.set_payload(out) # remove embedded scripts
|
||||||
|
del msg["content-transfer-encoding"]
|
||||||
|
email.Encoders.encode_quopri(msg)
|
||||||
|
return Milter.CONTINUE
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import sys
|
||||||
|
def _list_attach(msg):
|
||||||
|
t = msg.get_content_type()
|
||||||
|
p = msg.get_payload(decode=True)
|
||||||
|
print msg.get_filename(),msg.get_content_type(),type(p)
|
||||||
|
msg = msg.get_submsg()
|
||||||
|
if isinstance(msg,Message):
|
||||||
|
return check_attachments(msg,_list_attach)
|
||||||
|
return Milter.CONTINUE
|
||||||
|
|
||||||
|
for fname in sys.argv[1:]:
|
||||||
|
fp = open(fname)
|
||||||
|
msg = message_from_file(fp)
|
||||||
|
email.Iterators._structure(msg)
|
||||||
|
check_attachments(msg,_list_attach)
|
||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
# Analyze milter log to find abusers
|
||||||
|
|
||||||
|
fp = open('/var/log/milter/milter.log','r')
|
||||||
|
subdict = {}
|
||||||
|
ipdict = {}
|
||||||
|
spamcnt = {}
|
||||||
|
for line in fp:
|
||||||
|
a = line.split(None,4)
|
||||||
|
if len(a) < 4: continue
|
||||||
|
dt,tm,id,op = a[:4]
|
||||||
|
if op == 'Subject:':
|
||||||
|
if len(a) > 4: subdict[id] = a[4].rstrip()
|
||||||
|
elif op == 'connect':
|
||||||
|
ipdict[id] = a[4].rstrip()
|
||||||
|
elif op in ('eom','dspam'):
|
||||||
|
if id in subdict: del subdict[id]
|
||||||
|
if id in ipdict: del ipdict[id]
|
||||||
|
elif op in ('REJECT:','DSPAM:','SPAM:','abort'):
|
||||||
|
if id in subdict:
|
||||||
|
if id in ipdict:
|
||||||
|
ip = ipdict[id]
|
||||||
|
del ipdict[id]
|
||||||
|
f,host,raw = ip.split(None,2)
|
||||||
|
if host in spamcnt:
|
||||||
|
spamcnt[host] += 1
|
||||||
|
else:
|
||||||
|
spamcnt[host] = 1
|
||||||
|
else: ip = ''
|
||||||
|
print dt,tm,op,a[4].rstrip(),subdict[id]
|
||||||
|
del subdict[id]
|
||||||
|
else:
|
||||||
|
print line.rstrip()
|
||||||
|
print len(subdict),'leftover entries'
|
||||||
|
|
||||||
|
spamlist = filter(lambda x: x[1] > 1,spamcnt.items())
|
||||||
|
spamlist.sort(lambda x,y: x[1] - y[1])
|
||||||
|
for ip,cnt in spamlist:
|
||||||
|
print cnt,ip
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
divert(-1)
|
||||||
|
#
|
||||||
|
# Copyright (c) 2002 Derek J. Balling
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Permission to use granted for all purposes. If modifications are made
|
||||||
|
# they are requested to be sent to <dredd@megacity.org> for inclusion in future
|
||||||
|
# versions
|
||||||
|
#
|
||||||
|
# Allows (hopefully) for checking of access.db whitelisting now. This ONLY
|
||||||
|
# works on sendmail-8.12.x ... use on any other version may require tinkering
|
||||||
|
# by you the downloader.
|
||||||
|
#
|
||||||
|
# Incorporates many changes by Sergey S. Mokryshev <mokr@mokr.net>
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
|
divert(0)
|
||||||
|
ifdef(`_RHSBL_R_',`dnl',`dnl
|
||||||
|
VERSIONID(`$Id$')
|
||||||
|
define(`_RHSBL_R_',`')
|
||||||
|
ifdef(`_DNSBL_R_',`dnl',`dnl
|
||||||
|
LOCAL_CONFIG
|
||||||
|
# map for DNS based blacklist lookups based on the sender RHS
|
||||||
|
Kdnsbl host -T<TMP>')')
|
||||||
|
divert(-1)
|
||||||
|
define(`_RHSBL_SRV_', `_ARG_')dnl
|
||||||
|
define(`_RHSBL_MSG_', `ifelse(len(X`'_ARG2_),`1',`"550 Mail from " $`'&{RHS} " refused by blackhole site '_RHSBL_SRV_`"',`_ARG2_')')dnl
|
||||||
|
define(`_RHSBL_MSG_TMP_', `ifelse(_ARG3_,`t',`"451 Temporary lookup failure of " $`'&{RHS} " at '_RHSBL_SRV_`"',`_ARG3_')')dnl
|
||||||
|
|
||||||
|
MAILER_DEFINITIONS
|
||||||
|
|
||||||
|
SLocal_check_mail
|
||||||
|
# DNS based RHS spam list blackholes.bmsi.com
|
||||||
|
R$* $: <?> $>CanonAddr $1
|
||||||
|
R<?> $*<@$+.> $: <?> $1<@$2.> $| $>SearchList <+ rhs> $| <F:$1@$2> <D:$2> <>
|
||||||
|
R<?> $* $| <$={Accept}> $: OKSOFAR
|
||||||
|
R<?> $*<@$+.> $| $* $: <?> $(dnsbl $2._RHSBL_SRV_. $: OK $) $(macro {RHS} $@ $2 $)
|
||||||
|
R<?> OK $: OKSOFAR
|
||||||
|
R<?> $*<@$*> $: OKSOFAR
|
||||||
|
ifelse(len(X`'_ARG3_),`1',
|
||||||
|
`R<?>$+<TMP> $: TMPOK',
|
||||||
|
`R<?>$+<TMP> $#error $@ 4.7.1 $: _RHSBL_MSG_TMP_')
|
||||||
|
R<?>$+ $#error $@ 5.7.1 $: _RHSBL_MSG_
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
|
||||||
|
# A simple milter.
|
||||||
|
|
||||||
|
# Author: Stuart D. Gathman <stuart@bmsi.com>
|
||||||
|
# Copyright 2001 Business Management Systems, Inc.
|
||||||
|
# This code is under GPL. See COPYING for details.
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import StringIO
|
||||||
|
import rfc822
|
||||||
|
import mime
|
||||||
|
import Milter
|
||||||
|
import tempfile
|
||||||
|
from time import strftime
|
||||||
|
#import syslog
|
||||||
|
|
||||||
|
#syslog.openlog('milter')
|
||||||
|
|
||||||
|
class sampleMilter(Milter.Milter):
|
||||||
|
"Milter to replace attachments poisonous to Windows with a WARNING message."
|
||||||
|
|
||||||
|
def log(self,*msg):
|
||||||
|
print "%s [%d]" % (strftime('%Y%b%d %H:%M:%S'),self.id),
|
||||||
|
for i in msg: print i,
|
||||||
|
print
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.tempname = None
|
||||||
|
self.mailfrom = None
|
||||||
|
self.fp = None
|
||||||
|
self.bodysize = 0
|
||||||
|
self.id = Milter.uniqueID()
|
||||||
|
|
||||||
|
# multiple messages can be received on a single connection
|
||||||
|
# envfrom (MAIL FROM in the SMTP protocol) seems to mark the start
|
||||||
|
# of each message.
|
||||||
|
def envfrom(self,f,*str):
|
||||||
|
self.log("mail from",f,str)
|
||||||
|
self.fp = StringIO.StringIO()
|
||||||
|
self.tempname = None
|
||||||
|
self.mailfrom = f
|
||||||
|
self.bodysize = 0
|
||||||
|
return Milter.CONTINUE
|
||||||
|
|
||||||
|
def envrcpt(self,to,*str):
|
||||||
|
# mail to MAILER-DAEMON is generally spam that bounced
|
||||||
|
if to.startswith('<MAILER-DAEMON@'):
|
||||||
|
self.log('DISCARD: RCPT TO:',to,str)
|
||||||
|
return Milter.DISCARD
|
||||||
|
self.log("rcpt to",to,str)
|
||||||
|
return Milter.CONTINUE
|
||||||
|
|
||||||
|
def header(self,name,val):
|
||||||
|
lname = name.lower()
|
||||||
|
if lname == 'subject':
|
||||||
|
|
||||||
|
# even if we wanted the Taiwanese spam, we can't read Chinese
|
||||||
|
# (delete if you read chinese mail)
|
||||||
|
if val.startswith('=?big5') or val.startswith('=?ISO-2022-JP'):
|
||||||
|
self.log('REJECT: %s: %s' % (name,val))
|
||||||
|
#self.setreply('550','','Go away spammer')
|
||||||
|
return Milter.REJECT
|
||||||
|
|
||||||
|
# check for common spam keywords
|
||||||
|
if val.find("$$$") >= 0 or val.find("XXX") >= 0 \
|
||||||
|
or val.find("!!!") >= 0 or val.find("FREE") >= 0:
|
||||||
|
self.log('REJECT: %s: %s' % (name,val))
|
||||||
|
#self.setreply('550','','Go away spammer')
|
||||||
|
return Milter.REJECT
|
||||||
|
|
||||||
|
# check for spam that pretends to be legal
|
||||||
|
lval = val.lower()
|
||||||
|
if lval.startswith("adv:") or lval.startswith("adv.") \
|
||||||
|
or lval.find('viagra') >= 0:
|
||||||
|
self.log('REJECT: %s: %s' % (name,val))
|
||||||
|
return Milter.REJECT
|
||||||
|
|
||||||
|
# check for invalid message id
|
||||||
|
if lname == 'message-id' and len(val) < 4:
|
||||||
|
self.log('REJECT: %s: %s' % (name,val))
|
||||||
|
#self.setreply('550','','Go away spammer')
|
||||||
|
return Milter.REJECT
|
||||||
|
|
||||||
|
# check for common bulk mailers
|
||||||
|
if lname == 'x-mailer' and \
|
||||||
|
val.lower() in ('direct email','calypso','mail bomber'):
|
||||||
|
self.log('REJECT: %s: %s' % (name,val))
|
||||||
|
#self.setreply('550','','Go away spammer')
|
||||||
|
return Milter.REJECT
|
||||||
|
|
||||||
|
# log selected headers
|
||||||
|
if lname in ('subject','x-mailer'):
|
||||||
|
self.log('%s: %s' % (name,val))
|
||||||
|
if self.fp:
|
||||||
|
self.fp.write("%s: %s\n" % (name,val)) # add header to buffer
|
||||||
|
return Milter.CONTINUE
|
||||||
|
|
||||||
|
def eoh(self):
|
||||||
|
if not self.fp: return Milter.TEMPFAIL # not seen by envfrom
|
||||||
|
self.fp.write("\n")
|
||||||
|
self.fp.seek(0)
|
||||||
|
# copy headers to a temp file for scanning the body
|
||||||
|
headers = self.fp.getvalue()
|
||||||
|
self.fp.close()
|
||||||
|
self.tempname = fname = tempfile.mktemp(".defang")
|
||||||
|
self.fp = open(fname,"w+b")
|
||||||
|
self.fp.write(headers) # IOError (e.g. disk full) causes TEMPFAIL
|
||||||
|
return Milter.CONTINUE
|
||||||
|
|
||||||
|
def body(self,chunk): # copy body to temp file
|
||||||
|
if self.fp:
|
||||||
|
self.fp.write(chunk) # IOError causes TEMPFAIL in milter
|
||||||
|
self.bodysize += len(chunk)
|
||||||
|
return Milter.CONTINUE
|
||||||
|
|
||||||
|
def _headerChange(self,msg,name,value):
|
||||||
|
if value: # add header
|
||||||
|
self.addheader(name,value)
|
||||||
|
else: # delete all headers with name
|
||||||
|
h = msg.getheaders(name)
|
||||||
|
cnt = len(h)
|
||||||
|
for i in range(cnt,0,-1):
|
||||||
|
self.chgheader(name,i-1,'')
|
||||||
|
|
||||||
|
def eom(self):
|
||||||
|
if not self.fp: return Milter.ACCEPT
|
||||||
|
self.fp.seek(0)
|
||||||
|
msg = mime.message_from_file(self.fp)
|
||||||
|
msg.headerchange = self._headerChange
|
||||||
|
if not mime.defang(msg,self.tempname):
|
||||||
|
os.remove(self.tempname)
|
||||||
|
self.tempname = None # prevent re-removal
|
||||||
|
self.log("eom")
|
||||||
|
return Milter.ACCEPT # no suspicious attachments
|
||||||
|
self.log("Temp file:",self.tempname)
|
||||||
|
self.tempname = None # prevent removal of original message copy
|
||||||
|
# copy defanged message to a temp file
|
||||||
|
out = tempfile.TemporaryFile()
|
||||||
|
try:
|
||||||
|
msg.dump(out)
|
||||||
|
out.seek(0)
|
||||||
|
msg = rfc822.Message(out)
|
||||||
|
msg.rewindbody()
|
||||||
|
while 1:
|
||||||
|
buf = out.read(8192)
|
||||||
|
if len(buf) == 0: break
|
||||||
|
self.replacebody(buf) # feed modified message to sendmail
|
||||||
|
return Milter.ACCEPT # ACCEPT modified message
|
||||||
|
finally:
|
||||||
|
out.close()
|
||||||
|
return Milter.TEMPFAIL
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
sys.stdout.flush() # make log messages visible
|
||||||
|
if self.tempname:
|
||||||
|
os.remove(self.tempname) # remove in case session aborted
|
||||||
|
if self.fp:
|
||||||
|
self.fp.close()
|
||||||
|
return Milter.CONTINUE
|
||||||
|
|
||||||
|
def abort(self):
|
||||||
|
self.log("abort after %d body chars" % self.bodysize)
|
||||||
|
return Milter.CONTINUE
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
#tempfile.tempdir = "/var/log/milter"
|
||||||
|
#socketname = "/var/log/milter/pythonsock"
|
||||||
|
socketname = os.getenv("HOME") + "/pythonsock"
|
||||||
|
Milter.factory = sampleMilter
|
||||||
|
Milter.set_flags(Milter.CHGBODY + Milter.CHGHDRS + Milter.ADDHDRS)
|
||||||
|
print """To use this with sendmail, add the following to sendmail.cf:
|
||||||
|
|
||||||
|
O InputMailFilters=pythonfilter
|
||||||
|
Xpythonfilter, S=local:%s
|
||||||
|
|
||||||
|
See the sendmail README for libmilter.
|
||||||
|
sample milter startup""" % socketname
|
||||||
|
sys.stdout.flush()
|
||||||
|
Milter.runmilter("pythonfilter",socketname,240)
|
||||||
|
print "sample milter shutdown"
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
[bdist_rpm]
|
||||||
|
python=python2
|
||||||
|
doc_files=README NEWS TODO
|
||||||
|
packager=Stuart D. Gathman <stuart@bmsi.com>
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from distutils.core import setup, Extension
|
||||||
|
|
||||||
|
# FIXME: on some versions of sendmail, smutil is renamed to sm
|
||||||
|
libs = ["milter", "smutil"]
|
||||||
|
|
||||||
|
# patch distutils if it can't cope with the "classifiers" or
|
||||||
|
# "download_url" keywords
|
||||||
|
if sys.version < '2.2.3':
|
||||||
|
from distutils.dist import DistributionMetadata
|
||||||
|
DistributionMetadata.classifiers = None
|
||||||
|
DistributionMetadata.download_url = None
|
||||||
|
|
||||||
|
setup(name = "milter", version = "0.8.0",
|
||||||
|
description="Python interface to sendmail milter API",
|
||||||
|
long_description="""\
|
||||||
|
This is a python extension module to enable python scripts to
|
||||||
|
attach to sendmail's libmilter functionality. Additional python
|
||||||
|
modules provide for navigating and modifying MIME parts, and
|
||||||
|
querying SPF records.
|
||||||
|
""",
|
||||||
|
author="Jim Niemira",
|
||||||
|
author_email="urmane@urmane.org",
|
||||||
|
maintainer="Stuart D. Gathman",
|
||||||
|
maintainer_email="stuart@bmsi.com",
|
||||||
|
license="GPL",
|
||||||
|
url="http://www.bmsi.com/python/milter.html",
|
||||||
|
py_modules=["Milter","mime","spf"],
|
||||||
|
ext_modules=[
|
||||||
|
Extension("milter", ["miltermodule.c"],
|
||||||
|
libraries=libs,
|
||||||
|
define_macros = [ ('MAX_ML_REPLY',32) ]
|
||||||
|
),
|
||||||
|
],
|
||||||
|
keywords = ['sendmail','milter'],
|
||||||
|
classifiers = [
|
||||||
|
'Development Status :: 5 - Production/Stable',
|
||||||
|
'Environment :: No Input/Output (Daemon)',
|
||||||
|
'Intended Audience :: System Administrators',
|
||||||
|
'License :: OSI Approved :: GNU General Public License (GPL)',
|
||||||
|
'Natural Language :: English',
|
||||||
|
'Operating System :: POSIX',
|
||||||
|
'Programming Language :: Python',
|
||||||
|
'Topic :: Communications :: Email :: Mail Transport Agents'
|
||||||
|
]
|
||||||
|
)
|
||||||
Executable
+91
@@ -0,0 +1,91 @@
|
|||||||
|
#!/usr/bin/python2.3
|
||||||
|
# $Log$
|
||||||
|
# Revision 2.3 2004/04/19 22:12:11 stuart
|
||||||
|
# Release 0.6.9
|
||||||
|
#
|
||||||
|
# Revision 2.2 2004/04/18 03:29:35 stuart
|
||||||
|
# Pass most tests except -local and -rcpt-to
|
||||||
|
#
|
||||||
|
# Revision 2.1 2004/04/08 18:41:15 stuart
|
||||||
|
# Reject numeric hello names
|
||||||
|
#
|
||||||
|
# Driver for SPF test system
|
||||||
|
|
||||||
|
import spf
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from optparse import OptionParser
|
||||||
|
|
||||||
|
class PerlOptionParser(OptionParser):
|
||||||
|
def _process_args (self, largs, rargs, values):
|
||||||
|
"""_process_args(largs : [string],
|
||||||
|
rargs : [string],
|
||||||
|
values : Values)
|
||||||
|
|
||||||
|
Process command-line arguments and populate 'values', consuming
|
||||||
|
options and arguments from 'rargs'. If 'allow_interspersed_args' is
|
||||||
|
false, stop at the first non-option argument. If true, accumulate any
|
||||||
|
interspersed non-option arguments in 'largs'.
|
||||||
|
"""
|
||||||
|
while rargs:
|
||||||
|
arg = rargs[0]
|
||||||
|
# We handle bare "--" explicitly, and bare "-" is handled by the
|
||||||
|
# standard arg handler since the short arg case ensures that the
|
||||||
|
# len of the opt string is greater than 1.
|
||||||
|
if arg == "--":
|
||||||
|
del rargs[0]
|
||||||
|
return
|
||||||
|
elif arg[0:2] == "--":
|
||||||
|
# process a single long option (possibly with value(s))
|
||||||
|
self._process_long_opt(rargs, values)
|
||||||
|
elif arg[:1] == "-" and len(arg) > 1:
|
||||||
|
# process a single perl style long option
|
||||||
|
rargs[0] = '-' + arg
|
||||||
|
self._process_long_opt(rargs, values)
|
||||||
|
elif self.allow_interspersed_args:
|
||||||
|
largs.append(arg)
|
||||||
|
del rargs[0]
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
def format(q):
|
||||||
|
res,code,txt = q.check()
|
||||||
|
print res
|
||||||
|
if res in ('pass','neutral','unknown'): print
|
||||||
|
else: print txt
|
||||||
|
print 'spfquery:',q.get_header_comment(res)
|
||||||
|
print 'Received-SPF:',q.get_header(res,'spfquery')
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
parser = PerlOptionParser()
|
||||||
|
parser.add_option("--file",dest="file")
|
||||||
|
parser.add_option("--ip",dest="ip")
|
||||||
|
parser.add_option("--sender",dest="sender")
|
||||||
|
parser.add_option("--helo",dest="hello_name")
|
||||||
|
parser.add_option("--local",dest="local_policy")
|
||||||
|
parser.add_option("--rcpt-to",dest="rcpt")
|
||||||
|
parser.add_option("--default-explanation",dest="explanation")
|
||||||
|
parser.add_option("--sanitize",type="int",dest="sanitize")
|
||||||
|
parser.add_option("--debug",type="int",dest="debug")
|
||||||
|
opts,args = parser.parse_args(argv)
|
||||||
|
if opts.ip:
|
||||||
|
q = spf.query(opts.ip,opts.sender,opts.hello_name,local=opts.local_policy)
|
||||||
|
if opts.explanation:
|
||||||
|
q.set_default_explanation(opts.explanation)
|
||||||
|
format(q)
|
||||||
|
if opts.file:
|
||||||
|
if opts.file == '0':
|
||||||
|
fp = sys.stdin
|
||||||
|
else:
|
||||||
|
fp = open(opts.file,'r')
|
||||||
|
for ln in fp:
|
||||||
|
ip,sender,helo,rcpt = ln.split(None,3)
|
||||||
|
q = spf.query(ip,sender,helo,local=opts.local_policy)
|
||||||
|
if opts.explanation:
|
||||||
|
q.set_default_explanation(opts.explanation)
|
||||||
|
format(q)
|
||||||
|
fp.close()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import sys
|
||||||
|
main(sys.argv[1:])
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import unittest
|
||||||
|
import testbms
|
||||||
|
import testmime
|
||||||
|
import testsample
|
||||||
|
import os
|
||||||
|
|
||||||
|
def suite():
|
||||||
|
s = unittest.TestSuite()
|
||||||
|
s.addTest(testbms.suite())
|
||||||
|
s.addTest(testmime.suite())
|
||||||
|
s.addTest(testsample.suite())
|
||||||
|
return s
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
try: os.remove('test/milter.log')
|
||||||
|
except: pass
|
||||||
|
unittest.TextTestRunner().run(suite())
|
||||||
+710
@@ -0,0 +1,710 @@
|
|||||||
|
From stuart@bmsi.com Wed May 1 14:37:14 2002
|
||||||
|
Return-Path: <stuart@bmsi.com>
|
||||||
|
Received: from bmsi.com (IDENT:stuart@localhost [127.0.0.1])
|
||||||
|
by gathman.bmsi.com (8.11.6/8.11.6) with ESMTP id g41IbCF01796
|
||||||
|
for <stuart@gathman.bmsi.com>; Wed, 1 May 2002 14:37:13 -0400
|
||||||
|
Sender: stuart@gathman.bmsi.com
|
||||||
|
Message-ID: <3CD035D7.18ADF27F@bmsi.com>
|
||||||
|
Date: Wed, 01 May 2002 14:37:11 -0400
|
||||||
|
From: "Stuart D. Gathman" <stuart@bmsi.com>
|
||||||
|
Organization: Business Management Systems, Inc.
|
||||||
|
X-Mailer: Mozilla 4.78 [en] (X11; U; Linux 2.4.9-21 i586)
|
||||||
|
X-Accept-Language: en
|
||||||
|
MIME-Version: 1.0
|
||||||
|
To: stuart@gathman.bmsi.com
|
||||||
|
Subject: Amazon.com--Earth's Biggest Selection
|
||||||
|
Content-Type: multipart/mixed;
|
||||||
|
boundary="------------59A46341C90BA737DD47867B"
|
||||||
|
|
||||||
|
This is a multi-part message in MIME format.
|
||||||
|
--------------59A46341C90BA737DD47867B
|
||||||
|
Content-Type: multipart/alternative;
|
||||||
|
boundary="------------0B098FB91956AC123C61B151"
|
||||||
|
|
||||||
|
|
||||||
|
--------------0B098FB91956AC123C61B151
|
||||||
|
Content-Type: text/plain; charset=us-ascii
|
||||||
|
Content-Transfer-Encoding: 7bit
|
||||||
|
|
||||||
|
http://www.amazon.com/exec/obidos/subst/home/redirect.html/103-3111065-2579065
|
||||||
|
|
||||||
|
--
|
||||||
|
Stuart D. Gathman
|
||||||
|
Business Management Systems Inc. Phone: 703 591-0911 Fax: 703 591-6154
|
||||||
|
"Confutatis maledictis, flamis acribus addictis" - background song for
|
||||||
|
a Microsoft sponsored "Where do you want to go from here?" commercial.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--------------0B098FB91956AC123C61B151
|
||||||
|
Content-Type: text/html; charset=us-ascii
|
||||||
|
Content-Transfer-Encoding: 7bit
|
||||||
|
|
||||||
|
<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
|
||||||
|
<html>
|
||||||
|
<A HREF="http://www.amazon.com/exec/obidos/subst/home/redirect.html/103-3111065-2579065">http://www.amazon.com/exec/obidos/subst/home/redirect.html/103-3111065-2579065</A>
|
||||||
|
<pre>--
|
||||||
|
Stuart D. Gathman <stuart@bmsi.com>
|
||||||
|
Business Management Systems Inc. Phone: 703 591-0911 Fax: 703 591-6154
|
||||||
|
"Confutatis maledictis, flamis acribus addictis" - background song for
|
||||||
|
a Microsoft sponsored "Where do you want to go from here?" commercial.</pre>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
--------------0B098FB91956AC123C61B151--
|
||||||
|
|
||||||
|
--------------59A46341C90BA737DD47867B
|
||||||
|
Content-Type: text/html; charset=us-ascii;
|
||||||
|
name="103-3111065-2579065"
|
||||||
|
Content-Transfer-Encoding: 7bit
|
||||||
|
Content-Disposition: inline;
|
||||||
|
filename="103-3111065-2579065"
|
||||||
|
Content-Base: "http://www.amazon.com/exec/obidos/subs
|
||||||
|
t/home/redirect.html/103-3111065-25
|
||||||
|
79065"
|
||||||
|
Content-Location: "http://www.amazon.com/exec/obidos/subs
|
||||||
|
t/home/redirect.html/103-3111065-25
|
||||||
|
79065"
|
||||||
|
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>
|
||||||
|
Amazon.com--Earth's Biggest Selection
|
||||||
|
</title>
|
||||||
|
<meta name="keywords" content="amazon.com,amazon books,amazon,amazon.com books,amazon music,amazon.com music,amazon video,amazon.com video,auctions,amazon auctions,amazon.com auctions,electronics,consumer electronics,gifts,amazon gifts,amazon.com gifts,cards,e-cards,e-mail cards,greeting cards,amazon cards,amazon.com cards,toys,amazon toys,amazon.com toys,games,amazon games,amazon.com games,toys & games,toys and games">
|
||||||
|
<style type="text/css"><!-- .serif { font-family: times,serif; font-size: medium; }
|
||||||
|
.sans { font-family: verdana,arial,helvetica,sans-serif; font-size: medium; }
|
||||||
|
.small { font-family: verdana,arial,helvetica,sans-serif; font-size: small; }
|
||||||
|
.h1 { font-family: verdana,arial,helvetica,sans-serif; color: #CC6600; font-size: medium; }
|
||||||
|
.h3color { font-family: verdana,arial,helvetica,sans-serif; color: #CC6600; font-size: small; }
|
||||||
|
.tiny { font-family: verdana,arial,helvetica,sans-serif; font-size: x-small; }
|
||||||
|
.listprice { font-family: arial,verdana,helvetica,sans-serif; text-decoration: line-through; font-size: small; }
|
||||||
|
.price { font-family: verdana,arial,helvetica,sans-serif; color: #990000; font-size: small; }
|
||||||
|
--></style>
|
||||||
|
</head>
|
||||||
|
<body bgcolor="#FFFFFF" link="#003399" alink="#FF9933" vlink="#996633" text="#000000" onLoad="document.searchform.elements[1].focus()">
|
||||||
|
<a name="top"></a>
|
||||||
|
<map name="right_top_nav_map">
|
||||||
|
<area shape="rect" href=/exec/obidos/shopping-basket/ref=top_nav_sb_gateway/103-3111065-2579065 coords="0,0,80,21">
|
||||||
|
<area shape="rect" href=/exec/obidos/wishlist/ref=cm_wl_topnav_gateway/103-3111065-2579065 coords="85,0,151,21">
|
||||||
|
<area shape="rect" href=/exec/obidos/account-access-login/ref=top_nav_ya_gateway/103-3111065-2579065 coords="155,0,256,21">
|
||||||
|
<area shape="rect" href=/exec/obidos/tg/browse/-/508510/ref=top_nav_hp_gateway/103-3111065-2579065 coords="260,0,299,21">
|
||||||
|
</map>
|
||||||
|
<map name="gateway_nav_map">
|
||||||
|
<area shape=rect coords="0,0,124,28" href=/exec/obidos/tg/stores/static/-/gateway/international-gateway/ref=gw_subnav_in/103-3111065-2579065>
|
||||||
|
<area shape=rect coords="125,0,228,28" href=/exec/obidos/tg/new-for-you/top-sellers/-/main/ref=gw_subnav_ts/103-3111065-2579065>
|
||||||
|
<area shape=rect coords="229,0,332,28" href=/exec/obidos/tg/browse/-/700060/ref=gw_subnav_target/103-3111065-2579065>
|
||||||
|
<area shape=rect coords="333,0,450,28" href=/exec/obidos/tg/browse/-/909656/ref=stuffandsubnav_td1_/103-3111065-2579065>
|
||||||
|
<area shape=rect coords="451,0,580,28" href=/exec/obidos/subst/misc/sell-your-stuff.html/ref=subnav_sys_/103-3111065-2579065>
|
||||||
|
</map>
|
||||||
|
<table border=0 width=100% cellspacing=0 cellpadding=0>
|
||||||
|
<tr><td width=100%>
|
||||||
|
<center>
|
||||||
|
<table width=100% border=0 cellspacing=0 cellpadding=0 vspace=0>
|
||||||
|
<tr>
|
||||||
|
<td width=25% rowspan=2> </td>
|
||||||
|
<td align=left valign=bottom><a href=/exec/obidos/subst/home/redirect.html/ref=nh_gateway/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/associates/navbar2000/logo-no-border(1).gif" width=148 height=43 alt="" border=0></a></td>
|
||||||
|
<td width=10%> </td>
|
||||||
|
<td align=right>
|
||||||
|
<img src="http://g-images.amazon.com/images/G/01/nav/personalized/cartwish/right-topnav-default-2.gif" width=300 height=22 alt="" USEMAP=#right_top_nav_map border=0></td>
|
||||||
|
<td align=right rowspan=2 width=25%>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr valign=bottom>
|
||||||
|
<td colspan=3 align=center>
|
||||||
|
<table align=center border=0 cellpadding=0 cellspacing=0><tr valign=bottom>
|
||||||
|
<td><a href=/exec/obidos/subst/home/home.html/ref%3Dtab%5Fgw%5Fgw%5F1/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/nav/personalized/tabs/welcome-on-whole.gif" width=60 height=26 border=0></a></td>
|
||||||
|
<td><a href=/exec/obidos/tg/stores/your/store-home/-/0/ref%3Dtab%5Fgw%5Ffr%5F2/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/nav/personalized/tabs/yourstore-off-sliced._ZCSTUART%27S,0,2,0,0,verdenab,7,90,90,80_.gif" width=81 height=26 border=0></a></td>
|
||||||
|
<td><a href=/exec/obidos/tg/browse/-/283155/ref%3Dtab%5Fgw%5Fb%5F3/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/nav/personalized/tabs/books-off-sliced.gif" width=39 height=26 border=0></a></td>
|
||||||
|
<td><a href=/exec/obidos/tg/browse/-/172282/ref%3Dtab%5Fgw%5Fe%5F4/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/nav/personalized/tabs/electronics-off-sliced.gif" width=74 height=26 border=0></a></td>
|
||||||
|
<td><a href=/exec/obidos/tg/browse/-/130/ref%3Dtab%5Fgw%5Fd%5F5/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/nav/personalized/tabs/dvd-off-sliced.gif" width=35 height=26 border=0></a></td>
|
||||||
|
<td><a href=/exec/obidos/tg/browse/-/171280/ref%3Dtab%5Fgw%5Ft%5F6/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/nav/personalized/tabs/toys-off-sliced.gif" width=47 height=26 border=0></a></td>
|
||||||
|
<td><a href=/exec/obidos/tg/browse/-/468642/ref%3Dtab%5Fgw%5Fvg%5F7/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/nav/personalized/tabs/videogames-off-sliced.gif" width=73 height=26 border=0></a></td>
|
||||||
|
<td><a href=/exec/obidos/tg/browse/-/600460/ref%3Dtab%5Fgw%5F%5F8/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/nav/personalized/tabs/corporate-off-sliced.gif" width=70 height=26 border=0></a></td>
|
||||||
|
<td><a href=/exec/obidos/subst/home/all-stores.html/ref%3Dtab_gw_storesdirectory/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/nav/personalized/tabs/see-more-off-sliced.gif" width=70 height=26 border=0></a></td>
|
||||||
|
</tr></table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</center>
|
||||||
|
</td></tr>
|
||||||
|
<tr align=center bgcolor=#006699>
|
||||||
|
<td><img src="http://g-images.amazon.com/images/G/01/nav/amazon/gateway/blue/gateway-subnav-default.gif" width=580 height=28 width=580 height=28 alt="" USEMAP="#gateway_nav_map" border=0></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td bgcolor=#ffffdd align=center class=small>
|
||||||
|
<font face=verdana,arial,helvetica size=-1>
|
||||||
|
<font color="#CC6600"><B>Hello, Stuart D. Gathman.</B></font>
|
||||||
|
We have <A href="http://www.amazon.com/exec/obidos/ilm-redirect/103-3111065-2579065?append-uid=no&path=http://www.amazon.com/exec/obidos/subst%2Frecs%2Finstant-recs-home.html%2Fref%3Dpd_ir_gw_r/ref=ilm_stripe_272005/103-3111065-2579065&message=272005,m1,26">recommendations</A> for you.
|
||||||
|
</font><font face=verdana,arial,helvetica size=-2>
|
||||||
|
(If you're not Stuart D. Gathman, <A href="http://www.amazon.com/exec/obidos/ilm-redirect/103-3111065-2579065?append-uid=no&path=http://www.amazon.com/exec/obidos/flex-sign-in%2Fref%3Dpd_ir_gw_r%2F%3Fopt%3Doa%26page%3Drecs%2Fsign-in-secure.html%26response%3Dtg%2Frecs%2Frecs-post-login-dispatch%2F-%2Frecs%2Fpd_rw_gw_r/ref=ilm_stripe_272005/103-3111065-2579065&message=272005,m1,26">click here</A>.)
|
||||||
|
</font>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<table width=100% cellpadding=0 cellspacing=0 border=0>
|
||||||
|
<tr valign=top>
|
||||||
|
<td width=174>
|
||||||
|
<TABLE border=0 cellspacing=0 cellpadding=0><TR valign=bottom align=center>
|
||||||
|
<td><img src="http://g-images.amazon.com/images/G/01/v9/search-browse/search-gateway.gif" width=171 height=19 border=0 alt="Search Amazon.com"></td>
|
||||||
|
</TR> <TR valign=top align=center><TD> <TABLE border=0 width= 171 cellpadding=1 cellspacing=0 bgcolor=#708090 ><TR> <TD width=100%><TABLE width=100% border=0 cellpadding=4 cellspacing=0 bgcolor=#708090><TR> <TD bgcolor=#FFCC66 valign=top width=100%>
|
||||||
|
<form method="post" action="/exec/obidos/search-handle-form/103-3111065-2579065" name="searchform">
|
||||||
|
<select name=index>
|
||||||
|
<option value=blended selected>All Products
|
||||||
|
<option value=books>Books
|
||||||
|
<option value=music>Popular Music
|
||||||
|
<option value=music-dd>Music Downloads
|
||||||
|
<option value=classical>Classical Music
|
||||||
|
<option value="dvd">DVD
|
||||||
|
<option value="vhs">VHS
|
||||||
|
<option value=theatrical>Movie Showtimes
|
||||||
|
<option value=toys>Toys
|
||||||
|
<option value=baby>Baby
|
||||||
|
<option value=pc-hardware>Computers
|
||||||
|
<option value=videogames>Video Games
|
||||||
|
<option value=electronics>Electronics
|
||||||
|
<option value=photo>Camera & Photo
|
||||||
|
<option value=software>Software
|
||||||
|
<option value=tools>Tools & Hardware
|
||||||
|
<option value=magazines>Magazines
|
||||||
|
<option value=garden>Outdoor Living
|
||||||
|
<option value=kitchen>Kitchen
|
||||||
|
<option value=travel>Travel
|
||||||
|
<option value=wireless-phones>Cell Phones & Service
|
||||||
|
<option value=outlet>Outlet
|
||||||
|
<option value=auction-redirect>Auctions
|
||||||
|
<option value=fixed-price-redirect>zShops
|
||||||
|
</select>
|
||||||
|
<input type="text" name="field-keywords" size="15">
|
||||||
|
<input type="image" height="21" width="21" border=0 value="Go" name="Go" src="http://g-images.amazon.com/images/G/01/v9/search-browse/go-button-gateway.gif" align=absmiddle>
|
||||||
|
</TD> </TR> </TABLE> </TD> </TR> </TABLE> </TD> </form>
|
||||||
|
</TR> </TABLE> <br clear=left>
|
||||||
|
<TABLE border=0 cellspacing=0 cellpadding=0>
|
||||||
|
<TR valign=bottom align=center>
|
||||||
|
<td><img src="http://g-images.amazon.com/images/G/01/v9/search-browse/browse-gateway.gif" width=171 height=19 border=0 alt="Browse Amazon.com"></td>
|
||||||
|
</TR> <TR valign=top align=center>
|
||||||
|
<TD> <TABLE border=0 width= 171 cellpadding=1 cellspacing=0 bgcolor=#708090 ><TR> <TD width=100%><TABLE width=100% border=0 cellpadding=4 cellspacing=0 bgcolor=#708090><TR> <TD bgcolor=#ffffff valign=top width=100%>
|
||||||
|
<table cellpadding=3 cellspacing=0>
|
||||||
|
<tr>
|
||||||
|
<td class=small>• <b><a href="/exec/obidos/tg/browse/-/283155/ref=gw_br_bo/103-3111065-2579065">Books</a></b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class=small>• <b><a href="/exec/obidos/tg/browse/-/172282/ref=gw_br_el/103-3111065-2579065">Electronics</a></b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class=small>• <b><a href="/exec/obidos/tg/browse/-/540744/ref=gw_br_ba/103-3111065-2579065">Baby &</a><br> <a href="/exec/obidos/tg/browse/-/540744/ref=gw_br_ba/103-3111065-2579065">Baby Registry</a></b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class=small>• <b><a href="/exec/obidos/tg/browse/-/5174/ref=gw_br_mu/103-3111065-2579065">Music</a></b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class=small>• <b><a href="/exec/obidos/redirect-to-partner/ref=gw_br_dscm/103-3111065-2579065?name=dscm&aid=2&aparam=tb5270_bhp&trx=8056">Health & Beauty</a></b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class=small>• <b><a href="/exec/obidos/tg/browse/-/130/ref=gw_br_dvd/103-3111065-2579065">DVD</a></b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class=small>• <b><a href="/exec/obidos/tg/browse/-/229534/ref=gw_br_sw/103-3111065-2579065">Software</a></b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class=small>• <b><a href="/exec/obidos/tg/browse/-/284507/ref=gw_br_ki/103-3111065-2579065">Kitchen &</a><br> <a href="/exec/obidos/tg/browse/-/284507/ref=gw_br_ki/103-3111065-2579065">Housewares</a></b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class=small>• <b><a href="/exec/obidos/tg/browse/-/228013/ref=gw_br_hi/103-3111065-2579065">Tools &</a><br> <a href="/exec/obidos/tg/browse/-/228013/ref=gw_br_hi/103-3111065-2579065">Hardware</a></b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class=small>• <b><a href="/exec/obidos/tg/browse/-/541966/ref=gw_br_pc/103-3111065-2579065">Computers</a></b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class=small>• <b><a href="/exec/obidos/tg/browse/-/502394/ref=gw_br_p/103-3111065-2579065">Camera & Photo</a></b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class=small>• <b><a href="/exec/obidos/tg/browse/-/562436/ref=gw_br_th/103-3111065-2579065">Movie Showtimes</a></b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class=small>• <b><a href="/exec/obidos/tg/browse/-/468642/ref=gw_br_cvg/103-3111065-2579065">Computer &</a><br> <a href="/exec/obidos/tg/browse/-/468642/ref=gw_br_cvg/103-3111065-2579065">Video Games</a></b></td>
|
||||||
|
</tr> <tr>
|
||||||
|
<td class=small>• <b><a href="/exec/obidos/tg/browse/-/171280/ref=gw_br_tg/103-3111065-2579065">Toys & Games</a></b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class=small>• <b><a href="/exec/obidos/tg/browse/-/301185/ref=gw_br_wi/103-3111065-2579065">Cell Phones</a><br> <a href="/exec/obidos/tg/browse/-/301185/ref=gw_br_wi/103-3111065-2579065">& Service</a></b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class=small>• <b><a href="/exec/obidos/tg/browse/-/404272/ref=gw_br_vi/103-3111065-2579065">Video</a></b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class=small>• <b><a href="/exec/obidos/tg/browse/-/599858/ref=gw_br_zi/103-3111065-2579065">Magazine</a><br> <a href="/exec/obidos/tg/browse/-/599858/ref=gw_br_zi/103-3111065-2579065">Subscriptions</a></b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class=small>• <b><a href="/exec/obidos/tg/browse/-/286168/ref=gw_br_lp/103-3111065-2579065">Outdoor Living</a></b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class=small>• <b><a href="/exec/obidos/tg/browse/-/605012/ref=gw_br_tr/103-3111065-2579065">Travel</a></b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class=small>• <b><a href="/exec/obidos/acn-redirect-to-partner/ref=gw_br_cars/103-3111065-2579065?partner-name=carsdirect&partner-url=home%3Fpartner%3Damzn%26customerid%3Dbrowse">Cars</a></b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class=small>• <b><a href="/exec/obidos/tg/browse/-/229220/ref=gw_br_gi/103-3111065-2579065">Gifts &</a><br> <a href="/exec/obidos/tg/browse/-/229220/ref=gw_br_gi/103-3111065-2579065">Gift Certificates</a></b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class=small>• <b><a href="http://s1.amazon.com/exec/varzea/subst/home/home.html/ref=gw_br_au/103-3111065-2579065">Auctions</a></b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class=small>• <b><a href="http://s1.amazon.com/exec/varzea/subst/home/fixed.html/ref=gw_br_zs/103-3111065-2579065">zShops</a></b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class=small>• <b><a href="/exec/obidos/tg/browse/-/517808/ref=gw_br_ou/103-3111065-2579065">Outlet</a></b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class=small>• <b><a href="/exec/obidos/tg/browse/-/600460/ref=gw_br_cb/103-3111065-2579065">Corporate</a> <br> <a href="/exec/obidos/tg/browse/-/600460/ref=gw_br_cb/103-3111065-2579065">Accounts</a></b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class=small>
|
||||||
|
<a href="/exec/obidos/flex-sign-in/ref=pd_fr_gw_fav_edt/103-3111065-2579065?page=personalization/favorites/favorites-sign-in-secure.html&response=favorites-edit/personalization/favorites/edit-areas.html&pass_through=product-group-id.gateway.hp&method=GET">
|
||||||
|
<img src="http://g-images.amazon.com/images/G/01/buttons/edit-favorites.gif" width=69 height=15 border=0 valign=top vspace=2></a><br>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class=small><b>Browse Partners</b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class=small>• <a href="/exec/obidos/tg/browse/-/700060/ref=gw_tarb_/103-3111065-2579065"><img src="http://g-images.amazon.com/images/G/01/target/target-logo-sm.gif" width=71 height=17 border=0 alt=Target></a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class=small>• <a href="/exec/obidos/tg/browse/-/171280/ref=gw_trub_/103-3111065-2579065"><img src="http://g-images.amazon.com/images/G/01/toys/navigation/tru-logo.gif" width=117 height=14 border=0 alt=Toysrus.com></a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class=small>• <a href="/exec/obidos/tg/browse/-/540744/ref=gw_brub_/103-3111065-2579065"><img src="http://g-images.amazon.com/images/G/01/toys/navigation/bru-logo.gif" width=136 height=15 border=0 alt=Babiesrus.com></a></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</TD> </TR> </TABLE> </TD> </TR> </TABLE> </TD>
|
||||||
|
</TR>
|
||||||
|
</TABLE> <br>
|
||||||
|
<TABLE border=0 width=171 cellpadding=1 cellspacing=0 bgcolor=#708090 ><TR> <TD width=100%><TABLE width=100% border=0 cellpadding=4 cellspacing=0 bgcolor=#708090><TR> <TD bgcolor=#ffffff valign=top width=100%>
|
||||||
|
<font face=verdana,arial,helvetica color=#000000 size=-1><b>Special Features</b></font><br>
|
||||||
|
<font face=verdana,arial,helvetica size=-1>
|
||||||
|
<ul><li> <A href="/exec/obidos/subst/alerts/signup.html/ref=gw_hp_ls_1_1/103-3111065-2579065">Alerts</A><li> <A href="/exec/obidos/subst/misc/anywhere/anywhere.html/ref=gw_hp_ls_1_2/103-3111065-2579065">Amazon.com
|
||||||
|
Anywhere</A><li> <A href="/exec/obidos/subst/misc/amazon-credit/marketing-page.html/ref=gw_hp_ls_1_3/103-3111065-2579065">Amazon Credit Account</A><li> <A href="/exec/obidos/subst/delivers/delivers-signup-combo.html/ref=gw_hp_ls_1_4/103-3111065-2579065">Delivers</A><li><A href="/exec/obidos/tg/browse/-/225840/ref=gw_hp_ls_1_5/103-3111065-2579065">Free e-Cards</A><li><A href="/exec/obidos/subst/community/community-home.html/ref=gw_hp_ls_1_6/103-3111065-2579065">Friends & Favorites</A><li> <A href="/exec/obidos/subst/gifts/gift-services/gift-certificates.html/ref=gw_hp_ls_1_7/103-3111065-2579065">Gift
|
||||||
|
Certificates</A><li> <A href="http://auctions.amazon.com/exec/varzea/subst/fx/home.html/ref=gw_hp_ls_1_8/103-3111065-2579065">Honor
|
||||||
|
System</A><li> <A href="/exec/obidos/subst/community/community.html/ref=gw_hp_ls_1_9/103-3111065-2579065">Purchase
|
||||||
|
Circles</A><li>
|
||||||
|
<A href="/exec/obidos/tg/browse/-/885446/ref=gw_hp_ls_1_10/103-3111065-2579065">Wedding
|
||||||
|
Registry</A></ul>
|
||||||
|
</font>
|
||||||
|
</TD> </TR> </TABLE> </TD> </TR> </TABLE> <br>
|
||||||
|
<TABLE border=0 width=171 cellpadding=1 cellspacing=0 bgcolor=#708090 ><TR> <TD width=100%><TABLE width=100% border=0 cellpadding=4 cellspacing=0 bgcolor=#708090><TR> <TD bgcolor=#ffffff valign=top width=100%>
|
||||||
|
<font face=verdana,arial,helvetica color=#000000 size=-1><b>Associates</b></font><br>
|
||||||
|
<font face=verdana,arial,helvetica size=-1>
|
||||||
|
Sell books, music, videos, and more from your
|
||||||
|
Web site. <A href="/exec/obidos/subst/associates/join/associates.html/ref=gw_hp_ls_2_1/103-3111065-2579065">Start earning
|
||||||
|
today</A>!<BR>
|
||||||
|
</font>
|
||||||
|
</TD> </TR> </TABLE> </TD> </TR> </TABLE> <br>
|
||||||
|
<p>
|
||||||
|
<br clear=all>
|
||||||
|
</td>
|
||||||
|
<td> </td>
|
||||||
|
<td>
|
||||||
|
<center>
|
||||||
|
</center>
|
||||||
|
<br clear=all><p>
|
||||||
|
<A href="http://www.amazon.com/exec/obidos/ilm-redirect/103-3111065-2579065?append-uid=no&path=http://www.amazon.com/exec/obidos/tg/browse/-/283155/ref=ilm_rc_285799/103-3111065-2579065&message=285799,m1,27">
|
||||||
|
<center><img src="http://g-images.amazon.com/images/G/01/books/homepage-pricing/books-home-pricing-iii.gif" width=257 height=99 border=0></center>
|
||||||
|
</A>
|
||||||
|
<br clear=all><br>
|
||||||
|
<A href="http://www.amazon.com/exec/obidos/ilm-redirect/103-3111065-2579065?append-uid=no&path=http://www.amazon.com/exec/obidos/tg/browse/-/753570/ref=ilm_rc_283024/103-3111065-2579065&message=283024,gw_lr_dvd_lor,28"><img src="http://g-images.amazon.com/images/G/01/icons/thumbnails/b00003cwt6_thumb.gif" width=41 height=60 border=0 valign=top align=left></A>
|
||||||
|
Pre-order the Oscar®-winning blockbuster <A href="http://www.amazon.com/exec/obidos/ilm-redirect/103-3111065-2579065?append-uid=no&path=http://www.amazon.com/exec/obidos/tg/browse/-/753570/ref=ilm_rc_283024/103-3111065-2579065&message=283024,gw_lr_dvd_lor,28"><I>The Lord of the Rings: The Fellowship of the Ring</I></A>, arriving on <A href="http://www.amazon.com/exec/obidos/ilm-redirect/103-3111065-2579065?append-uid=no&path=http://www.amazon.com/exec/obidos/ASIN/B00003CWT6/ref=ilm_rc_283024/103-3111065-2579065&message=283024,gw_lr_dvd_lor,28">DVD</A> and <A href="http://www.amazon.com/exec/obidos/ilm-redirect/103-3111065-2579065?append-uid=no&path=http://www.amazon.com/exec/obidos/ASIN/B000065U6Q/ref=ilm_rc_283024/103-3111065-2579065&message=283024,gw_lr_dvd_lor,28">video</A> August 6.
|
||||||
|
<br clear=all><br>
|
||||||
|
<b class=small><A href="/exec/obidos/tg/browse/-/229220/ref=gw_hp_cs_1_1/103-3111065-2579065">In Gifts</A></b><br>
|
||||||
|
<A href="/exec/obidos/tg/browse/-/229220/ref=gw_hp_cs_2_1/103-3111065-2579065"><img src="http://g-images.amazon.com/images/G/01/marketing/mothers_day/md_sd_roto.jpg" width=100 height=95 border=0 align=left hspace=4></A>
|
||||||
|
<b><font face=verdana,arial,helvetica color=#CC6600>Mother's Day Is May 12</font></b><br>
|
||||||
|
We've made it fun and easy to buy the perfect
|
||||||
|
present for Mom. Shop by <A href="/exec/obidos/tg/stores/recs/gift-wizard-refine/-/holiday/ref=gw_hp_cs_2_2/103-3111065-2579065">recipient</A>
|
||||||
|
or <A href="/exec/obidos/tg/stores/recs/gift-wizard/-/price/ref=gw_hp_cs_2_3/103-3111065-2579065">price</A>,
|
||||||
|
browse <A href="/exec/obidos/tg/stores/recs/gift-wizard/-/topsellers/ref=gw_hp_cs_2_4/103-3111065-2579065">top
|
||||||
|
sellers</A>, or order <A href="http://www.amazon.com/exec/obidos/redirect-to-external-url/103-3111065-2579065?path=http%3A//www.proflowers.com/freechocolate/freechocolate.cfm%3FREF%3DFCHAmazonGatewayExp042702">flowers</A>.
|
||||||
|
Visit <A href="/exec/obidos/tg/browse/-/229220/ref=gw_hp_cs_2_5/103-3111065-2579065">Gifts</A> for
|
||||||
|
these and more great ideas for expressing your love and
|
||||||
|
appreciation.<BR>
|
||||||
|
<br clear=left>
|
||||||
|
<br clear=all>
|
||||||
|
<a href=/exec/obidos/instant-recs/recs/instant-recs-home.html/ref=pd_gw_qpt_h/103-3111065-2579065><b class=small>Your Recommendations</b></a>
|
||||||
|
<br> <b class=h1>
|
||||||
|
<i>War in Heaven</i>
|
||||||
|
</b>
|
||||||
|
</b><br>
|
||||||
|
<a href=/exec/obidos/ASIN/0802812198/ref=pd_gw_qpt_1/103-3111065-2579065><img src="http://images.amazon.com/images/P/0802812198.01.__PE20_PIm.arrow,TopLeft,-2,-19_SCTZZZZZZZ_.jpg" width=76 height=116 vspace=3 hspace=7 align=left border=0></a>
|
||||||
|
<b>Amazon.com</b><br>
|
||||||
|
"The telephone was ringing wildly," begins Charles Williams's novel <I>War in Heaven</I>, "but without result, since there was no-one in the room but the corpse." From this abrupt--and darkly humorous--start, Williams takes us on a 20th-century version of the Grail quest, with an Archdeacon, a Duke, and an...
|
||||||
|
<a href=/exec/obidos/ASIN/0802812198/ref=pd_gw_qpt_1/103-3111065-2579065>
|
||||||
|
<font size=-1>Read more</font></a>
|
||||||
|
<span class=tiny>
|
||||||
|
|
|
||||||
|
(<a href=/exec/obidos/tg/recs/ir-why/-/books/0/regular/none/0802812198/gw/1/pc/3/none/ref=pd_gw_qpt_1/103-3111065-2579065>Why was I recommended this?</a>)
|
||||||
|
</span>
|
||||||
|
<br clear=all>
|
||||||
|
<br><b class=small>More Recommendations</b><br>
|
||||||
|
<img src="http://g-images.amazon.com/images/G/01/icons/small-blue-books-icon.gif" width=18 height=18 border=0 alt=Icon >
|
||||||
|
<a href=/exec/obidos/ASIN/0471070408/ref=pd_gw_qpt_2/103-3111065-2579065><i>Reliable Linux</i></a> by Iain Campbell
|
||||||
|
<span class=tiny>
|
||||||
|
(<a href=/exec/obidos/tg/recs/ir-why/-/books/0/regular/none/0471070408/gw/1/pc/3/none/ref=pd_gw_qpt_2/103-3111065-2579065>Why?</a>)
|
||||||
|
</span>
|
||||||
|
<br>
|
||||||
|
<img src="http://g-images.amazon.com/images/G/01/icons/small-blue-books-icon.gif" width=18 height=18 border=0 alt=Icon >
|
||||||
|
<a href=/exec/obidos/ASIN/1565926102/ref=pd_gw_qpt_3/103-3111065-2579065><i>Programming PHP</i></a> by Rasmus Lerdorf, et al
|
||||||
|
<span class=tiny>
|
||||||
|
(<a href=/exec/obidos/tg/recs/ir-why/-/books/0/regular/none/1565926102/gw/1/pc/3/none/ref=pd_gw_qpt_3/103-3111065-2579065>Why?</a>)
|
||||||
|
</span>
|
||||||
|
<br>
|
||||||
|
<img src="http://g-images.amazon.com/images/G/01/icons/small-blue-books-icon.gif" width=18 height=18 border=0 alt=Icon >
|
||||||
|
<a href=/exec/obidos/ASIN/0802812201/ref=pd_gw_qpt_4/103-3111065-2579065><i>Descent into Hell</i></a> by Charles W. Williams
|
||||||
|
<span class=tiny>
|
||||||
|
(<a href=/exec/obidos/tg/recs/ir-why/-/books/0/regular/none/0802812201/gw/1/pc/3/none/ref=pd_gw_qpt_4/103-3111065-2579065>Why?</a>)
|
||||||
|
</span>
|
||||||
|
<br>
|
||||||
|
<img src="http://g-images.amazon.com/images/G/01/icons/small-blue-books-icon.gif" width=18 height=18 border=0 alt=Icon >
|
||||||
|
<a href=/exec/obidos/ASIN/059600186X/ref=pd_gw_qpt_5/103-3111065-2579065><i>Network Troubleshooting Tools (O'Reilly System Administration)</i></a> by Joseph D. Sloan
|
||||||
|
<span class=tiny>
|
||||||
|
(<a href=/exec/obidos/tg/recs/ir-why/-/books/0/regular/none/059600186X/gw/1/pc/3/none/ref=pd_gw_qpt_5/103-3111065-2579065>Why?</a>)
|
||||||
|
</span>
|
||||||
|
<br>
|
||||||
|
<p>
|
||||||
|
<font face=verdana,arial,helvetica size=-1><a href=/exec/obidos/tg/stores/your/favorites/-/music/ref=pd_fr_gw_nr_h/103-3111065-2579065><b>Your Music Store</b></a></font><br>
|
||||||
|
<font face=verdana,arial,helvetica color=#CC6600><b>
|
||||||
|
Isaac Freeman, et al,
|
||||||
|
<i>Beautiful Stars</i>
|
||||||
|
</b></font>
|
||||||
|
<br>
|
||||||
|
<a href=/exec/obidos/ASIN/B000063TQV/ref=pd_fr_qw_nr_1/103-3111065-2579065><img src="http://images.amazon.com/images/P/B000063TQV.01.26TLZZZZ.jpg" width=73 height=71 vspace=3 hspace=7 align=left border=0></a>
|
||||||
|
Great African American gospel music has an indisputable power, rooted in the audible faith of its performers and the beauty of their voices. As the bass singer of the <a href="/exec/obidos/tg/stores/artist/glance/-/73920/103-3111065-2579065">Fairfield Four</a>, an a cappella group that started more than a half century ago,...
|
||||||
|
<a href=/exec/obidos/ASIN/B000063TQV/ref=pd_fr_qw_nr_1/103-3111065-2579065><font size=-1>Read more</font></a>
|
||||||
|
<br>
|
||||||
|
<br clear=left>
|
||||||
|
<br>
|
||||||
|
<table border=0 cellpadding=2 cellspacing=0><tr><td colspan=2>
|
||||||
|
<p><b class="small">More Stores:</b>
|
||||||
|
</td></tr>
|
||||||
|
<tr valign=top><td width=1%><a href=/exec/obidos/tg/stores/your/favorites/-/electronics/ref=pd_fr_qw_nr_2/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/icons/small-blue-electronics-icon.gif" width=18 height=18 alt=Icon border=0 align=absmiddle></a></td><td><b class="small"><a href=/exec/obidos/tg/stores/your/favorites/-/electronics/ref=pd_fr_gw_nr_2_p/103-3111065-2579065>Your Electronics Store</a>:</b> <a href=/exec/obidos/ASIN/B000063574/ref=pd_fr_gw_nr_2/103-3111065-2579065>iRiver SlimX iMP-350 CD/MP3 Player with 8 minutes ASP and Upgradeable Firmware</a>
|
||||||
|
by iRiver
|
||||||
|
</td></tr>
|
||||||
|
<tr valign=top><td width=1%><a href=/exec/obidos/tg/stores/your/favorites/-/video/ref=pd_fr_qw_nr_3/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/icons/small-blue-video-icon.gif" width=18 height=18 alt=Icon border=0 align=absmiddle></a></td><td><b class="small"><a href=/exec/obidos/tg/stores/your/favorites/-/video/ref=pd_fr_gw_nr_3_p/103-3111065-2579065>Your Video Store</a>:</b> <a href=/exec/obidos/ASIN/B000062XNA/ref=pd_fr_gw_nr_3/103-3111065-2579065><i>Ocean's Eleven</i></a>
|
||||||
|
<b>VHS</b> ~ George Clooney
|
||||||
|
</td></tr>
|
||||||
|
</table>
|
||||||
|
<p>
|
||||||
|
<b><font face=verdana,arial,helvetica color=#CC6600>Listmania!</font></b><br>
|
||||||
|
<font face=verdana,arial,helvetica size=-2>
|
||||||
|
(<a href=/exec/obidos/tg/browse/-/542566/103-3111065-2579065>What is this?</a>)
|
||||||
|
</font><br>
|
||||||
|
<table width=100% border=0 cellpadding=5 cellspacing=0>
|
||||||
|
<tr valign=top>
|
||||||
|
<td width=50% class=small>
|
||||||
|
<a href=/exec/obidos/tg/listmania/list-browse/-/2RKS17C9X4D3F/ref=pd_gw_lmq_1/103-3111065-2579065><img src="http://images.amazon.com/images/P/0072127732.01.__PIm.arrow,TopLeft,-2,-19_SCTZZZZZZZ_.jpg" width=76 height=109 border=0 vspace=4 hspace=5></a>
|
||||||
|
<p>
|
||||||
|
<font face=verdana,arial,helvetica size=-1>
|
||||||
|
<a href=/exec/obidos/tg/listmania/list-browse/-/2RKS17C9X4D3F/ref=pd_gw_lmq_1/103-3111065-2579065><b>Best Linux Security books</b></a>: A list by <a href=/exec/obidos/tg/cm/member-fil/-/A3362WVVMJ3LE9/ref=pd_gw_lmq_n1/103-3111065-2579065>J. Parker</a>, Administrator, hacker.<br>
|
||||||
|
(7 item list)</font>
|
||||||
|
</td>
|
||||||
|
<td width=50% class=small>
|
||||||
|
<a href=/exec/obidos/tg/listmania/list-browse/-/2B0DIAPG2D3RT/ref=pd_gw_lmq_2/103-3111065-2579065><img src="http://images.amazon.com/images/P/0070419531.01.__PIm.arrow,TopLeft,-2,-19_SCTZZZZZZZ_.jpg" width=76 height=109 border=0 vspace=4 hspace=5></a>
|
||||||
|
<p>
|
||||||
|
<font face=verdana,arial,helvetica size=-1>
|
||||||
|
<a href=/exec/obidos/tg/listmania/list-browse/-/2B0DIAPG2D3RT/ref=pd_gw_lmq_2/103-3111065-2579065><b>Networking</b></a>: A list by <a href=/exec/obidos/tg/cm/member-fil/-/AJINE650CAMUQ/ref=pd_gw_lmq_n2/103-3111065-2579065>gakis</a>, Engineer<br>
|
||||||
|
(13 item list)</font>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan=2 class=small><ul>
|
||||||
|
<li><a href=/exec/obidos/tg/listmania/list-browse/-/IEF1DNVKZO8B/ref=pd_gw_lmq_3/103-3111065-2579065>My Coder Library</a>: A list by <a href=/exec/obidos/tg/cm/member-fil/-/A3RK9LZQKL2YIN/ref=pd_gw_lmq_n3/103-3111065-2579065>John Washam</a><br> <li><a href=/exec/obidos/tg/listmania/list-browse/-/LE6A7H4L7VZK/ref=pd_gw_lmq_4/103-3111065-2579065>ALL THE FANTASY YOU'LL EVER NE</a>: A list by <a href=/exec/obidos/tg/cm/member-fil/-/A3628L43ZVEMP5/ref=pd_gw_lmq_n4/103-3111065-2579065>aramis</a><br> <li><a href=/exec/obidos/tg/listmania/list-browse/-/1MD5H6RUOIMIU/ref=pd_gw_lmq_5/103-3111065-2579065>Mythopoeic Fantasy</a>: A list by <a href=/exec/obidos/tg/cm/member-fil/-/A7CSNW9E46NR5/ref=pd_gw_lmq_n5/103-3111065-2579065>Vera Nazarian</a><br> </ul></td></tr></table>
|
||||||
|
<p>
|
||||||
|
<b class=small><A href="/exec/obidos/tg/browse/-/605012/ref=gw_hp_cb_1_1/103-3111065-2579065">In Travel</A></b><br>
|
||||||
|
<A href="/exec/obidos/tg/browse/-/605012/ref=gw_hp_cb_2_1/103-3111065-2579065"><img src="http://g-images.amazon.com/images/G/01/travel/promotions/travel-gateway1.gif" width=100 height=95 border=0 align=left hspace=4></A>
|
||||||
|
<b><font face=verdana,arial,helvetica color=#CC6600>Your Next Vacation Starts
|
||||||
|
Here</font></b><br>
|
||||||
|
Save up to 70% on hotels from Vegas to New York
|
||||||
|
and everywhere in between on <A href="/exec/obidos/acn-redirect-to-partner/103-3111065-2579065?partner-name=expedia&partner-url=pubspec/scripts/eap.asp%3FEAPID%3D11420-1%26GOTO%3DDAILY%26Page%3D/deals/hoteldeals.asp%3Frfrr%3D-2980">Expedia.com</A>.
|
||||||
|
Book a flight during Hotwire's <A href="/exec/obidos/acn-redirect-to-partner/103-3111065-2579065?partner-name=hotwire&partner-url=index.jsp%3Fsid%3D39151%26bid%3DB627">major-airline Spring Sale</A> through May 2 and fly the
|
||||||
|
big-name airlines at no-name airline prices. <A href="/exec/obidos/acn-redirect-to-partner/103-3111065-2579065?partner-name=thevacationstore&partner-url=cruises/show_cruise.asp%3Fd%3D%26i%3D743065%26c%3D24%26v%3D110">The
|
||||||
|
Vacation Store</A> is offering seven-day Holland America
|
||||||
|
Caribbean cruises from just $599. <BR>
|
||||||
|
<br clear=left>
|
||||||
|
<td width=174>
|
||||||
|
<table width=100% cellpadding=3 cellspacing=0 border=0>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href=/exec/obidos/subst/xs/hotpicks.html/ref=xs_ie_13_gw/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/marketing/cross-shop/web-labs/lp_gate_roto_t._ZCStuart%5c,,3,5,300,300,verdenab,14,204,0,0_SCLZZZZZZZ_.gif" width=174 height=34 border=0></a><br>
|
||||||
|
<a href=/exec/obidos/subst/xs/hotpicks.html/ref=xs_ie_13_gw/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/marketing/cross-shop/web-labs/lp_gate_roto_m.gif" width=174 height=200 border=0></a><br>
|
||||||
|
<a href=/exec/obidos/subst/xs/hotpicks.html/ref=xs_ie_13_gw/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/marketing/cross-shop/web-labs/lp_gate_roto_b.gif" width=174 height=231 border=0></a><br>
|
||||||
|
<a href=/exec/obidos/tg/new-for-you/new-for-you/-/main/ref=pd_nfy_gw_n/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/banners/n4u/n4u-header-recognized-01.gif" width=174 height=41 hspace=0 vspace=0 align=right border=0 alt="New For You"></a><br clear=all>
|
||||||
|
<table border=0 bgcolor=#708090 cellpadding=1 cellspacing=0 width=174 align=right valign=top vspace=0 hspace=0><tr><td>
|
||||||
|
<table border=0 cellpadding=3 cellspacing=0 width=100% bgcolor=#ffffff>
|
||||||
|
<tr><td bgcolor=#ffffff align=middle>
|
||||||
|
<span class=small><font color=#CC6600><b>Stuart,</b></font> check out what's<b> <a href=/exec/obidos/tg/new-for-you/new-for-you/-/main/ref=pd_nfy_gw_n/103-3111065-2579065>New for You</a></b>:<br></span>
|
||||||
|
</td></tr>
|
||||||
|
<tr><td bgcolor=#ffffff align=middle>
|
||||||
|
<span class=tiny>(If you're not Stuart D. Gathman, <a href=/exec/obidos/flex-sign-in/ref=pd_nfy_gw_n/103-3111065-2579065?opt=o&page=misc/login/flex-sign-in-secure.html&response=tg/new-for-you/new-for-you/-/main>click here</a>.)</span>
|
||||||
|
<br><br>
|
||||||
|
</td></tr>
|
||||||
|
<tr bgcolor=#eeeecc><td>
|
||||||
|
<a href=/exec/obidos/tg/new-for-you/inbox/inbox/-/main/ref=pd_nfy_gw_ibx/103-3111065-2579065><b class=small>Your Message Center</b></a>
|
||||||
|
</td></tr>
|
||||||
|
<tr bgcolor=#ffffee><td>
|
||||||
|
<table><tr bgcolor=#ffffee>
|
||||||
|
<td valign=top><a href=/exec/obidos/tg/new-for-you/inbox/inbox/-/main/ref=pd_nfy_gw_ibx/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/icons/exclamation-clear.gif" width=20 height=20 border=0 alt=!></a></td>
|
||||||
|
<td class=small> You have <a href=/exec/obidos/tg/new-for-you/inbox/inbox/-/main/ref=pd_nfy_gw_ibx/103-3111065-2579065>5 new messages</a>.
|
||||||
|
<br><br>
|
||||||
|
</td>
|
||||||
|
</tr></table>
|
||||||
|
</td></tr>
|
||||||
|
<tr bgcolor=#eeeecc><td>
|
||||||
|
<font face=verdana,arial,helvetica size=-1><a href=/exec/obidos/shopping-basket/ref=pd_nfy_gw_sc/103-3111065-2579065><b>Your Shopping Cart</b></a></font>
|
||||||
|
</td></tr>
|
||||||
|
<tr><td>
|
||||||
|
<table><tr>
|
||||||
|
<td valign=top><a href=/exec/obidos/shopping-basket/ref=pd_nfy_gw_sc/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/icons/shopping-cart-small.gif" width=25 height=25 border=0 alt="Shopping Cart" align=left></a></td>
|
||||||
|
<td valign=top><font face=verdana,arial,helvetica size=-1>You have 0 items in <a href=/exec/obidos/shopping-basket/ref=pd_nfy_gw_sc/103-3111065-2579065>your Shopping Cart</a>.</font><br><br></td>
|
||||||
|
</tr></table>
|
||||||
|
</td></tr></table>
|
||||||
|
<table border=0 cellpadding=3 cellspacing=0 width=100% bgcolor=#ffffff vspace=0>
|
||||||
|
<tr bgcolor=#eeeecc><td class=small>
|
||||||
|
<a href=/exec/obidos/tg/new-for-you/new-releases/-/main/ref=pd_nfy_gw_n/103-3111065-2579065><b>Your New Releases</b></a>
|
||||||
|
</td></tr></table>
|
||||||
|
<table border=0 cellpadding=3 cellspacing=0 width=100% bgcolor=#ffffff vspace=0>
|
||||||
|
<tr valign=top><td>
|
||||||
|
<a href=/exec/obidos/tg/new-for-you/new-releases/-/music/37/ref=pd_nfy_gw_n1/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/icons/small-blue-music-icon.gif" width=18 height=18 border=0 alt=Icon ></a></td><td>
|
||||||
|
<a href=/exec/obidos/tg/new-for-you/new-releases/-/music/37/ref=pd_nfy_gw_n1/103-3111065-2579065><font face=verdana,arial,helvetica size=-1>Pop</font></a>
|
||||||
|
</td></tr>
|
||||||
|
<tr valign=top><td>
|
||||||
|
<a href=/exec/obidos/tg/new-for-you/new-releases/-/music/173429/ref=pd_nfy_gw_n2/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/icons/small-blue-music-icon.gif" width=18 height=18 border=0 alt=Icon ></a></td><td>
|
||||||
|
<a href=/exec/obidos/tg/new-for-you/new-releases/-/music/173429/ref=pd_nfy_gw_n2/103-3111065-2579065><font face=verdana,arial,helvetica size=-1>Christian & Gospel</font></a>
|
||||||
|
</td></tr>
|
||||||
|
<tr valign=top><td>
|
||||||
|
<a href=/exec/obidos/tg/new-for-you/new-releases/-/books/5/ref=pd_nfy_gw_n3/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/icons/small-blue-books-icon.gif" width=18 height=18 border=0 alt=Icon ></a></td><td>
|
||||||
|
<a href=/exec/obidos/tg/new-for-you/new-releases/-/books/5/ref=pd_nfy_gw_n3/103-3111065-2579065><font face=verdana,arial,helvetica size=-1>Computers & Internet</font></a>
|
||||||
|
</td></tr>
|
||||||
|
<tr valign=top><td>
|
||||||
|
<a href=/exec/obidos/tg/new-for-you/new-releases/-/kitchen/289814/ref=pd_nfy_gw_n4/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/icons/icon-kitchen-blue.gif" width=18 height=18 border=0 alt=Icon ></a></td><td>
|
||||||
|
<a href=/exec/obidos/tg/new-for-you/new-releases/-/kitchen/289814/ref=pd_nfy_gw_n4/103-3111065-2579065><font face=verdana,arial,helvetica size=-1>Cookware</font></a>
|
||||||
|
</td></tr>
|
||||||
|
<tr valign=top><td>
|
||||||
|
<a href=/exec/obidos/tg/new-for-you/new-releases/-/video/141/ref=pd_nfy_gw_n5/103-3111065-2579065><img src="http://g-images.amazon.com/images/G/01/icons/small-blue-vhs-icon.gif" width=18 height=18 border=0 alt=Icon ></a></td><td>
|
||||||
|
<a href=/exec/obidos/tg/new-for-you/new-releases/-/video/141/ref=pd_nfy_gw_n5/103-3111065-2579065><font face=verdana,arial,helvetica size=-1>Action & Adventure</font></a>
|
||||||
|
</td></tr>
|
||||||
|
</td></tr>
|
||||||
|
<tr><td colspan=2 align=left> <img src="http://g-images.amazon.com/images/G/01/icons/orange-arrow.gif" width=10 height=9 border=0> <a href=/exec/obidos/tg/new-for-you/new-releases/-/main/ref=pd_nfy_gw_n/103-3111065-2579065><font face=verdana,arial,helvetica size=-1><b>More New Releases</b></font></a><p>
|
||||||
|
</td></tr></table>
|
||||||
|
<table border=0 cellpadding=3 cellspacing=0 width=100% bgcolor=#ffffff>
|
||||||
|
<tr bgcolor=#eeeecc><td class=small>
|
||||||
|
<a href=/exec/obidos/tg/new-for-you/movers-and-shakers/-/books/ref=pd_gw_msgr/103-3111065-2579065><b>Movers & Shakers</b></a>
|
||||||
|
</td></tr></table>
|
||||||
|
<table border=0 cellpadding=2 cellspacing=0 width=100% bgcolor=#ffffff vspace=0>
|
||||||
|
<tr><td valign=top align=center>
|
||||||
|
<img src="http://g-images.amazon.com/images/G/01/icons/uparrow_green2.gif" width=13 height=11 alt="Up">
|
||||||
|
</td>
|
||||||
|
<td valign=top>
|
||||||
|
<font color=#339900 face=verdana,arial,helvetica size=-1><b>974%</b></font> </td></tr>
|
||||||
|
<tr><td valign=top align=left>
|
||||||
|
<img src="http://g-images.amazon.com/images/G/01/icons/small-blue-dvd-icon.gif" width=18 height=18 border=0 alt=Icon >
|
||||||
|
</td>
|
||||||
|
<td valign=top>
|
||||||
|
<font face=verdana,arial,helvetica size=-1><a href=/exec/obidos/tg/new-for-you/movers-and-shakers/-/dvd/ref=pd_gw_msd2/103-3111065-2579065>Dorothy L. Sayers Mysteries (Strong Poison / Have His Carcass / Gaudy Night)</a>
|
||||||
|
<font face=verdana,arial,helvetica size=-1>
|
||||||
|
<b>DVD</b>
|
||||||
|
<br>~ Dorothy L. Sayers
|
||||||
|
</font>
|
||||||
|
</font>
|
||||||
|
</td></tr>
|
||||||
|
<tr><td valign=top align=center>
|
||||||
|
<img src="http://g-images.amazon.com/images/G/01/icons/uparrow_green2.gif" width=13 height=11 alt="Up">
|
||||||
|
</td>
|
||||||
|
<td valign=top>
|
||||||
|
<font color=#339900 face=verdana,arial,helvetica size=-1><b>2,415%</b></font> </td></tr>
|
||||||
|
<tr><td valign=top align=left>
|
||||||
|
<img src="http://g-images.amazon.com/images/G/01/icons/small-blue-books-icon.gif" width=18 height=18 border=0 alt=Icon >
|
||||||
|
</td>
|
||||||
|
<td valign=top>
|
||||||
|
<font face=verdana,arial,helvetica size=-1><a href=/exec/obidos/tg/new-for-you/movers-and-shakers/-/books/ref=pd_gw_msb2/103-3111065-2579065>Artemis Fowl</a>
|
||||||
|
<br><font face=verdana,arial,helvetica size=-1>by Eoin Colfer</font>
|
||||||
|
</font>
|
||||||
|
</td></tr>
|
||||||
|
<tr><td colspan=2>
|
||||||
|
<img src="http://g-images.amazon.com/images/G/01/icons/orange-arrow.gif" width=10 height=9 border=0> <font face=verdana,arial,helvetica size=-1><b><a href=/exec/obidos/tg/new-for-you/movers-and-shakers/-/books/ref=pd_gw_msgr/103-3111065-2579065>More Movers & Shakers</a></b>
|
||||||
|
<br>
|
||||||
|
</td></tr></table>
|
||||||
|
</td></tr></table>
|
||||||
|
</td></tr></table>
|
||||||
|
</td></tr></table>
|
||||||
|
<br clear="all">
|
||||||
|
<center>
|
||||||
|
<form method="post" action="/exec/obidos/search-handle-form/103-3111065-2579065">
|
||||||
|
<table border=0 width=100% cellpadding=1 cellspacing=0 bgcolor=#999999>
|
||||||
|
<tr><td>
|
||||||
|
<table border=0 width=100% bgcolor=#ffffff cellspacing=0 cellpadding=5 class="small">
|
||||||
|
<tr valign=top><td width=33% class="small">
|
||||||
|
<b>Where's My Stuff?</b><br>
|
||||||
|
• Track your <a href="/exec/obidos/flex-sign-in/ref=hy_f_1/103-3111065-2579065?opt=ab&page=help/ya-sign-in-secure.html&response=order-history-filtered&method=POST&ss-order-filter=wheres-my-stuff&return-url=order-history-filtered">recent orders</a>.<br>
|
||||||
|
• View or change your orders in <a href="/exec/obidos/account-access-login/ref=hy_f_2/103-3111065-2579065">Your Account</a>.
|
||||||
|
<script language="JavaScript1.1" type="text/javascript">
|
||||||
|
<!--
|
||||||
|
var agt=navigator.userAgent.toLowerCase();
|
||||||
|
var is_major = parseInt(navigator.appVersion);
|
||||||
|
var is_nav = ((agt.indexOf('mozilla')!=-1) && (agt.indexOf('spoofer')==-1)
|
||||||
|
&& (agt.indexOf('compatible') == -1) && (agt.indexOf('opera')==-1)
|
||||||
|
&& (agt.indexOf('webtv')==-1) && (agt.indexOf('hotjava')==-1));
|
||||||
|
var is_gecko = (agt.indexOf('gecko') != -1);
|
||||||
|
var is_ie = ((agt.indexOf("msie") != -1) && (agt.indexOf("opera") == -1));
|
||||||
|
var is_aol = (agt.indexOf("aol") != -1);
|
||||||
|
var is_opera = (agt.indexOf("opera") != -1);
|
||||||
|
var is_win = ( (agt.indexOf("win")!=-1) || (agt.indexOf("16bit")!=-1) );
|
||||||
|
//-->
|
||||||
|
</script>
|
||||||
|
<script language="JavaScript1.1" type="text/javascript">
|
||||||
|
<!--
|
||||||
|
var OpenedWin;
|
||||||
|
function openWin (URL, width, height) {
|
||||||
|
OpenedWin = window.open(URL, "demo_window", "width="+width+",height="+height+",status=no,menubar=no,location=no,toolbar=no,directories=no,scrollbars=no");
|
||||||
|
if (! is_aol) {
|
||||||
|
var NewX = (screen.availWidth/2)-(width/2);
|
||||||
|
var NewY = (screen.availHeight/2)-(height/2);
|
||||||
|
OpenedWin.moveTo(NewX, NewY);
|
||||||
|
NewX = null;
|
||||||
|
NewY = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function launch (URL, width, height) {
|
||||||
|
if (!URL || !width || !height) {
|
||||||
|
alert("Error");
|
||||||
|
} else if (width>screen.availWidth || height>screen.availHeight) {
|
||||||
|
var message;
|
||||||
|
message = "Your screen resolution is too low to display the demo.\nClick 'OK' if you wish to continue anyway.\n";
|
||||||
|
message += '\n Your screen resolution: '+screen.width+' x '+screen.height;
|
||||||
|
message += ' | Viewable: '+screen.availWidth+' x '+screen.availHeight;
|
||||||
|
message += '\n Required: '+width+' x '+height;
|
||||||
|
if (confirm(message)) {
|
||||||
|
message = "If you can not find the close buttons, use your keyboard:\n";
|
||||||
|
message += 'Windows: ALT+F4\n';
|
||||||
|
message += 'Macintosh: CONTROL+W';
|
||||||
|
alert(message);
|
||||||
|
openWin(URL, width, height);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
openWin(URL, width, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function displayLink(text){
|
||||||
|
if ( is_major >= 4 && is_win && ( is_nav || is_ie || is_opera || is_gecko ) ) {
|
||||||
|
document.write(text);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
//-->
|
||||||
|
</script>
|
||||||
|
<script language="JavaScript1.1" type="text/javascript">
|
||||||
|
<!--
|
||||||
|
displayLink('<br>• See our <b><a href=javascript:launch(\'/exec/obidos/subst/help/demo-wms/display-demo.html/ref=hy_f_demo/103-3111065-2579065\',788,444)>animated demo</a></b>!');
|
||||||
|
//-->
|
||||||
|
</script>
|
||||||
|
</td>
|
||||||
|
<td width=33% class="small">
|
||||||
|
<b>Shipping & Returns</b><br>
|
||||||
|
• See our <a href="/exec/obidos/tg/browse/-/468520/ref=hy_f_3/103-3111065-2579065">shipping rates & policies</a>.<br>
|
||||||
|
• <a href="/exec/obidos/subst/help/self-service-returns.html/ref=hy_f_4/103-3111065-2579065">Return</a> an item (here's our <a href="/exec/obidos/tg/browse/-/468532/103-3111065-2579065">Returns Policy</a>).
|
||||||
|
</td>
|
||||||
|
<td width=33% class="small">
|
||||||
|
<b>Need Help?</b><br>
|
||||||
|
• Forgot your password? <a href="/exec/obidos/self-service-forgot-password-get-email/ref=hy_f_6/103-3111065-2579065">Click here</a>.
|
||||||
|
<br>
|
||||||
|
• <a href="/exec/obidos/subst/gifts/gift-certificates/gc-redeeming.html/ref=hy_f_7/103-3111065-2579065">Redeem</a> or <a href="/exec/obidos/subst/gifts/gift-services/gift-certificates.html/ref=hy_f_8/103-3111065-2579065">buy</a> a gift certificate.<br>
|
||||||
|
• <a href="/exec/obidos/tg/browse/-/508510/ref=hy_f_9/103-3111065-2579065">Visit our Help department</a>. <br>
|
||||||
|
</td></tr>
|
||||||
|
</table>
|
||||||
|
</td></tr>
|
||||||
|
<tr><td>
|
||||||
|
<table border=0 width=100% bgcolor=#FFCC66 cellspacing=0 cellpadding=5>
|
||||||
|
<tr><td align=center class="small">
|
||||||
|
<b>Search </b>
|
||||||
|
<select name=index>
|
||||||
|
<option value=blended selected>All Products
|
||||||
|
<option value=books>Books
|
||||||
|
<option value=music>Popular Music
|
||||||
|
<option value=music-dd>Music Downloads
|
||||||
|
<option value=classical>Classical Music
|
||||||
|
<option value="dvd">DVD
|
||||||
|
<option value="vhs">VHS
|
||||||
|
<option value=theatrical>Movie Showtimes
|
||||||
|
<option value=toys>Toys
|
||||||
|
<option value=baby>Baby
|
||||||
|
<option value=pc-hardware>Computers
|
||||||
|
<option value=videogames>Video Games
|
||||||
|
<option value=electronics>Electronics
|
||||||
|
<option value=photo>Camera & Photo
|
||||||
|
<option value=software>Software
|
||||||
|
<option value=tools>Tools & Hardware
|
||||||
|
<option value=magazines>Magazines
|
||||||
|
<option value=garden>Outdoor Living
|
||||||
|
<option value=kitchen>Kitchen
|
||||||
|
<option value=travel>Travel
|
||||||
|
<option value=wireless-phones>Cell Phones & Service
|
||||||
|
<option value=outlet>Outlet
|
||||||
|
<option value=auction-redirect>Auctions
|
||||||
|
<option value=fixed-price-redirect>zShops
|
||||||
|
</select>
|
||||||
|
<b> for </b>
|
||||||
|
<input type="text" name="field-keywords" size="15">
|
||||||
|
<input type=image name="Go" value="Go!" border=0 alt="Go!" src=http://g-images.amazon.com/images/G/01/v9/search-browse/go-button-gateway.gif width=21 height=21 border=0 align=absmiddle > </td></tr></table>
|
||||||
|
</td></tr>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
<p align=center>
|
||||||
|
<b class=h1>Stuart D. Gathman, make </b><font color=#990000><b class=sans>$</b><b class=sans>310.61</b></font><br />
|
||||||
|
<b class=sans>Sell <a href="/exec/obidos/flex-sign-in/ref=sdp_bbump_gw/103-3111065-2579065?opt=an&page=misc/login/flex-sign-in-secure.html&response=tg/stores/static/-/used/sell-your-collection/1/">your past purchases</a> at Amazon.com today!</b>
|
||||||
|
</p>
|
||||||
|
<table width="100%">
|
||||||
|
<tr>
|
||||||
|
<td width="50%" valign="top" align="left">
|
||||||
|
<span class="small"><a href=/exec/obidos/change-style/subst/home/redirect.html/103-3111065-2579065>Text Only</a></span>
|
||||||
|
</td>
|
||||||
|
<td width="50%" valign="top" align="right" class="small">
|
||||||
|
<a href="#top">Top of Page</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<center>
|
||||||
|
<p>
|
||||||
|
<a href=/exec/obidos/subst/home/all-stores.html/ref=gw_bt_st/103-3111065-2579065>Directory of All Stores</a><p>
|
||||||
|
Our International Sites:
|
||||||
|
<a href="/exec/obidos/redirect-to-external-url/ref=gw_bt_uk/103-3111065-2579065?path=http%3A//www.amazon.co.uk/exec/obidos/redirect-home%3Ftag%3Dintl-usgt-ukhome-21%26site%3Damazon">United Kingdom</a>
|
||||||
|
|
|
||||||
|
<a href="/exec/obidos/redirect-to-external-url/ref=gw_bt_de/103-3111065-2579065?path=http%3A//www.amazon.de/exec/obidos/redirect-home%3Ftag%3Dintl-usgt-dehome-21%26site%3Dhome">Germany</a>
|
||||||
|
|
|
||||||
|
<a href="/exec/obidos/redirect-to-external-url/ref=gw_bt_jp/103-3111065-2579065?path=http%3A//www.amazon.co.jp/exec/obidos/redirect-home%3Ftag%3Dintl-usgatew-jphome-22%26site%3Damazon">Japan</a>
|
||||||
|
 |
|
||||||
|
<a href="/exec/obidos/redirect-to-external-url/ref=gw_bt_fr/103-3111065-2579065?path=http%3A//www.amazon.fr/exec/obidos/redirect-home%3Fsite%3Damazon%26tag%3Dusfr-gatew-footer-21">France</a>
|
||||||
|
<p>
|
||||||
|
<a href=/exec/obidos/tg/browse/-/508510/ref=gw_bt_he/103-3111065-2579065>Help</a> |
|
||||||
|
<a href=/exec/obidos/shopping-basket/ref=gw_bt_sc/103-3111065-2579065>Shopping Cart</a> |
|
||||||
|
<a href=/exec/obidos/account-access-login/ref=gw_bt_ya/103-3111065-2579065>Your Account</a> |
|
||||||
|
<a href="http://s1.amazon.com/exec/varzea/ts/announcement-list-zshops/slp/ref=gw_bt_si/103-3111065-2579065">Sell Items</a> |
|
||||||
|
<a href="/exec/obidos/flex-sign-in/ref=gw_bt_oc/103-3111065-2579065?opt=a&page=ordering/one-click-address-sign-in-secure.html&response=one-click-main&method=GET&return-url=one-click-main">1-Click Settings</a>
|
||||||
|
<p>
|
||||||
|
<a href=/exec/obidos/subst/misc/company-info.html/ref=gw_bt_aa/103-3111065-2579065>About Amazon.com</a> |
|
||||||
|
<a href=/exec/obidos/tg/stores/job-listings/-/generic/home/103-3111065-2579065>Join Our Staff</a> |
|
||||||
|
<a href="/exec/obidos/subst/associates/join/associates.html/ref=gw_bt_as/103-3111065-2579065">Join Associates</a> |
|
||||||
|
<a href=/exec/obidos/subst/partners/direct/direct-application.html/ref=gw_bt_ad/103-3111065-2579065>Join Advantage</a> |
|
||||||
|
<a href="http://s1.amazon.com/exec/varzea/subst/fx/home.html/ref=gw_bt_hs/103-3111065-2579065">Join Honor System</a>
|
||||||
|
</center>
|
||||||
|
<center>
|
||||||
|
<p>
|
||||||
|
<div class="tiny" align=center>
|
||||||
|
<A HREF="/exec/obidos/subst/misc/policy/conditions-of-use.html/103-3111065-2579065">Conditions of Use</A> | <A HREF="/exec/obidos/tg/browse/-/468496/103-3111065-2579065">Privacy Notice</A> © 1996-2002, Amazon.com, Inc. or its affiliates
|
||||||
|
</div>
|
||||||
|
</center>
|
||||||
|
<!-- whfhYn47qD1fv3PW2R8XWAkFcMwteHFKxorD -->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
--------------59A46341C90BA737DD47867B--
|
||||||
|
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
Received: from www.bmsi.com (bmsweb.bmsi.com [219.109.11.130])
|
||||||
|
by bmsaix.bmsi.com (8.12.1/8.12.1) with ESMTP id g218JVhw028058
|
||||||
|
for <stuart@bmsi.com>; Fri, 1 Mar 2002 03:19:31 -0500
|
||||||
|
Received: from apol ([210.201.89.183])
|
||||||
|
by www.bmsi.com (8.12.1/8.12.1) with SMTP id g218JQkY030600
|
||||||
|
for <stuart@bmsi.com>; Fri, 1 Mar 2002 03:19:27 -0500
|
||||||
|
Date: Fri, 1 Mar 2002 03:19:26 -0500
|
||||||
|
Received: from tcts1
|
||||||
|
by yahoo.com with SMTP id KAqmIGSKwGQHv6LYDEOUUS;
|
||||||
|
Fri, 01 Mar 2002 16:18:13 +0800
|
||||||
|
Message-ID: <VPvce@seed.net.tw>
|
||||||
|
From: 大中華國際留學教育中心@www.bmsi.com
|
||||||
|
To:
|
||||||
|
Subject: 8PxZzvJbH8VtozQ3rC01SOwm =?big5?Q?=A6p=AAG=A7A=B7Q=AFd=BE=C7=AA=BA=B8=DC=A1K?= BwnqwcNylfNuCIM3RG0mCx
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: multipart/related;
|
||||||
|
type="multipart/alternative";
|
||||||
|
boundary="----=_NextPart_kpWBTLcCozjeV8sH5gRbJoOo3aJ"
|
||||||
|
X-Mailer: foOkz11rguOMzavzZaDTw
|
||||||
|
X-Priority: 3
|
||||||
|
X-MSMail-Priority: Normal
|
||||||
|
|
||||||
|
This is a multi-part message in MIME format.
|
||||||
|
|
||||||
|
------=_NextPart_kpWBTLcCozjeV8sH5gRbJoOo3aJ
|
||||||
|
Content-Type: multipart/alternative;
|
||||||
|
boundary="----=_NextPart_kpWBTLcCozjeV8sH5gRbJoOo3aJAA"
|
||||||
|
|
||||||
|
|
||||||
|
------=_NextPart_kpWBTLcCozjeV8sH5gRbJoOo3aJAA
|
||||||
|
Content-Type: text/html;
|
||||||
|
charset="big5"
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
|
||||||
|
PGh0bWwgeG1sbnM6dj0idXJuOnNjaGVtYXMtbWljcm9zb2Z0LWNvbTp2bWwiDQp4bWxuczpvPSJ1
|
||||||
|
DQoNCjwvYm9keT4NCg0KPC9odG1sPg==
|
||||||
|
|
||||||
|
|
||||||
|
------=_NextPart_kpWBTLcCozjeV8sH5gRbJoOo3aJAA--
|
||||||
|
------=_NextPart_kpWBTLcCozjeV8sH5gRbJoOo3aJ--
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
+86
@@ -0,0 +1,86 @@
|
|||||||
|
Received: from localhost (localhost)
|
||||||
|
by bmsaix.bmsi.com (8.12.9/8.12.6) id h62JqW5p030912;
|
||||||
|
Wed, 2 Jul 2003 15:52:32 -0400
|
||||||
|
Date: Wed, 2 Jul 2003 15:52:32 -0400
|
||||||
|
From: Mail Delivery Subsystem <MAILER-DAEMON@bmsaix.bmsi.com>
|
||||||
|
Message-Id: <200307021952.h62JqW5p030912@bmsaix.bmsi.com>
|
||||||
|
To: <annagh000@bellsouth.net>
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: multipart/report; report-type=delivery-status;
|
||||||
|
boundary="h62JqW5p030912.1057175552/bmsaix.bmsi.com"
|
||||||
|
Subject: Returned mail: see transcript for details
|
||||||
|
Auto-Submitted: auto-generated (failure)
|
||||||
|
|
||||||
|
This is a MIME-encapsulated message
|
||||||
|
|
||||||
|
--h62JqW5p030912.1057175552/bmsaix.bmsi.com
|
||||||
|
|
||||||
|
The original message was received at Fri, 27 Jun 2003 15:28:03 -0400
|
||||||
|
from IDENT:ndcHoBWTR9Bf/rEFYJRejRoPTaRDgSCl@bmsweb.bmsi.com [192.168.9.81]
|
||||||
|
|
||||||
|
----- The following addresses had permanent fatal errors -----
|
||||||
|
makurat@erols.com
|
||||||
|
(reason: 452 4.3.0 Filter failure)
|
||||||
|
(expanded from: <makurat@bmsi.com>)
|
||||||
|
|
||||||
|
----- Transcript of session follows -----
|
||||||
|
... while talking to [192.168.9.81]:
|
||||||
|
>>> DATA
|
||||||
|
<<< 452 4.3.0 Filter failure
|
||||||
|
makurat@erols.com... Deferred: 452 4.3.0 Filter failure
|
||||||
|
Message could not be delivered for 5 days
|
||||||
|
Message will be deleted from queue
|
||||||
|
|
||||||
|
--h62JqW5p030912.1057175552/bmsaix.bmsi.com
|
||||||
|
Content-Type: message/delivery-status
|
||||||
|
|
||||||
|
Reporting-MTA: dns; bmsaix.bmsi.com
|
||||||
|
Arrival-Date: Fri, 27 Jun 2003 15:28:03 -0400
|
||||||
|
|
||||||
|
Final-Recipient: RFC822; makurat@bmsi.com
|
||||||
|
X-Actual-Recipient: RFC822; makurat@erols.com
|
||||||
|
Action: failed
|
||||||
|
Status: 4.4.7
|
||||||
|
Remote-MTA: DNS; [192.168.9.81]
|
||||||
|
Diagnostic-Code: SMTP; 452 4.3.0 Filter failure
|
||||||
|
Last-Attempt-Date: Wed, 2 Jul 2003 15:52:32 -0400
|
||||||
|
|
||||||
|
--h62JqW5p030912.1057175552/bmsaix.bmsi.com
|
||||||
|
Content-Type: message/rfc822
|
||||||
|
|
||||||
|
Return-Path: <annagh000@bellsouth.net>
|
||||||
|
Received: from spidey.bmsi.com (IDENT:ndcHoBWTR9Bf/rEFYJRejRoPTaRDgSCl@bmsweb.bmsi.com [192.168.9.81])
|
||||||
|
by bmsaix.bmsi.com (8.12.9/8.12.6) with ESMTP id h5RJS3Vi042394
|
||||||
|
for <makurat@bmsi.com>; Fri, 27 Jun 2003 15:28:03 -0400
|
||||||
|
Received: from sunlong.com ([202.105.130.54])
|
||||||
|
by spidey.bmsi.com (8.11.6/8.11.6) with SMTP id h5RJS2o03547
|
||||||
|
for <makurat@bmsi.com>; Fri, 27 Jun 2003 15:28:02 -0400
|
||||||
|
Message-Id: <200306271928.h5RJS2o03547@spidey.bmsi.com>
|
||||||
|
Received: from mx06.mail.bellsouth.net([218.104.6.10]) by sunlong.com(JetMail 2.5.3.0)
|
||||||
|
with SMTP id jma73efca64b; Fri, 27 Jun 2003 19:23:44 -0000
|
||||||
|
To: <Undisclosed.Recipients@spidey.bmsi.com>
|
||||||
|
From: "Stacy McClain" <annagh000@bellsouth.net>
|
||||||
|
Subject: Defy Gravity in 15 minutes
|
||||||
|
Date: Sat, 28 Jun 2003 03:34:15 -1600
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: multipart/mixed;
|
||||||
|
boundary="----=_NextPart_000_646C_00001D33.00000BE1"
|
||||||
|
Reply-To: annagh000@bellsouth.net
|
||||||
|
X-AntiAbuse: : This header was added to track abuse, please include it with any abuse report
|
||||||
|
X-AntiAbuse: Primary Hostname - 210.222.2.13
|
||||||
|
X-Originating-Host: : 210.188.201.159
|
||||||
|
|
||||||
|
------=_NextPart_000_646C_00001D33.00000BE1
|
||||||
|
Content-Type: text/html;
|
||||||
|
charset="iso-8859-1"
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
|
||||||
|
PGh0bWw+DQoNCjxoZWFkPg0KPHRpdGxlPjwvdGl0bGU+DQo8L2hlYWQ+DQoNCjxib2R5Pg0KDQo8cD4NCjxhIGhyZWY9Imh0dHA6Ly9zcmQueWFob28uY29tL2Ryc3QvNzQxMjQzMjM1LypodHRwOi93d3cuZnJ5YmVlLmNvbS8iPg0KPGltZyBzcmM9Imh0dHA6Ly8yMTAuMTUuNTEuOTUvcGljX3dlbGwvZ3YyLmdpZiIgYm9yZGVyPSIwIiB3aWR0aD0iNDA1IiBoZWlnaHQ9IjI3MCI+PC9hPjwvcD4NCg0KPHA+DQo8YSBocmVmPSJodHRwOi8vc3JkLnlhaG9vLmNvbS9kcnN0Lzc0MTQxNjg4Mjc3NzcvKmh0dHA6L3d3dy5mcnliZWUuY29tL3BhZ2UvYS5odG1sIj4NCjxpbWcgc3JjPSJodHRwOi8vY2xpY2suanVzdGZvcnlvdS1tYWlsLmNvbS9pbWFnZXMvRjEuZ2lmIiB3aWR0aD0iNDEwIiBoZWlnaHQ9IjE0IiBib3JkZXI9IjAiPjwvYT48L3A+DQoNCjxwIGFsaWduPSJsZWZ0Ij4NCiZuYnNwOzwvcD4NCg0KPHAgc3R5bGU9Im1hcmdpbi10b3A6IDA7IG1hcmdpbi1ib3R0b206IDAiPg0KJm5ic3A7PC9wPg0KDQo8cCBzdHlsZT0ibWFyZ2luLXRvcDogMDsgbWFyZ2luLWJvdHRvbTogMCI+DQombmJzcDs8L3A+DQoNCjxwIHN0eWxlPSJtYXJnaW4tdG9wOiAwOyBtYXJnaW4tYm90dG9tOiAwIj4NCiZuYnNwOzwvcD4NCg0KPHAgc3R5bGU9Im1hcmdpbi10b3A6IDA7IG1hcmdpbi1ib3R0b206IDAiPjxmb250IHNpemU9IjEiPnFhd3NteXp0ciBxYXdzYW9lZHRhZ2ZwdiANCnFhd3N5ZmRhb3FqIHFhd3NjaSBxYXdzY!
|
||||||
|
212Z3ZrIHFhd3NvaW55d3pkbyBxYXdzbXVxYXdza29jIA0KcWF3c2hobmVkZCBxYXdzZWllbiBxYXdzemlnZ3hucGN2cyBxYXdzd3lkZSBxYXdzeWFwIHFhd3NxamVkeWhxYXdzZmt1bSANCnFhd3NmbSBxYXdzdW11Ym1mYmR3IHFhd3Nkc29ka2xvIHFhd3Nhc2VtayBxYXdzZXdzIHFhd3NxdWRneGVvcWF3c3J6IA0KcWF3c290dSBxYXdzcHplbnJoZW1xYSBxYXdzdXplcmpqcWZxIHFhd3NydWFucyBxYXdzbnBjcGFoZ2pwIHFhd3NxYXdoZHJxYXdzYmFscXNxaiANCnFhd3N5bmggcWF3c2VrIHFhd3N0YmNndGd0IHFhd3N0ZnhzeHd4ICBxYXdzandlcHFhd3NsYmN6ZWRuIHFhd3NzcW1nb3YgDQpxYXdzZ3phdiBxYXdzZ2N2aCBxYXdzd21sYWt1bW5sbiBxYXdzZHpqcW9yeCBxYXdzdGhvbHRmaWxmeHFhd3NpcGJneSANCnFhd3NpbHp5Znd2dnMgIHFhd3NpdmJwdmNiIHFhd3NrZXRpYmtocGRhIHFhd3N6ZmJqYm1yayBxYXdzbWZvZ29ucWF3c2FvIA0KcWF3c21vcXggcWF3c3FkeWVuaCBxYXdzYnMgcWF3c2l5aXBkYWx4IHFhd3N6aXlpbyBxYXdzaWZ6dXFyamltcSANCnFhd3NuayBxYXdza3dhciBxYXdzanNleHNmc2IgcWF3c3RxaWlhY2cgcWF3c2p0YnFobnFlIHFhd3Niam1pcGpxYXdzaHl4anNwbXhuIA0KIHFhd3NqcmJlbnIgcWF3c3p6b3p0ZndydyBxYXdzZ25uaHdjIHFhd3NrdXkgcWF3c3ZwcWF3c25qbmd5eHl1eCBxYXdzd3lvc2EgDQpxYXdzb2lnIHFhd3Nub25rcm5pbWcgcWF3c2NtcGdxemtwcm!
|
||||||
|
U8L2ZvbnQ+PC9wPg0KDQo8L2JvZHk+DQoNCjwvaHRtbD48L3RpdGxlPg0K
|
||||||
|
|
||||||
|
------=_NextPart_000_646C_00001D33.00000BE1--
|
||||||
|
|
||||||
|
|
||||||
|
--h62JqW5p030912.1057175552/bmsaix.bmsi.com--
|
||||||
|
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
Received: from zuul.kastle.com (root@localhost)
|
||||||
|
by zuul.kastle.com with ESMTP id h7JGdwn27534
|
||||||
|
for <amy@koger.bmsi.com>; Tue, 19 Aug 2003 12:39:58 -0400 (EDT)
|
||||||
|
Received: from kastle.com (netgate.kastle.com [172.17.2.8])
|
||||||
|
by zuul.kastle.com with ESMTP id h7JGdwV27530
|
||||||
|
for <amy@koger.bmsi.com>; Tue, 19 Aug 2003 12:39:58 -0400 (EDT)
|
||||||
|
Received: by kastle.com
|
||||||
|
with XWall v3.27 ;
|
||||||
|
Tue, 19 Aug 2003 12:45:41 -0400
|
||||||
|
From: System Administrator <postmaster@kastle.com>
|
||||||
|
To: "amy@koger.bmsi.com" <amy@koger.bmsi.com>
|
||||||
|
Subject: Non delivery report: 5.9.5 (Blocked attachment)
|
||||||
|
Date: Tue, 19 Aug 2003 12:45:41 -0400
|
||||||
|
X-Mailer: XWall v3.27
|
||||||
|
Mime-Version: 1.0
|
||||||
|
Content-Type: multipart/report; report-type=delivery-status;
|
||||||
|
boundary="_NextPart_1_qmZrHLajoetbkwlTZTViemHPfyb"
|
||||||
|
|
||||||
|
This is a multi part message in MIME format.
|
||||||
|
|
||||||
|
--_NextPart_1_qmZrHLajoetbkwlTZTViemHPfyb
|
||||||
|
Content-Type: text/plain; charset="us-ascii"
|
||||||
|
Content-Transfer-Encoding: 7bit
|
||||||
|
|
||||||
|
Your message
|
||||||
|
|
||||||
|
From: amy@koger.bmsi.com
|
||||||
|
|
||||||
|
To: lwilliams@kastle.com
|
||||||
|
|
||||||
|
Subj: Thank you!
|
||||||
|
Sent: 2003-08-19 08:51
|
||||||
|
|
||||||
|
has encountered a delivery problem.
|
||||||
|
|
||||||
|
|
||||||
|
Reason: Blocked attachment
|
||||||
|
One of the attachment(s) in the message is blocked.
|
||||||
|
For security reasons the message was not or not completely delivered to
|
||||||
|
the recipient.
|
||||||
|
|
||||||
|
Additional info:
|
||||||
|
The blocked attachment is: thank_you.pif
|
||||||
|
|
||||||
|
--_NextPart_1_qmZrHLajoetbkwlTZTViemHPfyb
|
||||||
|
Content-Type: message/xdelivery-status ; name="delivery-status.txt"
|
||||||
|
|
||||||
|
Reporting-MTA: dns; kastle.com
|
||||||
|
Received-From-MTA: dns; zuul.kastle.com
|
||||||
|
Arrival-Date: Tue, 19 Aug 2003 12:45:41 -0400
|
||||||
|
|
||||||
|
Final-Recipient: rfc822; lwilliams@kastle.com
|
||||||
|
Action: failed
|
||||||
|
Status: 5.9.5
|
||||||
|
|
||||||
|
--_NextPart_1_qmZrHLajoetbkwlTZTViemHPfyb
|
||||||
|
Content-Type: message/rfc822
|
||||||
|
|
||||||
|
Received: from zuul.kastle.com [172.17.2.100]
|
||||||
|
by kastle.com
|
||||||
|
with XWall v3.27 ;
|
||||||
|
Tue, 19 Aug 2003 12:45:41 -0400
|
||||||
|
Received: from zuul.kastle.com (root@localhost)
|
||||||
|
by zuul.kastle.com with ESMTP id h7JGduo27526
|
||||||
|
for <lwilliams@kastle.com>; Tue, 19 Aug 2003 12:39:56 -0400 (EDT)
|
||||||
|
Received: from 1333AVE2 (wan-vc8f35e.norva3.biz.mindspring.com [216.135.140.174])
|
||||||
|
by zuul.kastle.com with ESMTP id h7JGdqS27522
|
||||||
|
for <lwilliams@kastle.com>; Tue, 19 Aug 2003 12:39:53 -0400 (EDT)
|
||||||
|
Message-Id: <200308191639.h7JGdqS27522@zuul.kastle.com>
|
||||||
|
From: <amy@koger.bmsi.com>
|
||||||
|
To: <lwilliams@kastle.com>
|
||||||
|
Subject: Thank you!
|
||||||
|
Date: Tue, 19 Aug 2003 12:51:38 --0400
|
||||||
|
X-MailScanner: Found to be clean
|
||||||
|
Importance: Normal
|
||||||
|
X-Mailer: Microsoft Outlook Express 6.00.2600.0000
|
||||||
|
X-MSMail-Priority: Normal
|
||||||
|
X-Priority: 3 (Normal)
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: multipart/mixed;
|
||||||
|
boundary="_NextPart_000_062C48F7"
|
||||||
|
|
||||||
|
--_NextPart_1_qmZrHLajoetbkwlTZTViemHPfyb--
|
||||||
|
|
||||||
|
|
||||||
+84
@@ -0,0 +1,84 @@
|
|||||||
|
From dspam Mon Sep 29 16:36:23 2003
|
||||||
|
Received: from orcon.net.nz (port-219-88-129-82.orcon.net.nz [219.88.129.82])
|
||||||
|
by spidey.planet.com (8.11.6/8.11.6) with SMTP id h8Q85c414321
|
||||||
|
for <postmaster@bugle.com>; Fri, 26 Sep 2003 04:05:39 -0400
|
||||||
|
Date: Fri, 26 Sep 2003 20:05:56 +1200
|
||||||
|
From: Mail Delivery Subsystem <MAILER-DAEMON@orcon.net.nz>
|
||||||
|
Message-Id: <200309262005.IEI23104@mx1.orcon.net.nz>
|
||||||
|
To: <postmaster@bugle.com>
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: multipart/report; report-type=delivery-status;
|
||||||
|
boundary="IEI23104.1064534400/mx1.orcon.net.nz"
|
||||||
|
Subject: Returned mail: User unknown
|
||||||
|
Auto-Submitted: auto-generated (failure)
|
||||||
|
X-DSpam-HeaderScore: 0.007433
|
||||||
|
|
||||||
|
This is a MIME-encapsulated message
|
||||||
|
|
||||||
|
--IEI23104.1064534400/mx1.orcon.net.nz
|
||||||
|
|
||||||
|
The original message was received at Fri, 26 Sep 2003 20:05:56 +1200
|
||||||
|
from
|
||||||
|
|
||||||
|
----- The following addresses had permanent fatal errors -----
|
||||||
|
<mike-liz@orcon.net.nz>
|
||||||
|
(expanded from: <mike-liz@orcon.net.nz>)
|
||||||
|
|
||||||
|
----- Transcript of session follows -----
|
||||||
|
mail.local: unknown name: mike-liz
|
||||||
|
550 <mike-liz@orcon.net.nz>... User unknown
|
||||||
|
|
||||||
|
--IEI23104.1064534400/mx1.orcon.net.nz
|
||||||
|
Content-Type: message/delivery-status
|
||||||
|
|
||||||
|
Reporting-MTA: dns; mx1.orcon.net.nz
|
||||||
|
Received-From-MTA: DNS;
|
||||||
|
Arrival-Date: Fri, 26 Sep 2003 20:05:56 +1200
|
||||||
|
|
||||||
|
Final-Recipient: RFC822; <mike-liz@orcon.net.nz>
|
||||||
|
X-Actual-Recipient: RFC822; mike-liz@orcon.net.nz
|
||||||
|
Action: failed
|
||||||
|
Status: 5.1.1
|
||||||
|
Last-Attempt-Date: Fri, 26 Sep 2003 20:05:56 +1200
|
||||||
|
|
||||||
|
--IEI23104.1064534400/mx1.orcon.net.nz
|
||||||
|
Content-Type: message/rfc822
|
||||||
|
|
||||||
|
Return-Path: <MAILER-DAEMON>
|
||||||
|
Received: from global_1.bugle.com ([12.4.120.82])
|
||||||
|
by dbmail-mx3.orcon.co.nz (8.12.6/8.12.6/Debian-7) with ESMTP id h8O6CRJ8015038
|
||||||
|
for <mike-liz@orcon.net.nz>; Wed, 24 Sep 2003 18:12:28 +1200
|
||||||
|
From: postmaster@bugle.com
|
||||||
|
To: mike-liz@orcon.net.nz
|
||||||
|
Date: Wed, 24 Sep 2003 02:13:53 -0400
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: multipart/report; report-type=delivery-status;
|
||||||
|
boundary="9B095B5ADSN=_01C3664F7D2C23400000BC00global_1.bugle."
|
||||||
|
X-DSNContext: 335a7efd - 4457 - 00000001 - 80040546
|
||||||
|
Message-ID: <YkMSnhRpy00001453@global_1.bugle.com>
|
||||||
|
Subject: Delivery Status Notification (Failure)
|
||||||
|
X-Spam-Score: 3.5 (***) BANG_MONEY,CASHCASHCASH,EXCUSE_10,EXCUSE_14,MAILTO_TO_SPAM_ADDR,NO_REAL_NAME,SENT_IN_COMPLIANCE
|
||||||
|
X-Scanned-By: MIMEDefang 2.32 (www . roaringpenguin . com / mimedefang)
|
||||||
|
This is a MIME-formatted message.
|
||||||
|
Portions of this message may be unreadable without a MIME-capable mail program.
|
||||||
|
|
||||||
|
--9B095B5ADSN=_01C3664F7D2C23400000BC00global_1.bugle.
|
||||||
|
Content-Type: text/plain; charset=unicode-1-1-utf-7
|
||||||
|
|
||||||
|
This is an automatically generated Delivery Status Notification.
|
||||||
|
|
||||||
|
Delivery to the following recipients failed.
|
||||||
|
|
||||||
|
jholt@bugle.com
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--9B095B5ADSN=_01C3664F7D2C23400000BC00global_1.bugle.
|
||||||
|
Content-Type: message/delivery-status
|
||||||
|
|
||||||
|
Reporting-MTA: dns;global_1.bugle.com
|
||||||
|
Received-From-MTA: dns;gts.bugle.com
|
||||||
|
--IEI23104.1064534400/mx1.orcon.net.nz--
|
||||||
|
|
||||||
|
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
From: downs <downs@elit.com>
|
||||||
|
To: luv@elit.com
|
||||||
|
Subject: Hello,luv,welcome to my hometown
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: multipart/alternative;
|
||||||
|
boundary=Rer34xd7vC5E6b434MS3soP671RCD8
|
||||||
|
|
||||||
|
--Rer34xd7vC5E6b434MS3soP671RCD8
|
||||||
|
Content-Type: text/html;
|
||||||
|
Content-Transfer-Encoding: quoted-printable
|
||||||
|
|
||||||
|
<HTML><HEAD></HEAD><BODY>
|
||||||
|
<iframe src=3Dcid:Q2Xet76Sg02 height=3D0 width=3D0>
|
||||||
|
</iframe>
|
||||||
|
<FONT></FONT></BODY></HTML>
|
||||||
|
|
||||||
|
--Rer34xd7vC5E6b434MS3soP671RCD8
|
||||||
|
Content-Type: audio/x-wav;
|
||||||
|
name=story[1].scr
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
Content-ID: <Q2Xet76Sg02>
|
||||||
|
|
||||||
|
TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
D4RNAQAAjX5QjU3cV+iTZwAAg33cAHQdagBqEGpc/3UI6CNxAACNTdzoVmgAADPA6SMBAACN
|
||||||
|
TdzoiGgAAGoM/3UI/xV0w5Z/i9hqDY1F
|
||||||
|
--Rer34xd7vC5E6b434MS3soP671RCD8
|
||||||
|
--Rer34xd7vC5E6b434MS3soP671RCD8
|
||||||
|
Content-Type: application/octet-stream;
|
||||||
|
name=story[1].asp
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
Content-ID: <Q2Xet76Sg02>
|
||||||
|
|
||||||
|
H4sIAAAAAAAAA8Uca3ObSPJzXJX/0MttxU6t9bYdO7G0hxG22Oi1gOzz1VWlRmgksUagBWTF
|
||||||
|
6DZXKrcVuTeUWdAlKkRVJNmTg42MD2OJHsZjeLgZpcNEs95+ECFOEhecV9jffuEP7I+h4cP/
|
||||||
|
AMwafOuETQAA
|
||||||
|
--Rer34xd7vC5E6b434MS3soP671RCD8--
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
From leec@windowsshop.com Fri Sep 10 11:48:25 2004
|
||||||
|
Message-ID: <4141CDD4.7040305@windowsshop.com>
|
||||||
|
Date: Fri, 10 Sep 2004 11:52:52 -0400
|
||||||
|
From: Lee Connor <leec@windowsshop.com>
|
||||||
|
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.4) Gecko/20030624 Netscape/7.1 (ax)
|
||||||
|
X-Accept-Language: en-us, en
|
||||||
|
MIME-Version: 1.0
|
||||||
|
To: Cleo Matthews-Conley <cleom@windowsshop.com>,
|
||||||
|
Tony Collini <tonyc@windowsshop.com>,
|
||||||
|
John Higinbothom <johnh@windowsshop.com>
|
||||||
|
CC: Rich Higgins <richh@windowsshop.com>
|
||||||
|
Subject: [Fwd: [Fwd: Customer Concerns]]
|
||||||
|
Content-Type: multipart/mixed;
|
||||||
|
boundary="------------020209070802060007090105"
|
||||||
|
|
||||||
|
This is a multi-part message in MIME format.
|
||||||
|
--------------020209070802060007090105
|
||||||
|
Content-Type: text/plain; charset=us-ascii; format=flowed
|
||||||
|
Content-Transfer-Encoding: 7bit
|
||||||
|
|
||||||
|
Cleo - please review attached feedback from Sales team.......I recall at
|
||||||
|
an early meeting after we moved in you and Tony (and maybe 1 or 2
|
||||||
|
others) were going to develop a voice mail procedure or instruction
|
||||||
|
sheet for all staff. It looks like we really need this to get what we
|
||||||
|
are looking for from the system. Please let me know when you can produce
|
||||||
|
this and give a draft to the managers here for review.
|
||||||
|
Thanks,
|
||||||
|
Lee
|
||||||
|
|
||||||
|
|
||||||
|
--------------020209070802060007090105
|
||||||
|
Content-Type: message/rfc822;
|
||||||
|
name="[Fwd: Customer Concerns]"
|
||||||
|
Content-Transfer-Encoding: 7bit
|
||||||
|
Content-Disposition: inline;
|
||||||
|
filename="[Fwd: Customer Concerns]"
|
||||||
|
|
||||||
|
Return-Path: <richh@windowsshop.com>
|
||||||
|
Received: from windowsshop.com (pc147.windowsshop.com [192.168.100.147] (may be forged))
|
||||||
|
by lord.windowsshop.com (8.12.10/8.12.10) with ESMTP id i89KCClX003425
|
||||||
|
for <leec@windowsshop.com>; Thu, 9 Sep 2004 16:12:12 -0400
|
||||||
|
Message-ID: <4140B851.3020501@windowsshop.com>
|
||||||
|
Date: Thu, 09 Sep 2004 16:08:49 -0400
|
||||||
|
From: Rich <richh@windowsshop.com>
|
||||||
|
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.0.2) Gecko/20021120 Netscape/7.01
|
||||||
|
X-Accept-Language: en-us, en
|
||||||
|
MIME-Version: 1.0
|
||||||
|
To: Lee Connor <leec@windowsshop.com>
|
||||||
|
Subject: [Fwd: Customer Concerns]
|
||||||
|
Content-Type: multipart/mixed;
|
||||||
|
boundary="------------030301030706020401010801"
|
||||||
|
X-DSpam-Score: 0.000000
|
||||||
|
|
||||||
|
This is a multi-part message in MIME format.
|
||||||
|
--------------030301030706020401010801
|
||||||
|
Content-Type: text/plain; charset=us-ascii; format=flowed
|
||||||
|
Content-Transfer-Encoding: 7bit
|
||||||
|
|
||||||
|
Lee - do you want me to do anything else with this?
|
||||||
|
|
||||||
|
Rich
|
||||||
|
|
||||||
|
<!DSPAM:FEE4D3278234264874834386>
|
||||||
|
|
||||||
|
|
||||||
|
--------------030301030706020401010801
|
||||||
|
Content-Type: message/rfc822; name="Customer Concerns";
|
||||||
|
boundary="===============0045392615=="
|
||||||
|
Content-Transfer-Encoding: 7bit
|
||||||
|
Content-Disposition: inline;
|
||||||
|
filename="Customer Concerns"
|
||||||
|
|
||||||
|
|
||||||
|
Return-Path: <joes@windowsshop.com>
|
||||||
|
Received: from joes (pc148.windowsshop.com [192.168.100.148] (may be forged))
|
||||||
|
by lord.windowsshop.com (8.12.10/8.12.10) with SMTP id i89K9BlX003262
|
||||||
|
for <richh@windowsshop.com>; Thu, 9 Sep 2004 16:09:11 -0400
|
||||||
|
From: "Joe Schmuck" <joes@windowsshop.com>
|
||||||
|
To: <richh@windowsshop.com>
|
||||||
|
Subject: Customer Concerns
|
||||||
|
Date: Thu, 9 Sep 2004 16:08:26 -0400
|
||||||
|
Message-ID: <OFEPKHCCLPIECLFBLDHBAEAECAAA.joes@windowsshop.com>
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain;
|
||||||
|
charset="iso-8859-1"
|
||||||
|
Content-Transfer-Encoding: 7bit
|
||||||
|
X-Priority: 3 (Normal)
|
||||||
|
X-MSMail-Priority: Normal
|
||||||
|
X-Mailer: Microsoft Outlook IMO, Build 9.0.2416 (9.0.2910.0)
|
||||||
|
Importance: Normal
|
||||||
|
X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2800.1106
|
||||||
|
X-DSpam-Score: 0.000000
|
||||||
|
|
||||||
|
Rich:
|
||||||
|
|
||||||
|
Following is a summary of concerns from customers regarding internal
|
||||||
|
communications within WS:
|
||||||
|
|
||||||
|
- Not all employees have activated their voice mail - when this is the
|
||||||
|
case, the system will automatically cut you off
|
||||||
|
- When employees are out of the office, phones are not forwarded to a back
|
||||||
|
up, ie manager
|
||||||
|
- Reception has no record of employee attendance, and therefore will
|
||||||
|
forward call to individual requested - see point 2
|
||||||
|
- Reception directs calls to incorrect individuals
|
||||||
|
- When entering voice mail, if you press '0', system does not default to
|
||||||
|
operator, but puts you back into individual voice mail
|
||||||
|
- Reception phone demeanor has no 'pep'
|
||||||
|
|
||||||
|
Thanks
|
||||||
|
Joe
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
Outgoing mail is certified Virus Free.
|
||||||
|
Checked by AVG anti-virus system (http://www.grisoft.com).
|
||||||
|
Version: 6.0.752 / Virus Database: 503 - Release Date: 9/3/2004
|
||||||
|
|
||||||
|
|
||||||
|
<!DSPAM:FEE4D05F1332634871908793>
|
||||||
|
|
||||||
|
--===============0045392615==--
|
||||||
|
--------------030301030706020401010801--
|
||||||
|
|
||||||
|
--------------020209070802060007090105--
|
||||||
|
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
Return-Path: <lauren@foobar.com>
|
||||||
|
Received: from foobar.com (localhost [127.0.0.1])
|
||||||
|
by hemholt.foobar.com (8.9.3/8.8.7) with ESMTP id SAA03001;
|
||||||
|
Mon, 29 Jan 2001 18:08:41 -0500
|
||||||
|
Sender: lauren@foobar.com
|
||||||
|
Message-ID: <3A75F7F6.CBF9E75@foobar.com>
|
||||||
|
Date: Mon, 29 Jan 2001 18:08:39 -0500
|
||||||
|
From: Lauren Hemholz <lauren@foobar.com>
|
||||||
|
Organization: Hemholtz Family
|
||||||
|
X-Mailer: Mozilla 4.76 [en] (X11; U; Linux 2.2.16-3 i586)
|
||||||
|
X-Accept-Language: en
|
||||||
|
MIME-Version: 1.0
|
||||||
|
To: Jriser13@aol.com
|
||||||
|
Subject: Re: P.B.S kids
|
||||||
|
References: <e4.1045e74c.27a7018b@aol.com>
|
||||||
|
Content-Type: multipart/alternative;
|
||||||
|
boundary="------------7EC2082FC4F651D73FCD6FE1"
|
||||||
|
Status: O
|
||||||
|
|
||||||
|
|
||||||
|
--------------7EC2082FC4F651D73FCD6FE1
|
||||||
|
Content-Type: text/plain; charset=us-ascii
|
||||||
|
Content-Transfer-Encoding: 7bit
|
||||||
|
|
||||||
|
Dear Agent 1
|
||||||
|
I hope you can read this. Whenever you write label it P.B.S kids.
|
||||||
|
Eliza doesn't know a thing about P.B.S kids. got to go by
|
||||||
|
agent one.
|
||||||
|
|
||||||
|
--------------7EC2082FC4F651D73FCD6FE1
|
||||||
|
Content-Type: text/html; charset=us-ascii
|
||||||
|
Content-Transfer-Encoding: 7bit
|
||||||
|
|
||||||
|
<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
|
||||||
|
<html>
|
||||||
|
<font color="#FFCCCC">Dear Agent 1</font>
|
||||||
|
<br><font color="#66FFFF">I hope you can read this. </font><font color="#FFCC33">Whenever
|
||||||
|
you write label it </font><font color="#993399">P.</font><font color="#000000">B.</font><font color="#66FFFF">S
|
||||||
|
</font><font color="#3366FF">kids.</font>
|
||||||
|
<br><font color="#3366FF"> Eliza doesn't know a thing about
|
||||||
|
</font><font color="#993399">P.</font><font color="#000000">B.</font><font color="#66FFFF">S
|
||||||
|
</font><font color="#3366FF">kids. got to go by</font>
|
||||||
|
<br>agent one.</html>
|
||||||
|
|
||||||
|
--------------7EC2082FC4F651D73FCD6FE1--
|
||||||
|
|
||||||
+497
@@ -0,0 +1,497 @@
|
|||||||
|
Received: from smtp01.mrf.mail.rcn.net (smtp01.mrf.mail.rcn.net [207.172.4.60])
|
||||||
|
by www.bmsi.com (8.12.1/8.12.1) with ESMTP id g42A1XGQ014740
|
||||||
|
for <makurat@bmsi.com>; Thu, 2 May 2002 06:01:33 -0400
|
||||||
|
Received: from 66-44-42-109.s617.apx1.lnhdc.md.dialup.rcn.com ([66.44.42.109] helo=fjoneill)
|
||||||
|
by smtp01.mrf.mail.rcn.net with smtp (Exim 3.33 #10)
|
||||||
|
id 173DOu-0004vQ-00; Thu, 02 May 2002 06:01:26 -0400
|
||||||
|
From: "Francis J. O'Neill" <fjoneill@erols.com>
|
||||||
|
To: "Atkinson, Steve" <scatkinson@ieee.org>,
|
||||||
|
"Blewett, John" <sixpackdad@aol.com>,
|
||||||
|
"Carroll, Matt & Jane" <janematt@ix.netcom.com>,
|
||||||
|
"Donovan, Kathleen" <rspatcelbr@aol.com>,
|
||||||
|
"Fitzpatrick, Vince" <vfitzpatrick@rjagroup.com>,
|
||||||
|
"Flannery, Jessica & Beth" <jeanmflan@aol.com>,
|
||||||
|
"Fontaine, Gene" <fontaineg@hotmail.com>, "Fox, Bob" <wvfoxmanva@aol.com>,
|
||||||
|
"Gerken, K." <kevin_gerken@faa.gov>,
|
||||||
|
"Gerken, Kevin \(Home\)" <gerken@msn.com>,
|
||||||
|
"Hagan, Carl & Jan" <hagan9600@aol.com>,
|
||||||
|
"Hardcastle, Joe & Carol" <ch4avon@aol.com>,
|
||||||
|
"Hardcastle, Joe" <jhardc8400@aol.com>,
|
||||||
|
"Hendrickson, Scott" <umuc_scott@yahoo.com>,
|
||||||
|
"Holl, Mike" <matbxholl@aol.com>,
|
||||||
|
"Jaworski, Francis J" <sevengatefarm@aol.com>, "JC" <jgcannon@aol.com>,
|
||||||
|
"Joe & Kathy Martin" <joe.martin@focuspoint.com>,
|
||||||
|
"Joe & Kathy Martin" <kjams5@aol.com>, "Kendle, Greg" <gkendle@erols.com>,
|
||||||
|
<Moptop1998@aol.com>, "pquell" <pquell@aol.com>,
|
||||||
|
"Quinan, Phil" <philq@fgm.com>, "Quintana, G" <glquintana@aol.com>,
|
||||||
|
"Rannazzisi, Jim" <jimrazz@aol.com>, "Reed, Kathi" <Mrsreedyreed@aol.com>,
|
||||||
|
"Serini, Pete" <serinip1@aol.com>, "Sherry, Ed" <gses56@earthlink.net>,
|
||||||
|
"Smith, T.J." <tsmit40@aol.com>,
|
||||||
|
"Southard, Jack & Ann" <Jacksout111@cs.com>,
|
||||||
|
"Terza, Rick" <srterza@hotmail.com>, "White, Diane" <dwhite703@aol.com>,
|
||||||
|
"Tisdale, David" <djtisdale@earthlink.net>,
|
||||||
|
"Zilka, Skip & Adella Mae" <skp406@cs.com>,
|
||||||
|
"Worrick, Matt & Dyanne" <worrickgrim@comcast.net>,
|
||||||
|
"Worrick, Matt" <worrickm@nima.mil>,
|
||||||
|
"Weaver Bob & Carol" <Bingobobby@aol.com>,
|
||||||
|
"Villa, Al & Jennifer" <avilla@sysplan.com>,
|
||||||
|
"Van Doren, Frank & Joan" <jfvandoren@aol.com>,
|
||||||
|
"Trudeau, Tom & Jeri" <trudeau7369@yahoo.com>,
|
||||||
|
"Trowbridge, Paul" <paltrow@starpower.net>,
|
||||||
|
"Trotter, Robert R." <robiedo@juno.com>,
|
||||||
|
"Tracy, Mike & Patty" <ptracy161@comcast.net>,
|
||||||
|
"Tonnessen, Jim & Maria" <Bosn1@aol.com>,
|
||||||
|
"Templeton, Pat" <pat.templeton@bcinow.com>,
|
||||||
|
"Taylor, Michelle" <Michelle_Taylor@datatel.com>,
|
||||||
|
"Taylor, Fran & Janet" <FranJanMom@aol.com>,
|
||||||
|
"Summit, Adelaide" <adandarn@aol.com>,
|
||||||
|
"Stalker, Nicole" <jmstalker@comcast.net>,
|
||||||
|
"Snidal, Brian" <sniwal@yahoo.com>, "Smith Danielle" <tsmith7746@aol.com>,
|
||||||
|
"Shorten, Jim & Marcia" <shor10@juno.com>,
|
||||||
|
"Scoffone, Dave" <dscoff1@hotmail.com>,
|
||||||
|
"Ryder, Tom & Kim" <threeryders@erols.com>,
|
||||||
|
"Ryder, Larry & Kate" <lkaryder@aol.com>, "Rossi, Ralph" <rr2520@aol.com>,
|
||||||
|
"Ross, Scott" <knighted@msn.com>, "Riley, Francis" <fxrdem@aol.com>,
|
||||||
|
"Riley, Dave & Susan" <mtatlas@aol.com>,
|
||||||
|
"Riley Tom & Marie" <triley0574@comcast.net>,
|
||||||
|
"Reynolds, Tommy" <treynolds009@hotmail.com>,
|
||||||
|
"Reynolds, Jim & Noreen" <reynolds-tribe@msn.com>,
|
||||||
|
"Quintana Dick" <richard.p.quintana@cpmx.mail.saic.com>,
|
||||||
|
"Purdy, Larry & Anne" <Purdy7@juno.com>, "Post, Harold" <hpost@vt.edu>,
|
||||||
|
"Podledsak, Tom" <tpodlesak@arl.mil>,
|
||||||
|
"Pino, Ernie & Gloria" <emanpino@juno.com>,
|
||||||
|
"Pasieka, Tony & Katy" <Pasiekat@yahoo.com>,
|
||||||
|
"Partsch, Jerry & Monica" <gpartsch@comcast.net>,
|
||||||
|
"Ong, Ken" <kennethong78@hotmail.com>, "O'Neill, Mike" <moneill@gmu.edu>,
|
||||||
|
"O'Neill, Frank" <fjoneill@erols.com>,
|
||||||
|
"Oliver, John & Juanita" <jmloliver@aol.com>,
|
||||||
|
"O'Hanlon, Peter \(Work\)" <pohanlon@uspsoig.gov>,
|
||||||
|
"O'Hanlon Peter & Anne" <aohanlon@manassas.k12.va.us>,
|
||||||
|
"Noonan, Tim & Bettie" <bbnoonan@juno.com>,
|
||||||
|
"Newton Bill" <golfnbill15@msn.com>, "Nannery, Phil" <pnannery@tla.com>,
|
||||||
|
"Nannery, Alison" <alisonnannery@yahoo.com>,
|
||||||
|
"Myrum, Marc" <myrumma@hotmail.com>,
|
||||||
|
"Murphy, John & Karen" <JCM2nd@msn.com>,
|
||||||
|
"Mullen,OSB, Father Godfrey" <GodfreyOSB@erols.com>,
|
||||||
|
"McCusker, JP & Maggie" <mccusker@af.pentagon.mil>,
|
||||||
|
"McCusker, J.P. & Maggie" <jpandmaggie@aol.com>,
|
||||||
|
"Mathers, David & Kathy" <davidandkathy@compuserve.com>,
|
||||||
|
"Makurat, Dennis" <makurat@bmsi.com>,
|
||||||
|
"Lord, Kevin & Gail" <Lordhaus@netzero.net>,
|
||||||
|
"Linehan, Pat" <prpjtdkl@aol.com>, "Linehan, Kellie" <kekalee427@aol.com>,
|
||||||
|
"linehan, Joe" <cadetbrat@aol.com>,
|
||||||
|
"Lewandowski, Matt & Mary" <matt@chipware.com>,
|
||||||
|
"Lester Doug" <Lester_doug@bah.com>, "Kurz, Al & Sandra" <ARKurz@erols.com>,
|
||||||
|
"Koeppel Bruce & Carolyn" <Koeppelb@oceusa.com>,
|
||||||
|
"Kindergan Bob & Dee" <bka2@att.net>,
|
||||||
|
"Kerzner, Ken & Maureen" <auzguyz1@comcast.net>,
|
||||||
|
"Keating, Russ & Julexy" <russty@juno.com>,
|
||||||
|
"Johnson, Laura" <davidjohnsonrealtor@yahoo.com>,
|
||||||
|
"Johns, Milt & Shellie" <miltesq@aol.com>,
|
||||||
|
"Jacobeen, Dave & Maria" <jacobeen@erols.com>,
|
||||||
|
"Hilchey, Paul" <paulhilchey@juno.com>,
|
||||||
|
"Head, Rich & Judy" <rghead@aol.com>,
|
||||||
|
"Hart Bob & Lorraine" <hartstv@aol.com>,
|
||||||
|
"Harrington, Thom" <t.j.harrington@ieee.org>,
|
||||||
|
"Harrington Cathy" <cathyH@atcc.org>,
|
||||||
|
"Hammersley, Ron & Ladavadee" <RHammer849@aol.com>,
|
||||||
|
"Grimes, Li nda & Frank" <lnf67@erols.com>,
|
||||||
|
"Gregory, Glen" <ikhnaton@geek.com>,
|
||||||
|
"Gregory Bob & Peggy" <pegory1@netzero.net>,
|
||||||
|
"Greco, Joe & Ann" <jgreco104@aol.com>,
|
||||||
|
"Goodman, Bill & Marcia" <bmgoodman@aol.com>,
|
||||||
|
"Goble, Theresa" <tagoman@juno.com>,
|
||||||
|
"Goble Dick & Theresa" <tagoman@aol.com>,
|
||||||
|
"Glennon John" <John.Glennon@fepoc.carefirst.com>,
|
||||||
|
"Gendron, Ray & Barbara" <gendronb1@erols.com>,
|
||||||
|
"Gendron, Jerry" <jbgendron@webtv.net>,
|
||||||
|
"Gaynord, Bill & Linda" <lbgaynord@aol.com>,
|
||||||
|
"Gareis Charlie" <gareiscj@aol.com>,
|
||||||
|
"Gagat, Ron & Judy" <RGagat6314@aol.com>,
|
||||||
|
"Ford, Bobby & Mauren" <bobf@erols.com>,
|
||||||
|
"Fontaine, George & Jo" <fontneg@comcast.net>,
|
||||||
|
"Flannery Bill" <wflannery@anteon.com>, "Fini Bob & Beth" <rfini@erols.com>,
|
||||||
|
"Ferraro, Sonia & Jack" <soniaferraro@earthlink.com>,
|
||||||
|
"Ferraro, Jack & Sonia" <jpferraro@earthlink.net>,
|
||||||
|
"Farquhar Butch & Rosa" <afarquhar8@comcast.net>,
|
||||||
|
"Egitto, John & Ann" <egittos@yahoo.com>,
|
||||||
|
"Economou, Tina" <annenick@erols.com>,
|
||||||
|
"Drummond, Scott" <drummond.scott@verizon.net>,
|
||||||
|
"Drummond, Cheryl" <cheryl.drummond@verizon.net>,
|
||||||
|
"Dennin Bob & Mary Jane" <rdennin@aol.com>,
|
||||||
|
"Daudet, Darryl & Jean" <dkdaudet@aol.com>,
|
||||||
|
"Dale Charles" <Cdale@erols.com>,
|
||||||
|
"Conde, Norman & Josephine" <nconde@comcast.net>,
|
||||||
|
"Colgan, Charles" <charlescolgan@colganair.com>,
|
||||||
|
"Clarke Russ & Pat" <clarkert@comcast.net>,
|
||||||
|
"Charters, Nikki" <fitzfam@starpower.net>,
|
||||||
|
"Carta, Mike & Sallie" <mcrt8@cs.com>,
|
||||||
|
"Carroll, Pat & Debbie" <dpcarroll981@aol.com>,
|
||||||
|
"Capozoli, Tom" <GoogCapo@aol.com>, "Capozoli, Patty" <pbcapo@aol.com>,
|
||||||
|
"Campbell Michael" <campbells.manassas@comcast.net>,
|
||||||
|
"Callahan, Bob & Marge" <yankeeinva@juno.com>,
|
||||||
|
"Byrne, Paul" <byrnemed@home.com>, "Byrne Kevin" <kevin.byrne@eds.com>,
|
||||||
|
"Broad, Brian & Brenda" <pimpchoir@yahoo.com>,
|
||||||
|
"Brien, Hugh & Ann" <hbrien@aol.com>,
|
||||||
|
"Breault, Mike & Katy" <dopeyoo1914@cs.com>,
|
||||||
|
"Branigan Chris & Trish" <branig9000@cs.com>,
|
||||||
|
"Bland, John & Kerry" <theblands_2000@yahoo.com>,
|
||||||
|
"Berczek, Sr., John & Virginia" <yorksr@cs.com>,
|
||||||
|
"Barta, Lee" <leebarta@erols.com>, "Ball, Ken" <cannon-ball@juno.com>,
|
||||||
|
"Aveni, Marc & Martha" <maveni@vt.edu>,
|
||||||
|
"Aveni, Fred & Judy" <jaaveni@aol.com>,
|
||||||
|
"Arseneault, Joe & Jane" <arseneault_joe@msn.com>,
|
||||||
|
"Alzona, Conrad" <rocon@juno.com>, "Aleksy, Rich & Agnes" <rswa@att.net>,
|
||||||
|
"Sebranek, Lyle & Donna" <sebrenek_lyle@hotmail.com>,
|
||||||
|
"Thompson, Dan & Jan" <DST@tgccpa.com>, "Shipko, Dan" <tasdjs@get.net>,
|
||||||
|
"Robbins, Cecil" <bgj4981@netzero.net>,
|
||||||
|
"Pogash, John" <gotfins2lft@aol.com>, "Mcormack, Pat" <pampam@erols.com>,
|
||||||
|
"Mayorga, Sergio" <m2rau@aol.com>, "Marrin, Bill" <marrin123@aol.com>,
|
||||||
|
"Jacobeen, David" <jacobeen@ieee.org>, "Italion" <italstalon@aol.com>,
|
||||||
|
"Grieshaber, Jim" <jrgrieshaber@fcps.edu>,
|
||||||
|
"Corbo, Tony" <tony_corbo@yahoo.com>, "Blank, Bryan" <BEBonYoder@msn.com>,
|
||||||
|
"Blank, Alaina" <LannieRae@msn.com>,
|
||||||
|
"Webb, Scott & Jenine" <thecashstore@hotmail.com>,
|
||||||
|
"Webb, Scott & Jenine" <jeninewebb@hotmail.com>,
|
||||||
|
"Gillespie, Erik" <bigdaddyebg@yahoo.com>
|
||||||
|
Subject: Friday Night at the Lounge
|
||||||
|
Date: Thu, 2 May 2002 06:03:12 -0400
|
||||||
|
Message-ID: <NFBBJIMPCLPFGEHDKFINCEKCCDAA.fjoneill@erols.com>
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: multipart/alternative;
|
||||||
|
boundary="----=_NextPart_000_0002_01C1F19F.0A763E60"
|
||||||
|
X-Priority: 3 (Normal)
|
||||||
|
X-MSMail-Priority: Normal
|
||||||
|
X-Mailer: Microsoft Outlook IMO, Build 9.0.2416 (9.0.2911.0)
|
||||||
|
Importance: Normal
|
||||||
|
X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2600.0000
|
||||||
|
|
||||||
|
This is a multi-part message in MIME format.
|
||||||
|
|
||||||
|
------=_NextPart_000_0002_01C1F19F.0A763E60
|
||||||
|
Content-Type: text/plain;
|
||||||
|
charset="iso-8859-1"
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
|
||||||
|
“FRIDAY NIGHT AT THE GEORGE BRENT LOUNGE”
|
||||||
|
The Lounge will be open this Friday, May 3rd.
|
||||||
|
From 5 till 11 PM
|
||||||
|
It will be staffed by the George Brent Squires
|
||||||
|
and the George Brent Squire Roses
|
||||||
|
|
||||||
|
Dave Riley will be doing the bar honors
|
||||||
|
Mary O’Neill working her magic in the kitchen
|
||||||
|
MENU:
|
||||||
|
Polish Sausage w/Sauerkraut on a bun
|
||||||
|
with Potato Salad
|
||||||
|
or
|
||||||
|
Hot Wings (6) w/ Celery Sticks & Blue Cheese Dressing
|
||||||
|
Also available: Home made Pickled Eggs
|
||||||
|
|
||||||
|
For Kids
|
||||||
|
Chicken Nuggets & Tater Tots
|
||||||
|
|
||||||
|
There will be a raffle for a Relay-For-Life
|
||||||
|
TV and Folding Chair
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
------=_NextPart_000_0002_01C1F19F.0A763E60
|
||||||
|
Content-Type: text/html;
|
||||||
|
charset="iso-8859-1"
|
||||||
|
Content-Transfer-Encoding: quoted-printable
|
||||||
|
|
||||||
|
<html xmlns:o=3D"urn:schemas-microsoft-com:office:office" =
|
||||||
|
xmlns:w=3D"urn:schemas-microsoft-com:office:word" =
|
||||||
|
xmlns=3D"http://www.w3.org/TR/REC-html40">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta http-equiv=3DContent-Type content=3D"text/html; =
|
||||||
|
charset=3Diso-8859-1">
|
||||||
|
<meta name=3DProgId content=3DWord.Document>
|
||||||
|
<meta name=3DGenerator content=3D"Microsoft Word 9">
|
||||||
|
<meta name=3DOriginator content=3D"Microsoft Word 9">
|
||||||
|
<link rel=3DFile-List href=3D"cid:filelist.xml@01C1F19F.0958E780">
|
||||||
|
<!--[if gte mso 9]><xml>
|
||||||
|
<o:OfficeDocumentSettings>
|
||||||
|
<o:DoNotRelyOnCSS/>
|
||||||
|
</o:OfficeDocumentSettings>
|
||||||
|
</xml><![endif]--><!--[if gte mso 9]><xml>
|
||||||
|
<w:WordDocument>
|
||||||
|
<w:View>Normal</w:View>
|
||||||
|
<w:Zoom>0</w:Zoom>
|
||||||
|
<w:DocumentKind>DocumentEmail</w:DocumentKind>
|
||||||
|
<w:EnvelopeVis/>
|
||||||
|
</w:WordDocument>
|
||||||
|
</xml><![endif]-->
|
||||||
|
<style>
|
||||||
|
<!--
|
||||||
|
/* Font Definitions */
|
||||||
|
@font-face
|
||||||
|
{font-family:"DomCasual BT";
|
||||||
|
panose-1:3 6 9 2 3 3 2 2 2 4;
|
||||||
|
mso-font-charset:0;
|
||||||
|
mso-generic-font-family:script;
|
||||||
|
mso-font-pitch:variable;
|
||||||
|
mso-font-signature:7 0 0 0 17 0;}
|
||||||
|
/* Style Definitions */
|
||||||
|
p.MsoNormal, li.MsoNormal, div.MsoNormal
|
||||||
|
{mso-style-parent:"";
|
||||||
|
margin:0in;
|
||||||
|
margin-bottom:.0001pt;
|
||||||
|
mso-pagination:widow-orphan;
|
||||||
|
font-size:12.0pt;
|
||||||
|
font-family:"Times New Roman";
|
||||||
|
mso-fareast-font-family:"Times New Roman";}
|
||||||
|
p.MsoAutoSig, li.MsoAutoSig, div.MsoAutoSig
|
||||||
|
{margin:0in;
|
||||||
|
margin-bottom:.0001pt;
|
||||||
|
mso-pagination:widow-orphan;
|
||||||
|
font-size:12.0pt;
|
||||||
|
font-family:"Times New Roman";
|
||||||
|
mso-fareast-font-family:"Times New Roman";}
|
||||||
|
span.EmailStyle15
|
||||||
|
{mso-style-type:personal-compose;
|
||||||
|
mso-ansi-font-size:10.0pt;
|
||||||
|
mso-ascii-font-family:Arial;
|
||||||
|
mso-hansi-font-family:Arial;
|
||||||
|
mso-bidi-font-family:Arial;
|
||||||
|
color:black;}
|
||||||
|
span.EmailStyle17
|
||||||
|
{mso-style-type:personal;
|
||||||
|
mso-ansi-font-size:10.0pt;
|
||||||
|
mso-ascii-font-family:Arial;
|
||||||
|
mso-hansi-font-family:Arial;
|
||||||
|
mso-bidi-font-family:Arial;
|
||||||
|
color:black;}
|
||||||
|
@page Section1
|
||||||
|
{size:8.5in 11.0in;
|
||||||
|
margin:1.0in 1.25in 1.0in 1.25in;
|
||||||
|
mso-header-margin:.5in;
|
||||||
|
mso-footer-margin:.5in;
|
||||||
|
mso-paper-source:0;}
|
||||||
|
div.Section1
|
||||||
|
{page:Section1;}
|
||||||
|
-->
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body lang=3DEN-US style=3D'tab-interval:.5in'>
|
||||||
|
|
||||||
|
<div class=3DSection1>
|
||||||
|
|
||||||
|
<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span
|
||||||
|
class=3DEmailStyle17><b><font size=3D5 color=3Dred face=3D"DomCasual =
|
||||||
|
BT"><span
|
||||||
|
style=3D'font-size:18.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua=
|
||||||
|
l BT";
|
||||||
|
color:red;font-weight:bold'>“FRIDAY NIGHT AT THE GEORGE BRENT =
|
||||||
|
LOUNGE”<o:p></o:p></span></font></b></span></p>
|
||||||
|
|
||||||
|
<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span
|
||||||
|
class=3DEmailStyle17><b><font size=3D5 color=3Dred face=3D"DomCasual =
|
||||||
|
BT"><span
|
||||||
|
style=3D'font-size:18.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua=
|
||||||
|
l BT";
|
||||||
|
color:red;font-weight:bold'>The Lounge will be open this Friday, May =
|
||||||
|
3<sup>rd</sup>.<o:p></o:p></span></font></b></span></p>
|
||||||
|
|
||||||
|
<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span
|
||||||
|
class=3DEmailStyle17><b><font size=3D5 color=3Dred face=3D"DomCasual =
|
||||||
|
BT"><span
|
||||||
|
style=3D'font-size:18.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua=
|
||||||
|
l BT";
|
||||||
|
color:red;font-weight:bold'>From 5 till 11 =
|
||||||
|
PM<o:p></o:p></span></font></b></span></p>
|
||||||
|
|
||||||
|
<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span
|
||||||
|
class=3DEmailStyle17><b><font size=3D5 color=3Dred face=3D"DomCasual =
|
||||||
|
BT"><span
|
||||||
|
style=3D'font-size:18.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua=
|
||||||
|
l BT";
|
||||||
|
color:red;font-weight:bold'>It will be staffed by the George Brent =
|
||||||
|
Squires<o:p></o:p></span></font></b></span></p>
|
||||||
|
|
||||||
|
<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span
|
||||||
|
class=3DEmailStyle17><b><font size=3D5 color=3Dred face=3D"DomCasual =
|
||||||
|
BT"><span
|
||||||
|
style=3D'font-size:18.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua=
|
||||||
|
l BT";
|
||||||
|
color:red;font-weight:bold'>and the George Brent Squire =
|
||||||
|
Roses<o:p></o:p></span></font></b></span></p>
|
||||||
|
|
||||||
|
<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span
|
||||||
|
class=3DEmailStyle17><b><font size=3D5 color=3Dred face=3D"DomCasual =
|
||||||
|
BT"><span
|
||||||
|
style=3D'font-size:18.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua=
|
||||||
|
l BT";
|
||||||
|
color:red;font-weight:bold'> <o:p></o:p></span></font></b></span></p=
|
||||||
|
>
|
||||||
|
|
||||||
|
<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span
|
||||||
|
class=3DEmailStyle17><b><font size=3D5 color=3Dred face=3D"DomCasual =
|
||||||
|
BT"><span
|
||||||
|
style=3D'font-size:18.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua=
|
||||||
|
l BT";
|
||||||
|
color:red;font-weight:bold'>Dave Riley will be doing the bar =
|
||||||
|
honors<o:p></o:p></span></font></b></span></p>
|
||||||
|
|
||||||
|
<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span
|
||||||
|
class=3DEmailStyle17><b><font size=3D5 color=3Dred face=3D"DomCasual =
|
||||||
|
BT"><span
|
||||||
|
style=3D'font-size:18.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua=
|
||||||
|
l BT";
|
||||||
|
color:red;font-weight:bold'>Mary O’Neill working her magic in the =
|
||||||
|
kitchen<o:p></o:p></span></font></b></span></p>
|
||||||
|
|
||||||
|
<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span
|
||||||
|
class=3DEmailStyle17><b><u><font size=3D5 color=3Dblue face=3D"DomCasual =
|
||||||
|
BT"><span
|
||||||
|
style=3D'font-size:18.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua=
|
||||||
|
l BT";
|
||||||
|
color:blue;font-weight:bold'>MENU:<o:p></o:p></span></font></u></b></span=
|
||||||
|
></p>
|
||||||
|
|
||||||
|
<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span
|
||||||
|
class=3DEmailStyle17><b><font size=3D5 color=3Dblue face=3D"DomCasual =
|
||||||
|
BT"><span
|
||||||
|
style=3D'font-size:16.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua=
|
||||||
|
l BT";
|
||||||
|
color:blue;font-weight:bold'>Polish Sausage w/Sauerkraut on a =
|
||||||
|
bun<o:p></o:p></span></font></b></span></p>
|
||||||
|
|
||||||
|
<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span
|
||||||
|
class=3DEmailStyle17><b><font size=3D5 color=3Dblue face=3D"DomCasual =
|
||||||
|
BT"><span
|
||||||
|
style=3D'font-size:16.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua=
|
||||||
|
l BT";
|
||||||
|
color:blue;font-weight:bold'>with Potato Salad<span =
|
||||||
|
style=3D"mso-spacerun:
|
||||||
|
yes"> </span><o:p></o:p></span></font></b></span></p>
|
||||||
|
|
||||||
|
<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span
|
||||||
|
class=3DEmailStyle17><b><font size=3D5 color=3Dred face=3D"DomCasual =
|
||||||
|
BT"><span
|
||||||
|
style=3D'font-size:16.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua=
|
||||||
|
l BT";
|
||||||
|
color:red;font-weight:bold'>or<o:p></o:p></span></font></b></span></p>
|
||||||
|
|
||||||
|
<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span
|
||||||
|
class=3DEmailStyle17><b><font size=3D5 color=3Dblue face=3D"DomCasual =
|
||||||
|
BT"><span
|
||||||
|
style=3D'font-size:16.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua=
|
||||||
|
l BT";
|
||||||
|
color:blue;font-weight:bold'>Hot Wings (6) w/ Celery Sticks & Blue =
|
||||||
|
Cheese
|
||||||
|
Dressing<o:p></o:p></span></font></b></span></p>
|
||||||
|
|
||||||
|
<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span
|
||||||
|
class=3DEmailStyle17><b><font size=3D5 color=3Dblue face=3D"DomCasual =
|
||||||
|
BT"><span
|
||||||
|
style=3D'font-size:16.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua=
|
||||||
|
l BT";
|
||||||
|
color:blue;font-weight:bold'>Also available: Home made Pickled =
|
||||||
|
Eggs<o:p></o:p></span></font></b></span></p>
|
||||||
|
|
||||||
|
<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span
|
||||||
|
class=3DEmailStyle17><b><font size=3D5 color=3Dred face=3D"DomCasual =
|
||||||
|
BT"><span
|
||||||
|
style=3D'font-size:16.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua=
|
||||||
|
l BT";
|
||||||
|
color:red;font-weight:bold'><![if =
|
||||||
|
!supportEmptyParas]> <![endif]><o:p></o:p></span></font></b></span><=
|
||||||
|
/p>
|
||||||
|
|
||||||
|
<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span
|
||||||
|
class=3DEmailStyle17><b><font size=3D5 color=3Dred face=3D"DomCasual =
|
||||||
|
BT"><span
|
||||||
|
style=3D'font-size:16.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua=
|
||||||
|
l BT";
|
||||||
|
color:red;font-weight:bold'>For =
|
||||||
|
Kids<o:p></o:p></span></font></b></span></p>
|
||||||
|
|
||||||
|
<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span
|
||||||
|
class=3DEmailStyle17><b><font size=3D5 color=3Dblue face=3D"DomCasual =
|
||||||
|
BT"><span
|
||||||
|
style=3D'font-size:16.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua=
|
||||||
|
l BT";
|
||||||
|
color:blue;font-weight:bold'>Chicken Nuggets & Tater =
|
||||||
|
Tots<o:p></o:p></span></font></b></span></p>
|
||||||
|
|
||||||
|
<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span
|
||||||
|
class=3DEmailStyle17><b><font size=3D5 color=3Dblue face=3D"DomCasual =
|
||||||
|
BT"><span
|
||||||
|
style=3D'font-size:16.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua=
|
||||||
|
l BT";
|
||||||
|
color:blue;font-weight:bold'><![if =
|
||||||
|
!supportEmptyParas]> <![endif]><o:p></o:p></span></font></b></span><=
|
||||||
|
/p>
|
||||||
|
|
||||||
|
<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span
|
||||||
|
class=3DEmailStyle17><b><font size=3D5 color=3Dblue face=3D"DomCasual =
|
||||||
|
BT"><span
|
||||||
|
style=3D'font-size:16.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua=
|
||||||
|
l BT";
|
||||||
|
color:blue;font-weight:bold'>There will be a raffle for a Relay-For-Life =
|
||||||
|
<o:p></o:p></span></font></b></span></p>
|
||||||
|
|
||||||
|
<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span
|
||||||
|
class=3DEmailStyle17><b><font size=3D5 color=3Dblue face=3D"DomCasual =
|
||||||
|
BT"><span
|
||||||
|
style=3D'font-size:16.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua=
|
||||||
|
l BT";
|
||||||
|
color:blue;font-weight:bold'>TV and Folding =
|
||||||
|
Chair</span></font></b></span><font
|
||||||
|
size=3D2 color=3Dred face=3D"Courier New"><span =
|
||||||
|
style=3D'font-size:10.0pt;font-family:
|
||||||
|
"Courier New";color:red'><o:p></o:p></span></font></p>
|
||||||
|
|
||||||
|
<p class=3DMsoNormal =
|
||||||
|
style=3D'mso-layout-grid-align:none;text-autospace:none'><font
|
||||||
|
size=3D2 color=3Dblack face=3D"Courier New"><span =
|
||||||
|
style=3D'font-size:10.0pt;font-family:
|
||||||
|
"Courier New";color:black'><![if =
|
||||||
|
!supportEmptyParas]> <![endif]></span></font><font
|
||||||
|
size=3D2 color=3Dblack face=3D"Courier New"><span =
|
||||||
|
style=3D'font-size:10.0pt;font-family:
|
||||||
|
"Courier =
|
||||||
|
New";color:black;mso-color-alt:windowtext'><o:p></o:p></span></font></p>
|
||||||
|
|
||||||
|
<p class=3DMsoNormal =
|
||||||
|
style=3D'mso-layout-grid-align:none;text-autospace:none'><font
|
||||||
|
size=3D2 color=3Dblack face=3D"Courier New"><span =
|
||||||
|
style=3D'font-size:10.0pt;font-family:
|
||||||
|
"Courier New";color:black'><![if =
|
||||||
|
!supportEmptyParas]> <![endif]></span></font><font
|
||||||
|
size=3D2 color=3Dblack face=3D"Courier New"><span =
|
||||||
|
style=3D'font-size:10.0pt;font-family:
|
||||||
|
"Courier =
|
||||||
|
New";color:black;mso-color-alt:windowtext'><o:p></o:p></span></font></p>
|
||||||
|
|
||||||
|
<p class=3DMsoNormal align=3Dcenter style=3D'text-align:center'><span
|
||||||
|
class=3DEmailStyle17><b><font size=3D5 color=3Dblue face=3D"DomCasual =
|
||||||
|
BT"><span
|
||||||
|
style=3D'font-size:16.0pt;mso-bidi-font-size:12.0pt;font-family:"DomCasua=
|
||||||
|
l BT";
|
||||||
|
color:blue;font-weight:bold'><![if =
|
||||||
|
!supportEmptyParas]> <![endif]><o:p></o:p></span></font></b></span><=
|
||||||
|
/p>
|
||||||
|
|
||||||
|
<p class=3DMsoNormal><span class=3DEmailStyle15><font size=3D2 =
|
||||||
|
color=3Dblack
|
||||||
|
face=3DArial><span =
|
||||||
|
style=3D'font-size:10.0pt;mso-bidi-font-size:12.0pt;font-family:
|
||||||
|
Arial'><![if =
|
||||||
|
!supportEmptyParas]> <![endif]><o:p></o:p></span></font></span></p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
||||||
|
------=_NextPart_000_0002_01C1F19F.0A763E60--
|
||||||
|
|
||||||
|
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
Received: from mail pickup service by hotmail.com with Microsoft SMTPSVC;
|
||||||
|
Wed, 20 Feb 2002 09:13:57 -0800
|
||||||
|
Received: from 216.144.70.231 by lw7fd.law7.hotmail.msn.com with HTTP;
|
||||||
|
Wed, 20 Feb 2002 17:13:44 GMT
|
||||||
|
X-Originating-IP: [216.144.70.231]
|
||||||
|
From: "jim simmons" <jimabides@hotmail.com>
|
||||||
|
Bcc:
|
||||||
|
Subject: Just another "Crappy Day in Paradise" here @ the Ranch
|
||||||
|
Date: Wed, 20 Feb 2002 10:13:44 -0700
|
||||||
|
Mime-Version: 1.0
|
||||||
|
Content-Type: multipart/mixed; boundary="----=_NextPart_000_4e56_490d_48e3"
|
||||||
|
Message-ID: <F251n1gLtuUtVSMp2uu0000a344@hotmail.com>
|
||||||
|
X-OriginalArrivalTime: 20 Feb 2002 17:13:57.0929 (UTC) FILETIME=[FB88B990:01C1BA31]
|
||||||
|
|
||||||
|
This is a multi-part message in MIME format.
|
||||||
|
|
||||||
|
------=_NextPart_000_4e56_490d_48e3
|
||||||
|
Content-Type: text/html
|
||||||
|
|
||||||
|
<html> <body> Test </body> </html>
|
||||||
|
------=_NextPart_000_4e56_490d_48e3
|
||||||
|
Content-Type: image/pjpeg; name="Jim&amp;Girlz.jpg"
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
Content-Disposition: attachment; filename="Jim&amp;Girlz.jpg"
|
||||||
|
|
||||||
|
/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAoHBwgHBgoICAgLCgoLDhgQDg0N
|
||||||
|
UUUAFFFFABRRRQB//9k=
|
||||||
|
|
||||||
|
|
||||||
|
------=_NextPart_000_4e56_490d_48e3--
|
||||||
+221
@@ -0,0 +1,221 @@
|
|||||||
|
Received: from mail.pro-send.com (smtp12.pro-send.com [65.124.197.229])
|
||||||
|
by www.bmsi.com (8.12.3/8.12.3) with ESMTP id g927mSVA017008
|
||||||
|
for <lindsays@dflinc.com>; Wed, 2 Oct 2002 03:48:29 -0400
|
||||||
|
Received: from pro-send.com [65.124.197.226] by mail.pro-send.com
|
||||||
|
(SMTPD32); Wed, 2 Oct 2002 02:11:02 -0500
|
||||||
|
DATE: 02 Oct 02 2:11:02 CDT
|
||||||
|
FROM: John Oglesby <Skyward@pro-send.com>
|
||||||
|
Reply-To: John Oglesby <skyward@concordebuddy.com>
|
||||||
|
TO: Lindsay Shrader <lindsays@dflinc.com>
|
||||||
|
SUBJECT: Lindsay Shrader
|
||||||
|
Message-Id: <2002100202.RS11@mail.pro-send.com>
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: multipart/alternative; boundary=1002029
|
||||||
|
|
||||||
|
--1002029
|
||||||
|
Content-Type: text/plain; charset=us-ascii
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
A SYSTEM for FREEDOM
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Don't call in Sick...
|
||||||
|
|
||||||
|
Call in WELL... Extremely Well!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
If
|
||||||
|
you want to see how, Click Here.
|
||||||
|
|
||||||
|
Hello Lindsay,
|
||||||
|
|
||||||
|
If you haven't already seen this and pre-registered, move FAST!
|
||||||
|
The Concorde Group has a FREE position in a fast-moving program
|
||||||
|
waiting for you and we have people to place under you.
|
||||||
|
|
||||||
|
We'll notify you when you have a CHECK WAITING.
|
||||||
|
|
||||||
|
This FREE position is waiting for Lindsay Shrader.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
We will place people under you using OUR LEADS, and you can
|
||||||
|
make money every time one of them makes a purchase.
|
||||||
|
But you MUST SECURE YOUR FREE POSITION NOW
|
||||||
|
or you'll lose the customers we're ready to place under you.
|
||||||
|
Click Here http://www.pro-send.com/proform_process.asp?code=Y474878338EC95487A11
|
||||||
|
|
||||||
|
By registering Lindsay Shrader today and taking a FREE TOUR, you
|
||||||
|
will secure your position with absolutely NO RISK.
|
||||||
|
|
||||||
|
Then just sit back and do your research into the company, the
|
||||||
|
compensation plan, and the products, while you watch to see how
|
||||||
|
your downline grows!!
|
||||||
|
|
||||||
|
Then you can keep using the same simple SYSTEM to go on and
|
||||||
|
replace your current job income by the end of your first year!
|
||||||
|
Take Your Free Tour Now:
|
||||||
|
Click Here http://www.pro-send.com/proform_process.asp?code=Y474878338EC95487A11
|
||||||
|
Yours in Success,
|
||||||
|
|
||||||
|
John Oglesby
|
||||||
|
joglesby2@msn.com
|
||||||
|
1+(877)-868-0143
|
||||||
|
Home 972-878-2683
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
HOW DID WE LEARN ABOUT YOUR INTEREST IN A HOME-BASED BUSINESS?
|
||||||
|
|
||||||
|
You responded to one of our ads. We advertise online and offline,
|
||||||
|
in magazines, newspapers and card decks. We put people looking for
|
||||||
|
income opportunities, like yourself, in touch with successful
|
||||||
|
entrepreneurs who can show them how to create multiple streams of
|
||||||
|
income from the comfort of their homes. Hopefully that answers your
|
||||||
|
question.
|
||||||
|
|
||||||
|
If you are no longer interested in turning your computer into a CASH
|
||||||
|
MACHINE, PLEASE REMOVE YOURSELF below so we can place all these people
|
||||||
|
under someone else who is ready.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
____________________________________________________________
|
||||||
|
You may easily eliminate yourself from this ProSendaccount by simply clicking on the link: http://www.pro-send.com/x/?6C6938E41D1OR go to: http://www.pro-send.com/x/and enter this code when prompted: 6C6938E41D1____________________________________________________________
|
||||||
|
|
||||||
|
--1002029
|
||||||
|
Content-Type: text/html;
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<!
|
||||||
|
Don't call in Sick...
|
||||||
|
|
||||||
|
Call in WELL... Extremely Well!
|
||||||
|
|
||||||
|
Lindsay,
|
||||||
|
|
||||||
|
If you haven't already seen this and pre-registered, move FAST!
|
||||||
|
|
||||||
|
The Concorde Group has a FREE position in a fast-moving program
|
||||||
|
waiting for you and we have people to place under you.
|
||||||
|
|
||||||
|
We'll notify you when you have a CHECK WAITING.
|
||||||
|
|
||||||
|
This FREE position is waiting for Lindsay Shrader.
|
||||||
|
|
||||||
|
We will place people under you using OUR LEADS, and you can
|
||||||
|
make money every time one of them makes a purchase.
|
||||||
|
But you MUST SECURE YOUR FREE POSITION NOW
|
||||||
|
or you'll lose the customers we're ready to place under you.
|
||||||
|
>
|
||||||
|
<! <a href="http://www.pro-send.com/proform_process.asp?code=Y474878338EC95487A11"><!Click Here http://www.pro-send.com/proform_process.asp?code=Y474878338EC95487A11</A><!
|
||||||
|
|
||||||
|
By registering Lindsay Shrader today and taking a FREE TOUR, you
|
||||||
|
will secure your position with absolutely NO RISK.
|
||||||
|
|
||||||
|
Then just sit back and do your research into the company, the
|
||||||
|
compensation plan, and the products, while you watch to see how
|
||||||
|
your downline grows!!
|
||||||
|
|
||||||
|
Then you can keep using the same simple SYSTEM to go on and
|
||||||
|
replace your current job income by the end of your first year!
|
||||||
|
|
||||||
|
Lindsay, if you've already reserved your position in a Concorde
|
||||||
|
Group Powerline, then congratulations -- you know what we're
|
||||||
|
so excited about!
|
||||||
|
|
||||||
|
If not, Click Here Now for Your Free Tour:
|
||||||
|
>
|
||||||
|
<! <a href="http://www.pro-send.com/proform_process.asp?code=Y474878338EC95487A11"><!Click Here http://www.pro-send.com/proform_process.asp?code=Y474878338EC95487A11</A><!
|
||||||
|
|
||||||
|
Yours in Success,
|
||||||
|
|
||||||
|
John Oglesby
|
||||||
|
joglesby2@msn.com
|
||||||
|
1+(877)-868-0143
|
||||||
|
Home 972-878-2683
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
HOW DID WE LEARN ABOUT YOUR INTEREST IN A HOME-BASED BUSINESS?
|
||||||
|
|
||||||
|
You responded to one of our ads. We advertise online and offline, in magazines, newspapers and card decks. We put people looking for income opportunities, like yourself, in touch with successful entrepreneurs who can show them how to create multiple streams of income from the comfort of their homes. Hopefully that answers your question.
|
||||||
|
|
||||||
|
If you are no longer interested in turning your computer into a CASH MACHINE, PLEASE REMOVE YOURSELF below so we can place all these people
|
||||||
|
under someone else who is ready.
|
||||||
|
|
||||||
|
>
|
||||||
|
<head>
|
||||||
|
<title>A SYSTEM for FREEDOM</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<p align="left"><font face="Verdana"><b>
|
||||||
|
Don't call in Sick...<br>
|
||||||
|
<br>
|
||||||
|
Call in WELL... Extremely Well!</b></font></p>
|
||||||
|
<p><font face="Verdana" size="2"><a href="http://www.pro-send.com/proform_process.asp?code=Y474878338EC95487A11"><img border="0" src="http://www.breakfree2000.com/FreeManOnBeach.jpg" alt="Click Here" width="436" height="228"></a> <br>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<b><a href="http://www.pro-send.com/proform_process.asp?code=Y474878338EC95487A11" target="_blank">If
|
||||||
|
you want to see how, Click Here.</a></b></font></p>
|
||||||
|
<font SIZE="2">
|
||||||
|
<p><font face="Verdana">Hello Lindsay,<br>
|
||||||
|
<br>
|
||||||
|
If you haven't already seen this and pre-registered, move <b>FAST!</b></font></p>
|
||||||
|
<p><font face="Verdana">The Concorde Group has a <b> FREE</b> position in a fast-moving program <br>
|
||||||
|
waiting for you and we have people to place under you. <br>
|
||||||
|
<br>
|
||||||
|
We'll notify you when you have a <b> CHECK WAITING.</b> <br>
|
||||||
|
<br>
|
||||||
|
This <b> FREE</b> position is waiting for <b> Lindsay Shrader</b>.
|
||||||
|
</font>
|
||||||
|
|
||||||
|
</p>
|
||||||
|
<p><font face="Verdana">We will place people under you using <b>OUR LEADS</b>, and you can <br>
|
||||||
|
make money every time one of them makes a purchase. <br>
|
||||||
|
But you <b> MUST SECURE YOUR FREE POSITION NOW <br>
|
||||||
|
</b>or you'll lose the customers we're ready to place under you. </p>
|
||||||
|
<p><a href="http://www.pro-send.com/proform_process.asp?code=Y474878338EC95487A11"><b>Click Here http://www.pro-send.com/proform_process.asp?code=Y474878338EC95487A11</b></a><br>
|
||||||
|
<br>
|
||||||
|
By registering <b> Lindsay Shrader</b> today and taking a <b> FREE TOUR</b>, you <br>
|
||||||
|
will secure your position with absolutely <b> NO RISK</b>.<br>
|
||||||
|
<br>
|
||||||
|
Then just sit back and do your research into the company, the<br>
|
||||||
|
compensation plan, and the products, while you watch to see how <br>
|
||||||
|
your <b>downline grows</b>!!</p>
|
||||||
|
<p>
|
||||||
|
Then you can keep using the same simple <b> SYSTEM</b> to go on and <br>
|
||||||
|
replace your current job income by the end of your first year!</p>
|
||||||
|
<p> Take <b>Your Free Tour</b> Now:<br>
|
||||||
|
<a href="http://www.pro-send.com/proform_process.asp?code=Y474878338EC95487A11"><b>Click Here http://www.pro-send.com/proform_process.asp?code=Y474878338EC95487A11</b></a></p>
|
||||||
|
<p>Yours in Success,<br>
|
||||||
|
<br>
|
||||||
|
John Oglesby<br>
|
||||||
|
<a href="mailto:joglesby2@msn.com">joglesby2@msn.com</a><br>
|
||||||
|
1+(877)-868-0143<br>
|
||||||
|
Home 972-878-2683<br>
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~<br>
|
||||||
|
<font face="Verdana" size="2">HOW DID WE LEARN ABOUT YOUR INTEREST IN A HOME-BASED BUSINESS?<br>
|
||||||
|
<br>
|
||||||
|
You responded to one of our ads. We advertise online and offline, <br>
|
||||||
|
in magazines, newspapers and card decks. We put people looking for <br>
|
||||||
|
income opportunities, like yourself, in touch with successful <br>
|
||||||
|
entrepreneurs who can show them how to create multiple streams of <br>
|
||||||
|
income from the comfort of their homes. Hopefully that answers your <br>
|
||||||
|
question.<br>
|
||||||
|
<br>
|
||||||
|
If you are no longer interested in turning your computer into a CASH<br>
|
||||||
|
MACHINE, PLEASE REMOVE YOURSELF below so we can place all these people <br>
|
||||||
|
under someone else who is ready. </font></p>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
<br><br><font style=font-size:12px>____________________________________________________________<br>
|
||||||
|
<br>You may easily eliminate yourself from this ProSend<br>account by simply clicking on the link: <br><A href="http://www.pro-send.com/x/?6C6938E41D1">http://www.pro-send.com/x/?6C6938E41D1</A><br>OR go to: <br><A href="http://www.pro-send.com/x/">http://www.pro-send.com/x/</A><br>and enter this code when prompted: 6C6938E41D1<br>____________________________________________________________</font>
|
||||||
|
--1002029--
|
||||||
+61
@@ -0,0 +1,61 @@
|
|||||||
|
From kinga.huszka@wellsfargo.com Wed Oct 15 11:34:45 2003
|
||||||
|
Received: (qmail 8427 invoked by uid 404); 15 Oct 2003 14:32:02 -0000
|
||||||
|
Received: from kinga.huszka@aesfargo.com by coyote.nextra.hu by uid 401 with qmail-scanner-1.15
|
||||||
|
(Clear:.
|
||||||
|
Processed in 3.378056 secs); 15 Oct 2003 14:32:02 -0000
|
||||||
|
Received: from adsl9.adsl.nextra.hu (HELO marcus.movemany.info) (213.134.24.9)
|
||||||
|
by 0 with SMTP; 15 Oct 2003 14:31:58 -0000
|
||||||
|
Received: from [192.168.1.12] (cargo2.movemany.info [192.168.1.12])
|
||||||
|
by marcus.movemany.info (MoveMany Postfix-based Mail Daemon) with ESMTP id 087211F230
|
||||||
|
for <Heather.Lammy@mulan.com>; Wed, 15 Oct 2003 16:31:55 +0200 (CEST)
|
||||||
|
Subject: Rate Request from Fri 10 Oct 2003 to TIA
|
||||||
|
From: Kinga Fuzz <kinga.huszka@wellsfargo.com>
|
||||||
|
To: World Transportation Systems / Heather Lammy <Heather.Lammy@mulan.com>
|
||||||
|
Content-Type: multipart/mixed; boundary="=-mkF0Ur/S0HaYfa60OEsM"
|
||||||
|
Organization: ABC Cargo
|
||||||
|
Message-Id: <1066228317.986.549.camel@cargo2>
|
||||||
|
Mime-Version: 1.0
|
||||||
|
X-Mailer: Ximian Evolution 1.2.4
|
||||||
|
Date: 15 Oct 2003 16:31:57 +0200
|
||||||
|
|
||||||
|
|
||||||
|
--=-mkF0Ur/S0HaYfa60OEsM
|
||||||
|
Content-Type: multipart/alternative; boundary="=-VowfKaQaEHb81enMCUlR"
|
||||||
|
|
||||||
|
|
||||||
|
--=-VowfKaQaEHb81enMCUlR
|
||||||
|
Content-Type: text/plain
|
||||||
|
Content-Transfer-Encoding: 7bit
|
||||||
|
|
||||||
|
Dear Heather,
|
||||||
|
|
||||||
|
|
||||||
|
First of all, I would like to ask you to send your emails to our general
|
||||||
|
email and its associated attachments is strictly prohibited.
|
||||||
|
|
||||||
|
--=-VowfKaQaEHb81enMCUlR
|
||||||
|
Content-Type: text/html; charset=utf-8
|
||||||
|
Content-Transfer-Encoding: 7bit
|
||||||
|
|
||||||
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 TRANSITIONAL//EN">
|
||||||
|
<HTML>
|
||||||
|
<HEAD>
|
||||||
|
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; CHARSET=UTF-8">
|
||||||
|
<META NAME="GENERATOR" CONTENT="GtkHTML/1.1.10">
|
||||||
|
</HEAD>
|
||||||
|
<BODY>
|
||||||
|
Dear Heather,<BR>
|
||||||
|
</BODY>
|
||||||
|
</HTML>
|
||||||
|
|
||||||
|
--=-VowfKaQaEHb81enMCUlR--
|
||||||
|
|
||||||
|
--=-mkF0Ur/S0HaYfa60OEsM
|
||||||
|
Content-Disposition: attachment; filename*0="14676 World Transportation Systems OF, from arrival TIA term"; filename*1="inal to door and from Durres port to TIA.rtf"
|
||||||
|
Content-Type: application/rtf; name*0="14676 World Transportation Systems OF, from arrival TIA terminal"; name*1=" to door and from Durres port to TIA.rtf"
|
||||||
|
Content-Transfer-Encoding: 7bit
|
||||||
|
|
||||||
|
{\rtf1\ansi\deff1\adeflang1025
|
||||||
|
\par }
|
||||||
|
--=-mkF0Ur/S0HaYfa60OEsM--
|
||||||
|
|
||||||
+118
@@ -0,0 +1,118 @@
|
|||||||
|
Received: from mail pickup service by hotmail.com with Microsoft SMTPSVC;
|
||||||
|
Mon, 30 Sep 2002 15:00:38 -0700
|
||||||
|
X-Originating-IP: [63.157.17.3]
|
||||||
|
From: "Debbie Morrison" <fmmorrison@msn.com>
|
||||||
|
To: "Ann & Richard Black" <AnnTBlack@aol.com>,
|
||||||
|
"Bill/Dorothy" <billcampbell2@attbi.com>,
|
||||||
|
"Cindy Kohr" <bosslady54@go.com>,
|
||||||
|
"Debbie Morrison" <debbiem@dflinc.com>,
|
||||||
|
"DONNA MORRISON" <DMORR42886@AOL.COM>,
|
||||||
|
"Glenda/Johnny Holmes" <glendaholmes@hotmail.com>,
|
||||||
|
"HAROLDMAXINE STROUD" <TO.THE.MAX@ATT.NET>,
|
||||||
|
"Janis & Bob Mathis" <teammathis@onebox.com>,
|
||||||
|
"Sherry Bigham" <Bighams@lisd.net>,
|
||||||
|
"Mark Bigham" <bigham11@swbell.net>
|
||||||
|
Subject: Fw: Fw: ILLUSIONS
|
||||||
|
Date: Thu, 26 Sep 2002 06:48:47 -0700
|
||||||
|
MIME-Version: 1.0
|
||||||
|
X-Mailer: MSN Explorer 7.02.0005.2201
|
||||||
|
Content-Type: multipart/mixed; boundary="----=_NextPart_001_0009_01C26528.C39C68E0"
|
||||||
|
Message-ID: <OE142r27kicP3lh9uKw0000a7a1@hotmail.com>
|
||||||
|
X-OriginalArrivalTime: 30 Sep 2002 22:00:38.0335 (UTC) FILETIME=[CF7AE4F0:01C268CC]
|
||||||
|
X-IMAPbase: 1033583964 1
|
||||||
|
Status: RO
|
||||||
|
X-Status:
|
||||||
|
X-Keywords:
|
||||||
|
X-UID: 1
|
||||||
|
|
||||||
|
|
||||||
|
------=_NextPart_001_0009_01C26528.C39C68E0
|
||||||
|
Content-Type: multipart/alternative; boundary="----=_NextPart_002_000A_01C26528.C39C68E0"
|
||||||
|
|
||||||
|
|
||||||
|
------=_NextPart_002_000A_01C26528.C39C68E0
|
||||||
|
Content-Type: text/plain; charset="iso-8859-1"
|
||||||
|
Content-Transfer-Encoding: quoted-printable
|
||||||
|
|
||||||
|
Keep opening on the forwards. Cool =20
|
||||||
|
=20
|
||||||
|
----- Original Message -----
|
||||||
|
From: Got2Fish42@aol.com
|
||||||
|
Sent: Tuesday, September 24, 2002 3:16 PM
|
||||||
|
To: dugiew@cox-internet.com; txnrnt@yahoo.com; mbrock@tstar.net; DendyDl@=
|
||||||
|
swbell.net; sdickey@att.net; deasley@vzinet.com; fmmorrison@msn.com; mama=
|
||||||
|
jack4@juno.com; DMorr42886@aol.com; LStra415@aol.com; wrwebster@juno.com;=
|
||||||
|
GWIL@tjc.edu
|
||||||
|
Subject: Fwd: Fw: ILLUSIONS
|
||||||
|
Get more from the Web. FREE MSN Explorer download : http://explorer.msn=
|
||||||
|
.com
|
||||||
|
|
||||||
|
------=_NextPart_002_000A_01C26528.C39C68E0
|
||||||
|
Content-Type: text/html; charset="iso-8859-1"
|
||||||
|
Content-Transfer-Encoding: quoted-printable
|
||||||
|
|
||||||
|
<HTML><BODY STYLE=3D"font:10pt verdana; border:none;"><DIV>Keep opening o=
|
||||||
|
n the forwards. Cool </DIV> <DIV> </DIV> <BLOCKQUOTE styl=
|
||||||
|
e=3D"PADDING-RIGHT: 0px; PADDING-LEFT: 5px; MARGIN-LEFT: 5px; BORDER-LEFT=
|
||||||
|
: #000000 2px solid; MARGIN-RIGHT: 0px"> <DIV style=3D"FONT: 10pt Arial">=
|
||||||
|
----- Original Message -----</DIV> <DIV style=3D"BACKGROUND: #e4e4e4; FON=
|
||||||
|
T: 10pt Arial; COLOR: black"><B>From:</B> Got2Fish42@aol.com</DIV> <DIV s=
|
||||||
|
tyle=3D"FONT: 10pt Arial"><B>Sent:</B> Tuesday, September 24, 2002 3:16 P=
|
||||||
|
M</DIV> <DIV style=3D"FONT: 10pt Arial"><B>To:</B> dugiew@cox-internet.co=
|
||||||
|
m; txnrnt@yahoo.com; mbrock@tstar.net; DendyDl@swbell.net; sdickey@att.ne=
|
||||||
|
t; deasley@vzinet.com; fmmorrison@msn.com; mamajack4@juno.com; DMorr42886=
|
||||||
|
@aol.com; LStra415@aol.com; wrwebster@juno.com; GWIL@tjc.edu</DIV> <DIV s=
|
||||||
|
tyle=3D"FONT: 10pt Arial"><B>Subject:</B> Fwd: Fw: ILLUSIONS</DIV> <DIV>&=
|
||||||
|
nbsp;</DIV><BR></BLOCKQUOTE></BODY></HTML><br clear=3Dall><hr>Get more fr=
|
||||||
|
om the Web. FREE MSN Explorer download : <a href=3D'http://explorer.msn.=
|
||||||
|
com'>http://explorer.msn.com</a><br></p>
|
||||||
|
|
||||||
|
------=_NextPart_002_000A_01C26528.C39C68E0--
|
||||||
|
|
||||||
|
|
||||||
|
------=_NextPart_001_0009_01C26528.C39C68E0
|
||||||
|
Content-Type: message/rfc822; name="Fwd_ Fw_ ILLUSIONS.email"
|
||||||
|
Content-Disposition: attachment; filename="Fwd_ Fw_ ILLUSIONS.email"
|
||||||
|
Content-Transfer-Encoding: quoted-printable
|
||||||
|
|
||||||
|
Return-path: <Bclc48@aol.com>
|
||||||
|
From: Bclc48@aol.com
|
||||||
|
Full-name: Bclc48
|
||||||
|
Message-ID: <42.2de5cbf8.2ac10b39@aol.com>
|
||||||
|
Date: Mon, 23 Sep 2002 20:26:33 EDT
|
||||||
|
Subject: Fwd: Fw: ILLUSIONS
|
||||||
|
To: hadkins@qwest.net, Bardojm@aol.com, swa_tom@swbell.net,
|
||||||
|
eve@mixedmediaoutdoor.com, ArthurJaharris11@aol.com,
|
||||||
|
j.gual@worldnet.att.net, JOSEFUR@cs.com, AR2976@aol.com, CCcaro@aol.com,
|
||||||
|
Zgirlnan@aol.com, Got2Fish42@aol.com
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: multipart/mixed; boundary=3D"part2_46.2e38b118.2ac10b39_bou=
|
||||||
|
ndary"
|
||||||
|
X-Mailer: AOL 7.0 for Windows US sub 10641
|
||||||
|
|
||||||
|
|
||||||
|
--part2_46.2e38b118.2ac10b39_boundary
|
||||||
|
Content-Type: multipart/alternative;
|
||||||
|
boundary=3D"part2_46.2e38b118.2ac10b39_alt_boundary"
|
||||||
|
|
||||||
|
|
||||||
|
--part2_46.2e38b118.2ac10b39_alt_boundary
|
||||||
|
Content-Type: text/plain; charset=3D"US-ASCII"
|
||||||
|
Content-Transfer-Encoding: 7bit
|
||||||
|
|
||||||
|
this is good
|
||||||
|
|
||||||
|
--part2_46.2e38b118.2ac10b39_alt_boundary
|
||||||
|
Content-Type: text/html; charset=3D"US-ASCII"
|
||||||
|
Content-Transfer-Encoding: 7bit
|
||||||
|
|
||||||
|
<HTML><FONT FACE=3Darial,helvetica><BODY BGCOLOR=3D"#ffffff"><SCRIPT style=
|
||||||
|
=3D"BACKGROUND-COLOR: #ffffff" SIZE=3D2 FAMILY=3D"SANSSERIF" FACE=3D"Aria=
|
||||||
|
l" LANG=3D"0">this is good</SCRIPT></HTML>
|
||||||
|
|
||||||
|
--part2_46.2e38b118.2ac10b39_alt_boundary--
|
||||||
|
|
||||||
|
--part2_46.2e38b118.2ac10b39_boundary--
|
||||||
|
|
||||||
|
------=_NextPart_001_0009_01C26528.C39C68E0--
|
||||||
|
|
||||||
+72
@@ -0,0 +1,72 @@
|
|||||||
|
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
@@ -0,0 +1,127 @@
|
|||||||
|
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
@@ -0,0 +1,90 @@
|
|||||||
|
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
@@ -0,0 +1,50 @@
|
|||||||
|
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
@@ -0,0 +1,60 @@
|
|||||||
|
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
@@ -0,0 +1,38 @@
|
|||||||
|
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
@@ -0,0 +1,27 @@
|
|||||||
|
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
@@ -0,0 +1,62 @@
|
|||||||
|
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--
|
||||||
|
|
||||||
+299
@@ -0,0 +1,299 @@
|
|||||||
|
import unittest
|
||||||
|
import Milter
|
||||||
|
import bms
|
||||||
|
import mime
|
||||||
|
import rfc822
|
||||||
|
import StringIO
|
||||||
|
import email
|
||||||
|
import sys
|
||||||
|
#import pdb
|
||||||
|
|
||||||
|
class TestMilter(bms.bmsMilter):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
bms.bmsMilter.__init__(self)
|
||||||
|
self.logfp = open("test/milter.log","a")
|
||||||
|
self._delrcpt = [] # record deleted rcpts for testing
|
||||||
|
self._addrcpt = [] # record added rcpts for testing
|
||||||
|
|
||||||
|
def log(self,*msg):
|
||||||
|
for i in msg: print >>self.logfp, i,
|
||||||
|
print >>self.logfp
|
||||||
|
|
||||||
|
def getsymval(self,name):
|
||||||
|
if name == 'j': return 'test.milter.org'
|
||||||
|
return bms.bmsMilter.getsymval(self,name)
|
||||||
|
|
||||||
|
def replacebody(self,chunk):
|
||||||
|
if self._body:
|
||||||
|
self._body.write(chunk)
|
||||||
|
self.bodyreplaced = True
|
||||||
|
else:
|
||||||
|
raise IOError,"replacebody not called from eom()"
|
||||||
|
|
||||||
|
# FIXME: rfc822 indexing does not really reflect the way chg/add header
|
||||||
|
# work for a milter
|
||||||
|
def chgheader(self,field,idx,value):
|
||||||
|
if not self._body:
|
||||||
|
raise IOError,"chgheader not called from eom()"
|
||||||
|
self.log('chgheader: %s[%d]=%s' % (field,idx,value))
|
||||||
|
if value == '':
|
||||||
|
del self._msg[field]
|
||||||
|
else:
|
||||||
|
self._msg[field] = value
|
||||||
|
self.headerschanged = True
|
||||||
|
|
||||||
|
def addheader(self,field,value):
|
||||||
|
if not self._body:
|
||||||
|
raise IOError,"addheader not called from eom()"
|
||||||
|
self.log('addheader: %s=%s' % (field,value))
|
||||||
|
self._msg[field] = value
|
||||||
|
self.headerschanged = True
|
||||||
|
|
||||||
|
def delrcpt(self,rcpt):
|
||||||
|
if not self._body:
|
||||||
|
raise IOError,"delrcpt not called from eom()"
|
||||||
|
self._delrcpt.append(rcpt)
|
||||||
|
|
||||||
|
def addrcpt(self,rcpt):
|
||||||
|
if not self._body:
|
||||||
|
raise IOError,"addrcpt not called from eom()"
|
||||||
|
self._addrcpt.append(rcpt)
|
||||||
|
|
||||||
|
def setreply(self,rcode,xcode,msg):
|
||||||
|
self.reply = (rcode,xcode,msg)
|
||||||
|
|
||||||
|
def feedFile(self,fp,sender="spam@adv.com",rcpt="victim@lamb.com"):
|
||||||
|
self._body = None
|
||||||
|
self.bodyreplaced = False
|
||||||
|
self.headerschanged = False
|
||||||
|
self.reply = None
|
||||||
|
msg = rfc822.Message(fp)
|
||||||
|
rc = self.envfrom('<%s>'%sender)
|
||||||
|
if rc != Milter.CONTINUE: return rc
|
||||||
|
rc = self.envrcpt('<%s>'%rcpt)
|
||||||
|
if rc != Milter.CONTINUE: return rc
|
||||||
|
line = None
|
||||||
|
for h in msg.headers:
|
||||||
|
if h[:1].isspace():
|
||||||
|
line = line + h
|
||||||
|
continue
|
||||||
|
if not line:
|
||||||
|
line = h
|
||||||
|
continue
|
||||||
|
s = line.split(': ',1)
|
||||||
|
if len(s) > 1: val = s[1].strip()
|
||||||
|
else: val = ''
|
||||||
|
rc = self.header(s[0],val)
|
||||||
|
if rc != Milter.CONTINUE: return rc
|
||||||
|
line = h
|
||||||
|
if line:
|
||||||
|
s = line.split(': ',1)
|
||||||
|
rc = self.header(s[0],s[1])
|
||||||
|
if rc != Milter.CONTINUE: return rc
|
||||||
|
rc = self.eoh()
|
||||||
|
if rc != Milter.CONTINUE: return rc
|
||||||
|
while 1:
|
||||||
|
buf = fp.read(8192)
|
||||||
|
if len(buf) == 0: break
|
||||||
|
rc = self.body(buf)
|
||||||
|
if rc != Milter.CONTINUE: return rc
|
||||||
|
self._msg = msg
|
||||||
|
self._body = StringIO.StringIO()
|
||||||
|
rc = self.eom()
|
||||||
|
if self.bodyreplaced:
|
||||||
|
body = self._body.getvalue()
|
||||||
|
else:
|
||||||
|
msg.rewindbody()
|
||||||
|
body = msg.fp.read()
|
||||||
|
self._body = StringIO.StringIO()
|
||||||
|
self._body.writelines(msg.headers)
|
||||||
|
self._body.write('\n')
|
||||||
|
self._body.write(body)
|
||||||
|
return rc
|
||||||
|
|
||||||
|
def feedMsg(self,fname,sender="spam@adv.com",rcpt="victim@lamb.com"):
|
||||||
|
fp = open('test/'+fname,'r')
|
||||||
|
rc = self.feedFile(fp,sender,rcpt)
|
||||||
|
fp.close()
|
||||||
|
return rc
|
||||||
|
|
||||||
|
def connect(self,host='localhost'):
|
||||||
|
self._body = None
|
||||||
|
self.bodyreplaced = False
|
||||||
|
rc = bms.bmsMilter.connect(self,host,1,('1.2.3.4',1234))
|
||||||
|
if rc != Milter.CONTINUE and rc != Milter.ACCEPT:
|
||||||
|
self.close()
|
||||||
|
return rc
|
||||||
|
rc = self.hello('spamrelay')
|
||||||
|
if rc != Milter.CONTINUE:
|
||||||
|
self.close()
|
||||||
|
return rc
|
||||||
|
|
||||||
|
class BMSMilterTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
def testDefang(self,fname='virus1'):
|
||||||
|
milter = TestMilter()
|
||||||
|
rc = milter.connect('testDefang')
|
||||||
|
self.assertEqual(rc,Milter.CONTINUE)
|
||||||
|
rc = milter.feedMsg(fname)
|
||||||
|
self.assertEqual(rc,Milter.ACCEPT)
|
||||||
|
self.failUnless(milter.bodyreplaced,"Message body not replaced")
|
||||||
|
fp = milter._body
|
||||||
|
open('test/'+fname+".tstout","w").write(fp.getvalue())
|
||||||
|
#self.failUnless(fp.getvalue() == open("test/virus1.out","r").read())
|
||||||
|
fp.seek(0)
|
||||||
|
msg = mime.message_from_file(fp)
|
||||||
|
str = msg.get_payload(1).get_payload()
|
||||||
|
milter.log(str)
|
||||||
|
milter.close()
|
||||||
|
|
||||||
|
# test some spams that crashed our parser
|
||||||
|
def testParse(self,fname='spam7'):
|
||||||
|
milter = TestMilter()
|
||||||
|
milter.connect('testParse')
|
||||||
|
rc = milter.feedMsg(fname)
|
||||||
|
self.assertEqual(rc,Milter.ACCEPT)
|
||||||
|
self.failIf(milter.bodyreplaced,"Milter needlessly replaced body.")
|
||||||
|
fp = milter._body
|
||||||
|
open('test/'+fname+".tstout","w").write(fp.getvalue())
|
||||||
|
milter.connect('pro-send.com')
|
||||||
|
rc = milter.feedMsg('spam8')
|
||||||
|
self.assertEqual(rc,Milter.ACCEPT)
|
||||||
|
self.failIf(milter.bodyreplaced,"Milter needlessly replaced body.")
|
||||||
|
rc = milter.feedMsg('bounce')
|
||||||
|
self.assertEqual(rc,Milter.ACCEPT)
|
||||||
|
self.failIf(milter.bodyreplaced,"Milter needlessly replaced body.")
|
||||||
|
rc = milter.feedMsg('bounce1')
|
||||||
|
self.assertEqual(rc,Milter.ACCEPT)
|
||||||
|
self.failIf(milter.bodyreplaced,"Milter needlessly replaced body.")
|
||||||
|
milter.close()
|
||||||
|
|
||||||
|
def testDefang2(self):
|
||||||
|
milter = TestMilter()
|
||||||
|
milter.connect('testDefang2')
|
||||||
|
rc = milter.feedMsg('samp1')
|
||||||
|
self.assertEqual(rc,Milter.ACCEPT)
|
||||||
|
self.failIf(milter.bodyreplaced,"Milter needlessly replaced body.")
|
||||||
|
rc = milter.feedMsg("virus3")
|
||||||
|
self.assertEqual(rc,Milter.ACCEPT)
|
||||||
|
self.failUnless(milter.bodyreplaced,"Message body not replaced")
|
||||||
|
fp = milter._body
|
||||||
|
open("test/virus3.tstout","w").write(fp.getvalue())
|
||||||
|
#self.failUnless(fp.getvalue() == open("test/virus3.out","r").read())
|
||||||
|
rc = milter.feedMsg("virus6")
|
||||||
|
self.assertEqual(rc,Milter.ACCEPT)
|
||||||
|
self.failUnless(milter.bodyreplaced,"Message body not replaced")
|
||||||
|
self.failUnless(milter.headerschanged,"Message headers not adjusted")
|
||||||
|
fp = milter._body
|
||||||
|
open("test/virus6.tstout","w").write(fp.getvalue())
|
||||||
|
milter.close()
|
||||||
|
|
||||||
|
def testDefang3(self):
|
||||||
|
milter = TestMilter()
|
||||||
|
milter.connect('testDefang3')
|
||||||
|
# test script removal on complex HTML attachment
|
||||||
|
rc = milter.feedMsg('amazon')
|
||||||
|
self.assertEqual(rc,Milter.ACCEPT)
|
||||||
|
self.failUnless(milter.bodyreplaced,"Message body not replaced")
|
||||||
|
fp = milter._body
|
||||||
|
open("test/amazon.tstout","w").write(fp.getvalue())
|
||||||
|
# test defanging Klez virus
|
||||||
|
rc = milter.feedMsg("virus13")
|
||||||
|
self.assertEqual(rc,Milter.ACCEPT)
|
||||||
|
self.failUnless(milter.bodyreplaced,"Message body not replaced")
|
||||||
|
fp = milter._body
|
||||||
|
open("test/virus13.tstout","w").write(fp.getvalue())
|
||||||
|
# test script removal on quoted-printable HTML attachment
|
||||||
|
# sgmllib can't handle the <![if cond]> syntax
|
||||||
|
rc = milter.feedMsg('spam44')
|
||||||
|
self.assertEqual(rc,Milter.ACCEPT)
|
||||||
|
self.failIf(milter.bodyreplaced,"Message body replaced")
|
||||||
|
fp = milter._body
|
||||||
|
open("test/spam44.tstout","w").write(fp.getvalue())
|
||||||
|
milter.close()
|
||||||
|
|
||||||
|
def testRFC822(self):
|
||||||
|
milter = TestMilter()
|
||||||
|
milter.connect('testRFC822')
|
||||||
|
# test encoded rfc822 attachment
|
||||||
|
#pdb.set_trace()
|
||||||
|
rc = milter.feedMsg('test8')
|
||||||
|
self.assertEqual(rc,Milter.ACCEPT)
|
||||||
|
# python2.4 doesn't scan encoded message attachments
|
||||||
|
if sys.hexversion < 0x02040000:
|
||||||
|
self.failUnless(milter.bodyreplaced,"Message body not replaced")
|
||||||
|
#self.failIf(milter.bodyreplaced,"Message body replaced")
|
||||||
|
fp = milter._body
|
||||||
|
open("test/test8.tstout","w").write(fp.getvalue())
|
||||||
|
rc = milter.feedMsg('virus7')
|
||||||
|
self.assertEqual(rc,Milter.ACCEPT)
|
||||||
|
self.failUnless(milter.bodyreplaced,"Message body not replaced")
|
||||||
|
#self.failIf(milter.bodyreplaced,"Message body replaced")
|
||||||
|
fp = milter._body
|
||||||
|
open("test/virus7.tstout","w").write(fp.getvalue())
|
||||||
|
|
||||||
|
def testSmartAlias(self):
|
||||||
|
milter = TestMilter()
|
||||||
|
milter.connect('testSmartAlias')
|
||||||
|
# test smart alias feature
|
||||||
|
key = ('foo@bar.com','baz@bat.com')
|
||||||
|
bms.smart_alias[key] = ['ham@eggs.com']
|
||||||
|
rc = milter.feedMsg('test8',key[0],key[1])
|
||||||
|
self.assertEqual(rc,Milter.ACCEPT)
|
||||||
|
self.failUnless(milter._delrcpt == ['<baz@bat.com>'])
|
||||||
|
self.failUnless(milter._addrcpt == ['<ham@eggs.com>'])
|
||||||
|
# python2.4 email does not decode message attachments, so script
|
||||||
|
# is not replaced
|
||||||
|
if sys.hexversion < 0x02040000:
|
||||||
|
self.failUnless(milter.bodyreplaced,"Message body not replaced")
|
||||||
|
|
||||||
|
def testBadBoundary(self):
|
||||||
|
milter = TestMilter()
|
||||||
|
milter.connect('testBadBoundary')
|
||||||
|
# test rfc822 attachment with invalid boundaries
|
||||||
|
#pdb.set_trace()
|
||||||
|
rc = milter.feedMsg('bound')
|
||||||
|
if sys.hexversion < 0x02040000:
|
||||||
|
# python2.4 adds invalid boundaries to decects list and makes
|
||||||
|
# payload a str
|
||||||
|
self.assertEqual(rc,Milter.REJECT)
|
||||||
|
self.assertEqual(milter.reply[0],'554')
|
||||||
|
#self.failUnless(milter.bodyreplaced,"Message body not replaced")
|
||||||
|
self.failIf(milter.bodyreplaced,"Message body replaced")
|
||||||
|
fp = milter._body
|
||||||
|
open("test/bound.tstout","w").write(fp.getvalue())
|
||||||
|
|
||||||
|
def testCompoundFilename(self):
|
||||||
|
milter = TestMilter()
|
||||||
|
milter.connect('testCompoundFilename')
|
||||||
|
# test rfc822 attachment with invalid boundaries
|
||||||
|
#pdb.set_trace()
|
||||||
|
rc = milter.feedMsg('test1')
|
||||||
|
self.assertEqual(rc,Milter.ACCEPT)
|
||||||
|
#self.failUnless(milter.bodyreplaced,"Message body not replaced")
|
||||||
|
self.failIf(milter.bodyreplaced,"Message body replaced")
|
||||||
|
fp = milter._body
|
||||||
|
open("test/test1.tstout","w").write(fp.getvalue())
|
||||||
|
|
||||||
|
# def testReject(self):
|
||||||
|
# "Test content based spam rejection."
|
||||||
|
# milter = TestMilter()
|
||||||
|
# milter.connect('gogo-china.com')
|
||||||
|
# rc = milter.feedMsg('big5');
|
||||||
|
# self.failUnless(rc == Milter.REJECT)
|
||||||
|
# milter.close();
|
||||||
|
|
||||||
|
def suite(): return unittest.makeSuite(BMSMilterTestCase,'test')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
for fname in sys.argv[1:]:
|
||||||
|
milter = TestMilter()
|
||||||
|
milter.connect('main')
|
||||||
|
fp = open(fname,'r')
|
||||||
|
rc = milter.feedFile(fp)
|
||||||
|
fp = milter._body
|
||||||
|
sys.stdout.write(fp.getvalue())
|
||||||
|
else:
|
||||||
|
unittest.main()
|
||||||
+145
@@ -0,0 +1,145 @@
|
|||||||
|
# $Log$
|
||||||
|
# Revision 1.23 2005/02/11 18:34:14 stuart
|
||||||
|
# Handle garbage after quote in boundary.
|
||||||
|
#
|
||||||
|
# Revision 1.22 2005/02/10 01:10:59 stuart
|
||||||
|
# Fixed MimeMessage.ismodified()
|
||||||
|
#
|
||||||
|
# Revision 1.21 2005/02/10 00:56:49 stuart
|
||||||
|
# Runs with python2.4. Defang not working correctly - more work needed.
|
||||||
|
#
|
||||||
|
# Revision 1.20 2004/11/20 16:38:17 stuart
|
||||||
|
# Add rcs log
|
||||||
|
#
|
||||||
|
import unittest
|
||||||
|
import mime
|
||||||
|
import socket
|
||||||
|
import StringIO
|
||||||
|
import email
|
||||||
|
import sys
|
||||||
|
from email import Errors
|
||||||
|
|
||||||
|
samp1_txt1 = """Dear Agent 1
|
||||||
|
I hope you can read this. Whenever you write label it P.B.S kids.
|
||||||
|
Eliza doesn't know a thing about P.B.S kids. got to go by
|
||||||
|
agent one."""
|
||||||
|
|
||||||
|
hostname = socket.gethostname()
|
||||||
|
|
||||||
|
class MimeTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
# test mime parameter parsing
|
||||||
|
def testParam(self):
|
||||||
|
plist = mime._parseparam(
|
||||||
|
'; boundary="----=_NextPart_000_4e56_490d_48e3"')
|
||||||
|
self.failUnless(len(plist)==1)
|
||||||
|
self.failUnless(plist[0] == 'boundary="----=_NextPart_000_4e56_490d_48e3"')
|
||||||
|
plist = mime._parseparam('; name="Jim&amp;Girlz.jpg"')
|
||||||
|
self.failUnless(len(plist)==1)
|
||||||
|
self.failUnless(plist[0] == 'name="Jim&amp;Girlz.jpg"')
|
||||||
|
|
||||||
|
def testParse(self,fname='samp1'):
|
||||||
|
msg = mime.message_from_file(open('test/'+fname,"r"))
|
||||||
|
self.failUnless(msg.ismultipart())
|
||||||
|
parts = msg.get_payload()
|
||||||
|
self.failUnless(len(parts) == 2)
|
||||||
|
txt1 = parts[0].get_payload()
|
||||||
|
self.failUnless(txt1.rstrip() == samp1_txt1,txt1)
|
||||||
|
msg = mime.message_from_file(open('test/missingboundary',"r"))
|
||||||
|
# should get no exception as long as we don't try to parse
|
||||||
|
# message attachments
|
||||||
|
mime.defang(msg,scan_rfc822=False)
|
||||||
|
msg.dump(open('test/missingboundary.out','w'))
|
||||||
|
msg = mime.message_from_file(open('test/missingboundary',"r"))
|
||||||
|
try:
|
||||||
|
mime.defang(msg)
|
||||||
|
# python 2.4 doesn't get exceptions on missing boundaries, and
|
||||||
|
# if message is modified, output is readable by mail clients
|
||||||
|
if sys.hexversion < 0x02040000:
|
||||||
|
self.fail('should get boundary error parsing bad rfc822 attachment')
|
||||||
|
except Errors.BoundaryError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def testDefang(self,vname='virus1',part=1,
|
||||||
|
fname='LOVE-LETTER-FOR-YOU.TXT.vbs'):
|
||||||
|
msg = mime.message_from_file(open('test/'+vname,"r"))
|
||||||
|
mime.defang(msg)
|
||||||
|
self.failUnless(msg.ismodified(),"virus not removed")
|
||||||
|
oname = vname + '.out'
|
||||||
|
msg.dump(open('test/'+oname,"w"))
|
||||||
|
msg = mime.message_from_file(open('test/'+oname,"r"))
|
||||||
|
txt2 = msg.get_payload()
|
||||||
|
if type(txt2) == list:
|
||||||
|
txt2 = txt2[part].get_payload()
|
||||||
|
self.failUnless(txt2.rstrip()+'\n' == mime.virus_msg % (fname,hostname,None),txt2)
|
||||||
|
|
||||||
|
def testDefang3(self):
|
||||||
|
self.testDefang('virus3',0,'READER_DIGEST_LETTER.TXT.pif')
|
||||||
|
|
||||||
|
# virus4 does not include proper end boundary
|
||||||
|
def testDefang4(self):
|
||||||
|
self.testDefang('virus4',1,'readme.exe')
|
||||||
|
|
||||||
|
# virus5 is even more screwed up
|
||||||
|
def testDefang5(self):
|
||||||
|
self.testDefang('virus5',1,'whatever.exe')
|
||||||
|
|
||||||
|
# virus6 has no parts - the virus is directly inline
|
||||||
|
def testDefang6(self,vname="virus6",fname='FAX20.exe'):
|
||||||
|
msg = mime.message_from_file(open('test/'+vname,"r"))
|
||||||
|
mime.defang(msg)
|
||||||
|
oname = vname + '.out'
|
||||||
|
msg.dump(open('test/'+oname,"w"))
|
||||||
|
msg = mime.message_from_file(open('test/'+oname,"r"))
|
||||||
|
self.failIf(msg.ismultipart())
|
||||||
|
txt2 = msg.get_payload()
|
||||||
|
self.failUnless(txt2 == mime.virus_msg % \
|
||||||
|
(fname,hostname,None),txt2)
|
||||||
|
|
||||||
|
# honey virus has a sneaky ASP payload which is parsed correctly
|
||||||
|
# by email package in python-2.2.2, but not by mime.MimeMessage or 2.2.1
|
||||||
|
def testDefang7(self,vname="honey",fname='story[1].scr'):
|
||||||
|
msg = mime.message_from_file(open('test/'+vname,"r"))
|
||||||
|
mime.defang(msg)
|
||||||
|
oname = vname + '.out'
|
||||||
|
msg.dump(open('test/'+oname,"w"))
|
||||||
|
msg = mime.message_from_file(open('test/'+oname,"r"))
|
||||||
|
parts = msg.get_payload()
|
||||||
|
txt2 = parts[1].get_payload()
|
||||||
|
txt3 = parts[2].get_payload()
|
||||||
|
self.failUnless(txt2.rstrip()+'\n' == mime.virus_msg % \
|
||||||
|
(fname,hostname,None),txt2)
|
||||||
|
if txt3 != '':
|
||||||
|
self.failUnless(txt3.rstrip()+'\n' == mime.virus_msg % \
|
||||||
|
('story[1].asp',hostname,None),txt3)
|
||||||
|
|
||||||
|
def testParse2(self,fname="spam7"):
|
||||||
|
msg = mime.message_from_file(open('test/'+fname,"r"))
|
||||||
|
self.failUnless(msg.ismultipart())
|
||||||
|
parts = msg.get_payload()
|
||||||
|
self.failUnless(len(parts) == 2)
|
||||||
|
name = parts[1].getname()
|
||||||
|
self.failUnless(name == "Jim&amp;Girlz.jpg","name=%s"%name)
|
||||||
|
|
||||||
|
def testHTML(self,fname=""):
|
||||||
|
result = StringIO.StringIO()
|
||||||
|
filter = mime.HTMLScriptFilter(result)
|
||||||
|
msg = """<! Illegal declaration used as comment>
|
||||||
|
<![if conditional]> Optional SGML <![endif]>
|
||||||
|
<!-- Legal SGML comment -->
|
||||||
|
"""
|
||||||
|
script = "<script lang=javascript> Dangerous script </script>"
|
||||||
|
filter.feed(msg + script)
|
||||||
|
filter.close()
|
||||||
|
#print result.getvalue()
|
||||||
|
self.failUnless(result.getvalue() == msg + filter.msg)
|
||||||
|
|
||||||
|
def suite(): return unittest.makeSuite(MimeTestCase,'test')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
unittest.main()
|
||||||
|
else:
|
||||||
|
for fname in sys.argv[1:]:
|
||||||
|
fp = open(fname,'r')
|
||||||
|
msg = mime.message_from_file(fp)
|
||||||
+149
@@ -0,0 +1,149 @@
|
|||||||
|
import unittest
|
||||||
|
import Milter
|
||||||
|
import sample
|
||||||
|
import mime
|
||||||
|
import rfc822
|
||||||
|
import StringIO
|
||||||
|
|
||||||
|
class TestMilter(sample.sampleMilter):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.logfp = open("test/milter.log","a")
|
||||||
|
|
||||||
|
def log(self,*msg):
|
||||||
|
for i in msg: print >>self.logfp, i,
|
||||||
|
print >>self.logfp
|
||||||
|
|
||||||
|
def replacebody(self,chunk):
|
||||||
|
if self._body:
|
||||||
|
self._body.write(chunk)
|
||||||
|
self.bodyreplaced = True
|
||||||
|
else:
|
||||||
|
raise IOError,"replacebody not called from eom()"
|
||||||
|
|
||||||
|
# FIXME: rfc822 indexing does not really reflect the way chg/add header
|
||||||
|
# work for a milter
|
||||||
|
def chgheader(self,field,idx,value):
|
||||||
|
self.log('chgheader: %s[%d]=%s' % (field,idx,value))
|
||||||
|
if value == '':
|
||||||
|
del self._msg[field]
|
||||||
|
else:
|
||||||
|
self._msg[field] = value
|
||||||
|
self.headerschanged = True
|
||||||
|
|
||||||
|
def addheader(self,field,value):
|
||||||
|
self.log('addheader: %s=%s' % (field,value))
|
||||||
|
self._msg[field] = value
|
||||||
|
self.headerschanged = True
|
||||||
|
|
||||||
|
def feedMsg(self,fname):
|
||||||
|
self._body = None
|
||||||
|
self.bodyreplaced = False
|
||||||
|
self.headerschanged = 0
|
||||||
|
fp = open('test/'+fname,'r')
|
||||||
|
msg = rfc822.Message(fp)
|
||||||
|
rc = self.envfrom('<spam@advertisements.com>')
|
||||||
|
if rc != Milter.CONTINUE: return rc
|
||||||
|
rc = self.envrcpt('<victim@lamb.com>')
|
||||||
|
if rc != Milter.CONTINUE: return rc
|
||||||
|
line = None
|
||||||
|
for h in msg.headers:
|
||||||
|
if h[:1].isspace():
|
||||||
|
line = line + h
|
||||||
|
continue
|
||||||
|
if not line:
|
||||||
|
line = h
|
||||||
|
continue
|
||||||
|
s = line.split(': ',1)
|
||||||
|
rc = self.header(s[0],s[1].strip())
|
||||||
|
if rc != Milter.CONTINUE: return rc
|
||||||
|
line = h
|
||||||
|
if line:
|
||||||
|
s = line.split(': ',1)
|
||||||
|
rc = self.header(s[0],s[1])
|
||||||
|
if rc != Milter.CONTINUE: return rc
|
||||||
|
rc = self.eoh()
|
||||||
|
if rc != Milter.CONTINUE: return rc
|
||||||
|
while 1:
|
||||||
|
buf = fp.read(8192)
|
||||||
|
if len(buf) == 0: break
|
||||||
|
rc = self.body(buf)
|
||||||
|
if rc != Milter.CONTINUE: return rc
|
||||||
|
self._msg = msg
|
||||||
|
self._body = StringIO.StringIO()
|
||||||
|
rc = self.eom()
|
||||||
|
if self.bodyreplaced:
|
||||||
|
body = self._body.getvalue()
|
||||||
|
else:
|
||||||
|
msg.rewindbody()
|
||||||
|
body = msg.fp.read()
|
||||||
|
self._body = StringIO.StringIO()
|
||||||
|
self._body.writelines(msg.headers)
|
||||||
|
self._body.write('\n')
|
||||||
|
self._body.write(body)
|
||||||
|
return rc
|
||||||
|
|
||||||
|
def connect(self,host='localhost'):
|
||||||
|
self._body = None
|
||||||
|
self.bodyreplaced = False
|
||||||
|
rc = sample.sampleMilter.connect(self,host,1,0)
|
||||||
|
if rc != Milter.CONTINUE and rc != Milter.ACCEPT:
|
||||||
|
self.close()
|
||||||
|
return rc
|
||||||
|
rc = self.hello('spamrelay')
|
||||||
|
if rc != Milter.CONTINUE:
|
||||||
|
self.close()
|
||||||
|
return rc
|
||||||
|
|
||||||
|
class BMSMilterTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
def testDefang(self,fname='virus1'):
|
||||||
|
milter = TestMilter()
|
||||||
|
rc = milter.connect()
|
||||||
|
self.failUnless(rc == Milter.CONTINUE)
|
||||||
|
rc = milter.feedMsg(fname)
|
||||||
|
self.failUnless(rc == Milter.ACCEPT)
|
||||||
|
self.failUnless(milter.bodyreplaced,"Message body not replaced")
|
||||||
|
fp = milter._body
|
||||||
|
open('test/'+fname+".tstout","w").write(fp.getvalue())
|
||||||
|
#self.failUnless(fp.getvalue() == open("test/virus1.out","r").read())
|
||||||
|
fp.seek(0)
|
||||||
|
msg = mime.message_from_file(fp)
|
||||||
|
s = msg.get_payload(1).get_payload()
|
||||||
|
milter.log(s)
|
||||||
|
milter.close()
|
||||||
|
|
||||||
|
def testParse(self,fname='spam7'):
|
||||||
|
milter = TestMilter()
|
||||||
|
milter.connect('somehost')
|
||||||
|
rc = milter.feedMsg(fname)
|
||||||
|
self.failUnless(rc == Milter.ACCEPT)
|
||||||
|
self.failIf(milter.bodyreplaced,"Milter needlessly replaced body.")
|
||||||
|
fp = milter._body
|
||||||
|
open('test/'+fname+".tstout","w").write(fp.getvalue())
|
||||||
|
milter.close()
|
||||||
|
|
||||||
|
def testDefang2(self):
|
||||||
|
milter = TestMilter()
|
||||||
|
milter.connect('somehost')
|
||||||
|
rc = milter.feedMsg('samp1')
|
||||||
|
self.failUnless(rc == Milter.ACCEPT)
|
||||||
|
self.failIf(milter.bodyreplaced,"Milter needlessly replaced body.")
|
||||||
|
rc = milter.feedMsg("virus3")
|
||||||
|
self.failUnless(rc == Milter.ACCEPT)
|
||||||
|
self.failUnless(milter.bodyreplaced,"Message body not replaced")
|
||||||
|
fp = milter._body
|
||||||
|
open("test/virus3.tstout","w").write(fp.getvalue())
|
||||||
|
#self.failUnless(fp.getvalue() == open("test/virus3.out","r").read())
|
||||||
|
rc = milter.feedMsg("virus6")
|
||||||
|
self.failUnless(rc == Milter.ACCEPT)
|
||||||
|
self.failUnless(milter.bodyreplaced,"Message body not replaced")
|
||||||
|
self.failUnless(milter.headerschanged,"Message headers not adjusted")
|
||||||
|
fp = milter._body
|
||||||
|
open("test/virus6.tstout","w").write(fp.getvalue())
|
||||||
|
milter.close()
|
||||||
|
|
||||||
|
def suite(): return unittest.makeSuite(BMSMilterTestCase,'test')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user