Compare commits
1 Commits
master
...
dynamic_li
Author | SHA1 | Date | |
---|---|---|---|
![]() |
87fc340f27 |
@ -1,13 +0,0 @@
|
|||||||
# The "checkoutlist" file is used to support additional version controlled
|
|
||||||
# administrative files in $CVSROOT/CVSROOT, such as template files.
|
|
||||||
#
|
|
||||||
# The first entry on a line is a filename which will be checked out from
|
|
||||||
# the corresponding RCS file in the $CVSROOT/CVSROOT directory.
|
|
||||||
# The remainder of the line is an error message to use if the file cannot
|
|
||||||
# be checked out.
|
|
||||||
#
|
|
||||||
# File format:
|
|
||||||
#
|
|
||||||
# [<whitespace>]<filename>[<whitespace><error message>]<end-of-line>
|
|
||||||
#
|
|
||||||
# comment lines begin with '#'
|
|
@ -1,15 +0,0 @@
|
|||||||
# The "commitinfo" file is used to control pre-commit checks.
|
|
||||||
# The filter on the right is invoked with the repository and a list
|
|
||||||
# of files to check. A non-zero exit of the filter program will
|
|
||||||
# cause the commit to be aborted.
|
|
||||||
#
|
|
||||||
# The first entry on a line is a regular expression which is tested
|
|
||||||
# against the directory that the change is being committed to, relative
|
|
||||||
# to the $CVSROOT. For the first match that is found, then the remainder
|
|
||||||
# of the line is the name of the filter to run.
|
|
||||||
#
|
|
||||||
# If the repository name does not match any of the regular expressions in this
|
|
||||||
# file, the "DEFAULT" line is used, if it is specified.
|
|
||||||
#
|
|
||||||
# If the name "ALL" appears as a regular expression it is always used
|
|
||||||
# in addition to the first matching regex or "DEFAULT".
|
|
@ -1,21 +0,0 @@
|
|||||||
# Set this to "no" if pserver shouldn't check system users/passwords
|
|
||||||
#SystemAuth=no
|
|
||||||
|
|
||||||
# Put CVS lock files in this directory rather than directly in the repository.
|
|
||||||
#LockDir=/var/lock/cvs
|
|
||||||
|
|
||||||
# Set `TopLevelAdmin' to `yes' to create a CVS directory at the top
|
|
||||||
# level of the new working directory when using the `cvs checkout'
|
|
||||||
# command.
|
|
||||||
#TopLevelAdmin=no
|
|
||||||
|
|
||||||
# Set `LogHistory' to `all' or `TOEFWUPCGMAR' to log all transactions to the
|
|
||||||
# history file, or a subset as needed (ie `TMAR' logs all write operations)
|
|
||||||
#LogHistory=TOEFWUPCGMAR
|
|
||||||
|
|
||||||
# Set `RereadLogAfterVerify' to `always' (the default) to allow the verifymsg
|
|
||||||
# script to change the log message. Set it to `stat' to force CVS to verify# that the file has changed before reading it (this can take up to an extra
|
|
||||||
# second per directory being committed, so it is not recommended for large
|
|
||||||
# repositories. Set it to `never' (the previous CVS behavior) to prevent
|
|
||||||
# verifymsg scripts from changing the log message.
|
|
||||||
#RereadLogAfterVerify=always
|
|
@ -1,19 +0,0 @@
|
|||||||
# This file affects handling of files based on their names.
|
|
||||||
#
|
|
||||||
# The -m option specifies whether CVS attempts to merge files.
|
|
||||||
#
|
|
||||||
# The -k option specifies keyword expansion (e.g. -kb for binary).
|
|
||||||
#
|
|
||||||
# Format of wrapper file ($CVSROOT/CVSROOT/cvswrappers or .cvswrappers)
|
|
||||||
#
|
|
||||||
# wildcard [option value][option value]...
|
|
||||||
#
|
|
||||||
# where option is one of
|
|
||||||
# -f from cvs filter value: path to filter
|
|
||||||
# -t to cvs filter value: path to filter
|
|
||||||
# -m update methodology value: MERGE or COPY
|
|
||||||
# -k expansion mode value: b, o, kkv, &c
|
|
||||||
#
|
|
||||||
# and value is a single-quote delimited value.
|
|
||||||
# For example:
|
|
||||||
#*.gif -k 'b'
|
|
@ -1,21 +0,0 @@
|
|||||||
# The "editinfo" file is used to allow verification of logging
|
|
||||||
# information. It works best when a template (as specified in the
|
|
||||||
# rcsinfo file) is provided for the logging procedure. Given a
|
|
||||||
# template with locations for, a bug-id number, a list of people who
|
|
||||||
# reviewed the code before it can be checked in, and an external
|
|
||||||
# process to catalog the differences that were code reviewed, the
|
|
||||||
# following test can be applied to the code:
|
|
||||||
#
|
|
||||||
# Making sure that the entered bug-id number is correct.
|
|
||||||
# Validating that the code that was reviewed is indeed the code being
|
|
||||||
# checked in (using the bug-id number or a seperate review
|
|
||||||
# number to identify this particular code set.).
|
|
||||||
#
|
|
||||||
# If any of the above test failed, then the commit would be aborted.
|
|
||||||
#
|
|
||||||
# Actions such as mailing a copy of the report to each reviewer are
|
|
||||||
# better handled by an entry in the loginfo file.
|
|
||||||
#
|
|
||||||
# One thing that should be noted is the the ALL keyword is not
|
|
||||||
# supported. There can be only one entry that matches a given
|
|
||||||
# repository.
|
|
@ -1,27 +0,0 @@
|
|||||||
# The "loginfo" file controls where "cvs commit" log information
|
|
||||||
# is sent. The first entry on a line is a regular expression which must match
|
|
||||||
# the directory that the change is being made to, relative to the
|
|
||||||
# $CVSROOT. If a match is found, then the remainder of the line is a filter
|
|
||||||
# program that should expect log information on its standard input.
|
|
||||||
#
|
|
||||||
# If the repository name does not match any of the regular expressions in this
|
|
||||||
# file, the "DEFAULT" line is used, if it is specified.
|
|
||||||
#
|
|
||||||
# If the name ALL appears as a regular expression it is always used
|
|
||||||
# in addition to the first matching regex or DEFAULT.
|
|
||||||
#
|
|
||||||
# You may specify a format string as part of the
|
|
||||||
# filter. The string is composed of a `%' followed
|
|
||||||
# by a single format character, or followed by a set of format
|
|
||||||
# characters surrounded by `{' and `}' as separators. The format
|
|
||||||
# characters are:
|
|
||||||
#
|
|
||||||
# s = file name
|
|
||||||
# V = old version number (pre-checkin)
|
|
||||||
# v = new version number (post-checkin)
|
|
||||||
# t = tag or branch name
|
|
||||||
#
|
|
||||||
# For example:
|
|
||||||
#DEFAULT (echo ""; id; echo %s; date; cat) >> $CVSROOT/CVSROOT/commitlog
|
|
||||||
# or
|
|
||||||
#DEFAULT (echo ""; id; echo %{sVv}; date; cat) >> $CVSROOT/CVSROOT/commitlog
|
|
@ -1,26 +0,0 @@
|
|||||||
# Three different line formats are valid:
|
|
||||||
# key -a aliases...
|
|
||||||
# key [options] directory
|
|
||||||
# key [options] directory files...
|
|
||||||
#
|
|
||||||
# Where "options" are composed of:
|
|
||||||
# -i prog Run "prog" on "cvs commit" from top-level of module.
|
|
||||||
# -o prog Run "prog" on "cvs checkout" of module.
|
|
||||||
# -e prog Run "prog" on "cvs export" of module.
|
|
||||||
# -t prog Run "prog" on "cvs rtag" of module.
|
|
||||||
# -u prog Run "prog" on "cvs update" of module.
|
|
||||||
# -d dir Place module in directory "dir" instead of module name.
|
|
||||||
# -l Top-level directory only -- do not recurse.
|
|
||||||
#
|
|
||||||
# NOTE: If you change any of the "Run" options above, you'll have to
|
|
||||||
# release and re-checkout any working directories of these modules.
|
|
||||||
#
|
|
||||||
# And "directory" is a path to a directory relative to $CVSROOT.
|
|
||||||
#
|
|
||||||
# The "-a" option specifies an alias. An alias is interpreted as if
|
|
||||||
# everything on the right of the "-a" had been typed on the command line.
|
|
||||||
#
|
|
||||||
# You can encode a module within a module by using the special '&'
|
|
||||||
# character to interpose another module into the current module. This
|
|
||||||
# can be useful for creating a module that consists of many directories
|
|
||||||
# spread out over the entire source repository.
|
|
@ -1,12 +0,0 @@
|
|||||||
# The "notify" file controls where notifications from watches set by
|
|
||||||
# "cvs watch add" or "cvs edit" are sent. The first entry on a line is
|
|
||||||
# a regular expression which is tested against the directory that the
|
|
||||||
# change is being made to, relative to the $CVSROOT. If it matches,
|
|
||||||
# then the remainder of the line is a filter program that should contain
|
|
||||||
# one occurrence of %s for the user to notify, and information on its
|
|
||||||
# standard input.
|
|
||||||
#
|
|
||||||
# "ALL" or "DEFAULT" can be used in place of the regular expression.
|
|
||||||
#
|
|
||||||
# For example:
|
|
||||||
#ALL mail -s "CVS notification" %s
|
|
@ -1,13 +0,0 @@
|
|||||||
# The "rcsinfo" file is used to control templates with which the editor
|
|
||||||
# is invoked on commit and import.
|
|
||||||
#
|
|
||||||
# The first entry on a line is a regular expression which is tested
|
|
||||||
# against the directory that the change is being made to, relative to the
|
|
||||||
# $CVSROOT. For the first match that is found, then the remainder of the
|
|
||||||
# line is the name of the file that contains the template.
|
|
||||||
#
|
|
||||||
# If the repository name does not match any of the regular expressions in this
|
|
||||||
# file, the "DEFAULT" line is used, if it is specified.
|
|
||||||
#
|
|
||||||
# If the name "ALL" appears as a regular expression it is always used
|
|
||||||
# in addition to the first matching regex or "DEFAULT".
|
|
@ -1,20 +0,0 @@
|
|||||||
# The "taginfo" file is used to control pre-tag checks.
|
|
||||||
# The filter on the right is invoked with the following arguments:
|
|
||||||
#
|
|
||||||
# $1 -- tagname
|
|
||||||
# $2 -- operation "add" for tag, "mov" for tag -F, and "del" for tag -d
|
|
||||||
# $3 -- repository
|
|
||||||
# $4-> file revision [file revision ...]
|
|
||||||
#
|
|
||||||
# A non-zero exit of the filter program will cause the tag to be aborted.
|
|
||||||
#
|
|
||||||
# The first entry on a line is a regular expression which is tested
|
|
||||||
# against the directory that the change is being committed to, relative
|
|
||||||
# to the $CVSROOT. For the first match that is found, then the remainder
|
|
||||||
# of the line is the name of the filter to run.
|
|
||||||
#
|
|
||||||
# If the repository name does not match any of the regular expressions in this
|
|
||||||
# file, the "DEFAULT" line is used, if it is specified.
|
|
||||||
#
|
|
||||||
# If the name "ALL" appears as a regular expression it is always used
|
|
||||||
# in addition to the first matching regex or "DEFAULT".
|
|
@ -1,21 +0,0 @@
|
|||||||
# The "verifymsg" file is used to allow verification of logging
|
|
||||||
# information. It works best when a template (as specified in the
|
|
||||||
# rcsinfo file) is provided for the logging procedure. Given a
|
|
||||||
# template with locations for, a bug-id number, a list of people who
|
|
||||||
# reviewed the code before it can be checked in, and an external
|
|
||||||
# process to catalog the differences that were code reviewed, the
|
|
||||||
# following test can be applied to the code:
|
|
||||||
#
|
|
||||||
# Making sure that the entered bug-id number is correct.
|
|
||||||
# Validating that the code that was reviewed is indeed the code being
|
|
||||||
# checked in (using the bug-id number or a seperate review
|
|
||||||
# number to identify this particular code set.).
|
|
||||||
#
|
|
||||||
# If any of the above test failed, then the commit would be aborted.
|
|
||||||
#
|
|
||||||
# Actions such as mailing a copy of the report to each reviewer are
|
|
||||||
# better handled by an entry in the loginfo file.
|
|
||||||
#
|
|
||||||
# One thing that should be noted is the the ALL keyword is not
|
|
||||||
# supported. There can be only one entry that matches a given
|
|
||||||
# repository.
|
|
@ -1,37 +0,0 @@
|
|||||||
import threading
|
|
||||||
import time
|
|
||||||
|
|
||||||
from Logging import *
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Cache(object):
|
|
||||||
def __init__(self, expiration):
|
|
||||||
self.lock = threading.Lock()
|
|
||||||
self.expiration = expiration
|
|
||||||
self.cache = {}
|
|
||||||
|
|
||||||
def put(self, key, value):
|
|
||||||
self.lock.acquire()
|
|
||||||
self.cache[key] = (time.time(), value)
|
|
||||||
debug("cache.put(%s, %s)" % (str(key), str(value)))
|
|
||||||
self.lock.release()
|
|
||||||
|
|
||||||
def get(self, key):
|
|
||||||
try:
|
|
||||||
self.lock.acquire()
|
|
||||||
debug("cache.get(%s)" % str(key))
|
|
||||||
try:
|
|
||||||
timestamp, value = self.cache[key]
|
|
||||||
debug("cache.get found: %s" % value)
|
|
||||||
if (timestamp + self.expiration) < time.time():
|
|
||||||
debug("cache.get: expired")
|
|
||||||
del self.cache[key]
|
|
||||||
raise KeyError
|
|
||||||
return value
|
|
||||||
except KeyError:
|
|
||||||
debug("cache.get: found nothing")
|
|
||||||
return None
|
|
||||||
finally:
|
|
||||||
self.lock.release()
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
|||||||
from Logging import *
|
|
||||||
|
|
||||||
from SendmailSocketMapHandler import smmapBaseHandlerWorker
|
|
||||||
from SendmailSocketMapHandler import smmapBaseHandlerContainer
|
|
||||||
from SendmailSocketMapHandler import MyPermanentVerifierException
|
|
||||||
from SendmailSocketMapHandler import MyTemporaryVerifierException
|
|
||||||
|
|
||||||
from VerifierHandler import MySMTP
|
|
||||||
|
|
||||||
class MyLMTP(MySMTP):
|
|
||||||
def lhlo(self, param):
|
|
||||||
return self.docmd("lhlo " + param)
|
|
||||||
|
|
||||||
class CyrusCheckerWorker(smmapBaseHandlerWorker):
|
|
||||||
OK = "OK"
|
|
||||||
NOK = "NOK"
|
|
||||||
TEMPNOK = "TEMPNOK"
|
|
||||||
|
|
||||||
def execute(self, data):
|
|
||||||
debug("data " + data)
|
|
||||||
|
|
||||||
host, address = data.split('|')
|
|
||||||
|
|
||||||
debug("host: (%s), address: (%s)" % (host, address))
|
|
||||||
|
|
||||||
return "<OK>"
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
|||||||
import syslog
|
|
||||||
|
|
||||||
|
|
||||||
config = None
|
|
||||||
|
|
||||||
def log(data):
|
|
||||||
syslog.syslog(syslog.LOG_INFO, data)
|
|
||||||
|
|
||||||
def debug(data):
|
|
||||||
syslog.syslog(syslog.LOG_DEBUG, data)
|
|
||||||
|
|
||||||
def openlog(c):
|
|
||||||
config = c
|
|
||||||
syslog.openlog(config.get('Logging', 'ApplID'), syslog.LOG_PID, syslog.LOG_MAIL)
|
|
@ -1,8 +0,0 @@
|
|||||||
Cache.py
|
|
||||||
Logging.py
|
|
||||||
SendmailSocketMapHandler.py
|
|
||||||
VerifierHandler.py
|
|
||||||
setup.py
|
|
||||||
smmapd
|
|
||||||
smmapd.ini
|
|
||||||
verifysender.m4
|
|
@ -1,190 +0,0 @@
|
|||||||
import SocketServer
|
|
||||||
import time
|
|
||||||
|
|
||||||
from Logging import *
|
|
||||||
|
|
||||||
|
|
||||||
class NetStringError(ValueError): pass
|
|
||||||
|
|
||||||
def NetStringDecode(s):
|
|
||||||
try:
|
|
||||||
length, data = s.split(':')
|
|
||||||
except ValueError:
|
|
||||||
raise NetStringError, "Separator not found"
|
|
||||||
try:
|
|
||||||
length = int(length)
|
|
||||||
except ValueError:
|
|
||||||
raise NetStringError, "Can not read length"
|
|
||||||
if len(data) != length+1:
|
|
||||||
raise NetStringError, "Data has unexpected length"
|
|
||||||
if data[-1] != ',':
|
|
||||||
raise NetStringError, "End-delimiter not found"
|
|
||||||
return data[:-1]
|
|
||||||
|
|
||||||
def NetStringEncode(s):
|
|
||||||
return str(len(s)) + ":" + s + ","
|
|
||||||
|
|
||||||
|
|
||||||
class MyPermanentVerifierException(ValueError): pass
|
|
||||||
|
|
||||||
class MyTemporaryVerifierException(ValueError): pass
|
|
||||||
|
|
||||||
class MyBaseRequestHandler(SocketServer.BaseRequestHandler):
|
|
||||||
def handle(self):
|
|
||||||
debug("Connected from " + str(self.client_address))
|
|
||||||
self.localSetup()
|
|
||||||
while 1:
|
|
||||||
receivedData = self.request.recv(8192)
|
|
||||||
if (receivedData == None) or (len(receivedData) == 0): break
|
|
||||||
debug("Data: (%s)" % receivedData)
|
|
||||||
self.request.sendall(self.process(receivedData))
|
|
||||||
self.request.close();
|
|
||||||
self.localFinish()
|
|
||||||
debug("Disconnected")
|
|
||||||
|
|
||||||
def process(self, data):
|
|
||||||
debug("MyBaseRequestHandler.process")
|
|
||||||
return data
|
|
||||||
|
|
||||||
def localSetup(self): pass
|
|
||||||
|
|
||||||
def localfinish(self): pass
|
|
||||||
|
|
||||||
|
|
||||||
class SendmailAdaptor:
|
|
||||||
PERM = "PERM "
|
|
||||||
OK = "OK "
|
|
||||||
NOTFOUND = "NOTFOUND "
|
|
||||||
TEMP = "TEMP "
|
|
||||||
|
|
||||||
def preProcess(self, data):
|
|
||||||
try:
|
|
||||||
data = NetStringDecode(data)
|
|
||||||
klass, data = data.split(' ')
|
|
||||||
return klass, data
|
|
||||||
except NetStringError, arg:
|
|
||||||
raise MyPermanentVerifierException, arg
|
|
||||||
except ValueError:
|
|
||||||
raise MyPermanentVerifierException, "<class> <data> expected, only one found"
|
|
||||||
|
|
||||||
def postProcess(self, data):
|
|
||||||
return NetStringEncode(data)
|
|
||||||
|
|
||||||
def process(self, data):
|
|
||||||
startTime = time.time()
|
|
||||||
try:
|
|
||||||
klass, data2 = self.preProcess(data)
|
|
||||||
arg = self.execute(klass, data2)
|
|
||||||
code = SendmailAdaptor.OK
|
|
||||||
except MyPermanentVerifierException, arg:
|
|
||||||
code, arg = SendmailAdaptor.PERM, str(arg)
|
|
||||||
except MyTemporaryVerifierException, arg:
|
|
||||||
code, arg = SendmailAdaptor.TEMP, str(arg)
|
|
||||||
endTime = time.time()
|
|
||||||
log("Class: %s, Data: %s, Code: %s, Arg: %s, Delay: %f" % (klass, data2, code, arg, endTime-startTime))
|
|
||||||
return self.postProcess(code + arg)
|
|
||||||
|
|
||||||
def execute(self, data):
|
|
||||||
return data
|
|
||||||
|
|
||||||
class NullAdaptor(SendmailAdaptor):
|
|
||||||
def preProcess(self, data):
|
|
||||||
return re.compile(r'^(.*?)[\r\n]{1,2}$').match(data).group(1)
|
|
||||||
|
|
||||||
def postProcess(self, data):
|
|
||||||
return data + "\n"
|
|
||||||
|
|
||||||
|
|
||||||
class SendmailDispatcher(SendmailAdaptor, MyBaseRequestHandler):
|
|
||||||
pluginContainerObjects = {}
|
|
||||||
|
|
||||||
|
|
||||||
def registerAll(config):
|
|
||||||
for section in config.get('Daemon', 'Plugins').split(','):
|
|
||||||
SendmailDispatcher.register(section, config)
|
|
||||||
|
|
||||||
registerAll = staticmethod(registerAll)
|
|
||||||
|
|
||||||
|
|
||||||
def register(section, config):
|
|
||||||
cfg = Config(section, config)
|
|
||||||
className = cfg.get('ContainerClass')
|
|
||||||
moduleName = cfg.get('ContainerModule')
|
|
||||||
if className == None:
|
|
||||||
className = 'smmapBaseHandlerContainer'
|
|
||||||
else:
|
|
||||||
if moduleName == None:
|
|
||||||
moduleName == className
|
|
||||||
m = __import__(moduleName)
|
|
||||||
log("Registering %s, %s" % (section, className))
|
|
||||||
klass = eval("m.%s" % className)
|
|
||||||
containerObject = klass(cfg)
|
|
||||||
containerObject.setup()
|
|
||||||
SendmailDispatcher.pluginContainerObjects[section] = containerObject
|
|
||||||
|
|
||||||
register = staticmethod(register)
|
|
||||||
|
|
||||||
|
|
||||||
def localSetup(self):
|
|
||||||
self.pluginWorkerObjects = {}
|
|
||||||
|
|
||||||
def localFinish(self):
|
|
||||||
for o in self.pluginWorkerObjects.values():
|
|
||||||
o.finish()
|
|
||||||
|
|
||||||
def execute(self, klass, data):
|
|
||||||
if not self.pluginContainerObjects.has_key(klass):
|
|
||||||
raise MyPermanentVerifierException, "Class %s not implemented" % klass
|
|
||||||
elif not self.pluginWorkerObjects.has_key(klass):
|
|
||||||
debug("Instantiate worker %s" % klass)
|
|
||||||
self.pluginWorkerObjects[klass] = self.pluginContainerObjects[klass].getWorker()
|
|
||||||
|
|
||||||
return self.pluginWorkerObjects[klass].execute(data)
|
|
||||||
|
|
||||||
|
|
||||||
class Config(object):
|
|
||||||
def __init__(self, section, config):
|
|
||||||
self.section = section
|
|
||||||
self.config = config
|
|
||||||
|
|
||||||
def getSection(self):
|
|
||||||
return self.section
|
|
||||||
|
|
||||||
def get(self, item):
|
|
||||||
return self.config.get(self.section, item)
|
|
||||||
|
|
||||||
|
|
||||||
class smmapBaseHandlerWorker(object):
|
|
||||||
def __init__(self, container):
|
|
||||||
self.container = container
|
|
||||||
|
|
||||||
def setup(self): pass
|
|
||||||
|
|
||||||
def finish(self): pass
|
|
||||||
|
|
||||||
def execute(self, data):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
class smmapBaseHandlerContainer(object):
|
|
||||||
def __init__(self, cfg):
|
|
||||||
self.config = cfg
|
|
||||||
workerClassName = cfg.get('WorkerClass')
|
|
||||||
workerModuleName = cfg.get('WorkerModule')
|
|
||||||
if workerModuleName == None:
|
|
||||||
workerModuleName = workerClassName
|
|
||||||
m = __import__(workerModuleName)
|
|
||||||
self.workerClass = eval("m.%s" % workerClassName)
|
|
||||||
|
|
||||||
def setup(self): pass
|
|
||||||
|
|
||||||
def finish(self): pass
|
|
||||||
|
|
||||||
def getWorker(self):
|
|
||||||
worker = self.workerClass(self)
|
|
||||||
worker.setup()
|
|
||||||
return worker
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,240 +0,0 @@
|
|||||||
import threading
|
|
||||||
import socket
|
|
||||||
import Queue
|
|
||||||
import re
|
|
||||||
import time
|
|
||||||
|
|
||||||
#import timeoutsocket
|
|
||||||
import DNS
|
|
||||||
|
|
||||||
from Logging import *
|
|
||||||
|
|
||||||
from Cache import Cache
|
|
||||||
|
|
||||||
from SendmailSocketMapHandler import smmapBaseHandlerWorker
|
|
||||||
from SendmailSocketMapHandler import smmapBaseHandlerContainer
|
|
||||||
from SendmailSocketMapHandler import MyPermanentVerifierException
|
|
||||||
from SendmailSocketMapHandler import MyTemporaryVerifierException
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class VerifierHandlerContainer(smmapBaseHandlerContainer):
|
|
||||||
def setup(self):
|
|
||||||
DNS.ParseResolvConf()
|
|
||||||
if self.config.get('EnableCaching').lower() in ('true', 'yes', '1'):
|
|
||||||
debug("enabling cache")
|
|
||||||
self.cache = Cache(int(self.config.get('CacheExpiration')))
|
|
||||||
else:
|
|
||||||
debug("disabling cache")
|
|
||||||
self.cache = None
|
|
||||||
|
|
||||||
|
|
||||||
class VerifierHandlerWorker(smmapBaseHandlerWorker):
|
|
||||||
OK = "OK"
|
|
||||||
NOK = "NOK"
|
|
||||||
TEMPNOK = "TEMPNOK"
|
|
||||||
|
|
||||||
def setup(self):
|
|
||||||
self.zombies = []
|
|
||||||
|
|
||||||
class checker(threading.Thread):
|
|
||||||
def __init__(self, ready, config, host, address):
|
|
||||||
threading.Thread.__init__(self)
|
|
||||||
self.ready = ready
|
|
||||||
self.config = config
|
|
||||||
self.host = host
|
|
||||||
self.address = address
|
|
||||||
|
|
||||||
def checkAddressAvailability(self):
|
|
||||||
try:
|
|
||||||
debug("Trying " + self.host)
|
|
||||||
s = MySMTP(self.host, float(self.config.get('SMTPTimeOut')))
|
|
||||||
s.helo(self.config.get('SMTPHeloParam'))
|
|
||||||
s.mail(self.config.get('SMTPCheckSender'))
|
|
||||||
s.rcpt(self.address.getAddress())
|
|
||||||
s.quit()
|
|
||||||
result = VerifierHandlerWorker.OK
|
|
||||||
except MySMTPTemporaryException:
|
|
||||||
result = VerifierHandlerWorker.TEMPNOK
|
|
||||||
except MySMTPPermanentException:
|
|
||||||
result = VerifierHandlerWorker.NOK
|
|
||||||
except socket.timeout:
|
|
||||||
result = VerifierHandlerWorker.TEMPNOK
|
|
||||||
except socket.error:
|
|
||||||
result = VerifierHandlerWorker.TEMPNOK
|
|
||||||
return result
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
self.result = self.checkAddressAvailability()
|
|
||||||
self.ready.put(self.getName())
|
|
||||||
debug("NOTIFIED Host %s, Result %s" % (self.host, self.result))
|
|
||||||
|
|
||||||
def getResult(self):
|
|
||||||
return self.result
|
|
||||||
|
|
||||||
def getHost(self):
|
|
||||||
return self.host
|
|
||||||
|
|
||||||
def getAddress(self):
|
|
||||||
return self.address
|
|
||||||
|
|
||||||
def checkAvailability(self, mxes, address):
|
|
||||||
ready = Queue.Queue()
|
|
||||||
checkerThreads = {}
|
|
||||||
for m in mxes:
|
|
||||||
checkerThread = VerifierHandlerWorker.checker(ready, self.container.config, m, address)
|
|
||||||
checkerThread.start()
|
|
||||||
checkerThreads[checkerThread.getName()] = checkerThread
|
|
||||||
|
|
||||||
result = VerifierHandlerWorker.TEMPNOK
|
|
||||||
while 1:
|
|
||||||
debug("%i threads left" % len(checkerThreads))
|
|
||||||
if len(checkerThreads) == 0:
|
|
||||||
debug("no threads left ...")
|
|
||||||
break
|
|
||||||
if result != VerifierHandlerWorker.TEMPNOK:
|
|
||||||
debug("got a permanent result ...")
|
|
||||||
break
|
|
||||||
debug("Waiting for results ...")
|
|
||||||
name = ready.get()
|
|
||||||
checkerThread = checkerThreads[name]
|
|
||||||
checkerThread.join()
|
|
||||||
tempResult = checkerThread.getResult()
|
|
||||||
debug("success, result is " + str(tempResult))
|
|
||||||
if [VerifierHandlerWorker.OK, VerifierHandlerWorker.NOK].count(tempResult) != 0:
|
|
||||||
result = tempResult
|
|
||||||
del checkerThreads[name]
|
|
||||||
self.zombies.extend(checkerThreads.values())
|
|
||||||
return result
|
|
||||||
|
|
||||||
def finish(self):
|
|
||||||
while 1:
|
|
||||||
debug("finish: %i zombies left" % len(self.zombies))
|
|
||||||
for z in self.zombies:
|
|
||||||
if not z.isAlive():
|
|
||||||
debug("finish: thread %s for %s, %s terminated" % (z.getName(), z.getHost(), z.getAddress().getAddress()))
|
|
||||||
self.zombies.remove(z)
|
|
||||||
for z in self.zombies:
|
|
||||||
debug("finish: left over %s for %s, %s" % (z.getName(), z.getHost(), z.getAddress().getAddress()))
|
|
||||||
if len(self.zombies) == 0:
|
|
||||||
debug("finish: no zombie left ...")
|
|
||||||
break
|
|
||||||
debug("finish: WAITING")
|
|
||||||
time.sleep(5)
|
|
||||||
debug("finish: CONTINUE")
|
|
||||||
debug("finish: all threads terminated")
|
|
||||||
|
|
||||||
|
|
||||||
def execute(self, address):
|
|
||||||
debug("address " + address)
|
|
||||||
|
|
||||||
address = EMailAddress(address)
|
|
||||||
|
|
||||||
bestmxes = address.getBestMX()
|
|
||||||
if not bestmxes:
|
|
||||||
return "<NOK> <no bestmx found>"
|
|
||||||
|
|
||||||
if self.container.cache == None:
|
|
||||||
debug("no caching")
|
|
||||||
result = self.checkAvailability(bestmxes, address)
|
|
||||||
else:
|
|
||||||
result = self.container.cache.get(address.getAddress())
|
|
||||||
if result == None:
|
|
||||||
debug("not found in cache")
|
|
||||||
result = self.checkAvailability(bestmxes, address)
|
|
||||||
if result != VerifierHandlerWorker.TEMPNOK:
|
|
||||||
self.container.cache.put(address.getAddress(), result)
|
|
||||||
else:
|
|
||||||
debug("found in cache")
|
|
||||||
|
|
||||||
if result == VerifierHandlerWorker.OK:
|
|
||||||
return "<OK>"
|
|
||||||
elif result == VerifierHandlerWorker.NOK:
|
|
||||||
return "<NOK> <home server sent a permanent negative answer>"
|
|
||||||
else:
|
|
||||||
raise MyTemporaryVerifierException, "no mx reachable"
|
|
||||||
|
|
||||||
|
|
||||||
class MySMTPPermanentException(ValueError): pass
|
|
||||||
|
|
||||||
class MySMTPTemporaryException(ValueError): pass
|
|
||||||
|
|
||||||
class MySMTP(object):
|
|
||||||
def __init__(self, host, timeout, port=25):
|
|
||||||
self.host = host
|
|
||||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
||||||
self.socket.settimeout(timeout)
|
|
||||||
self.socket.connect((host, port))
|
|
||||||
self.socket.recv(8192)
|
|
||||||
self.resPattern = re.compile(r'[\w\W]*?^(\d{3,3}) (.*?)[\r\n]{1,2}$', re.MULTILINE)
|
|
||||||
|
|
||||||
def checkResult(self, r):
|
|
||||||
code, text = r
|
|
||||||
code = code / 100
|
|
||||||
|
|
||||||
if code == 2:
|
|
||||||
return;
|
|
||||||
elif code == 4:
|
|
||||||
raise MySMTPTemporaryException, text
|
|
||||||
elif code == 5:
|
|
||||||
raise MySMTPPermanentException, text
|
|
||||||
else:
|
|
||||||
raise MySMTPPermanentException, "unknown code: " + str(code) + ", text: " + str(text)
|
|
||||||
|
|
||||||
def docmd(self, cmd):
|
|
||||||
debug("docmd: %s, cmd: %s " % (self.host, cmd))
|
|
||||||
self.socket.sendall(cmd + "\r\n")
|
|
||||||
res = self.socket.recv(8192)
|
|
||||||
debug("docmd: result: (%s)" % res)
|
|
||||||
m = self.resPattern.match(res)
|
|
||||||
return self.checkResult((int(m.group(1)), m.group(2)))
|
|
||||||
|
|
||||||
def helo(self, param):
|
|
||||||
return self.docmd("helo " + param)
|
|
||||||
|
|
||||||
def mail(self, sender):
|
|
||||||
if sender[0] != '<' and sender[-1] != '>': sender = '<' + sender + '>'
|
|
||||||
return self.docmd("mail from:" + sender)
|
|
||||||
|
|
||||||
def rcpt(self, recipient):
|
|
||||||
return self.docmd("rcpt to:<%s>" % recipient)
|
|
||||||
|
|
||||||
def quit(self):
|
|
||||||
self.docmd("quit")
|
|
||||||
self.socket.close()
|
|
||||||
|
|
||||||
|
|
||||||
class EMailAddress(object):
|
|
||||||
def __init__(self, address):
|
|
||||||
self.address = address
|
|
||||||
if self.address[0] == '<' and self.address[-1] == '>': self.address = self.address[1:-1]
|
|
||||||
try:
|
|
||||||
self.userpart, self.domain = self.address.split('@')
|
|
||||||
except ValueError:
|
|
||||||
raise MyPermanentVerifierException, "excepted email address, found not at-sign"
|
|
||||||
|
|
||||||
def getUserPart(self):
|
|
||||||
return self.userpart
|
|
||||||
|
|
||||||
def getDomain(self):
|
|
||||||
return self.domain
|
|
||||||
|
|
||||||
def getAddress(self):
|
|
||||||
return self.address
|
|
||||||
|
|
||||||
def getBestMX(self):
|
|
||||||
if self.domain[0] == '[' and self.domain[-1] == ']':
|
|
||||||
bestmx2 = [self.domain[1:-1]]
|
|
||||||
else:
|
|
||||||
bestmx = DNS.mxlookup(self.domain)
|
|
||||||
pref = None
|
|
||||||
bestmx2 = []
|
|
||||||
for mx in bestmx:
|
|
||||||
if pref == None: pref = mx[0]
|
|
||||||
if pref == mx[0]:
|
|
||||||
bestmx2.append(mx[1])
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
debug("bestmx " + str(bestmx2))
|
|
||||||
return bestmx2
|
|
||||||
|
|
@ -1,327 +0,0 @@
|
|||||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Sender Address Verifier for Sendmail</title>
|
|
||||||
<meta name="generator" content="emacs-wiki.el">
|
|
||||||
<meta http-equiv="Content-Type"
|
|
||||||
content="text/html; charset=iso-8859-1">
|
|
||||||
<link rev="made" href="mailto:woho@hottis.de">
|
|
||||||
<link rel="stylesheet" type="text/css" href="/web/default.css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Sender Address Verifier for Sendmail</h1>
|
|
||||||
<!-- Page published by Emacs Wiki begins here -->
|
|
||||||
<p>
|
|
||||||
Author: Wolfgang Hottgenroth <<a href="mailto:woho@hottis.de">woho@hottis.de</a>>, 2004-05-17
|
|
||||||
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
This is the prototype of a sender address verifier for sendmail-8.13.
|
|
||||||
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
It consists of a m4 file containing a FEATURE to be included in your
|
|
||||||
<code>sendmail.mc</code> and a verifier daemon in a bit of python code.
|
|
||||||
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
By including the FEATURE in your sendmail.mc file and running the
|
|
||||||
verifier daemon, sendmail file verify either
|
|
||||||
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>all sender addresses (with certain exceptions) or
|
|
||||||
</li>
|
|
||||||
<li>only certain sender addresses
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
This will be done by connecting to the best MX servers of the
|
|
||||||
particular domain, trying to send a mail to the particular address and
|
|
||||||
collect the replies.
|
|
||||||
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Actually only the <code>HELO</code>, <code>MAIL</code> and <code>RCPT</code> commands are issued.
|
|
||||||
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
If a positive reply was found, the mail is considered as valid.
|
|
||||||
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
If a permanent negative reply was found, the mail is considered as
|
|
||||||
invalid.
|
|
||||||
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
If no MX entry was found, the mail is considered as invalid.
|
|
||||||
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
If a temporary negative reply was found, the mail is considered as
|
|
||||||
temporary invalid.
|
|
||||||
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
If there is more than one best MX server all of these servers are
|
|
||||||
connected in parallel and the first permanent reply (either positive
|
|
||||||
or negative) is returned.
|
|
||||||
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>Download</h3>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
The complete sources: <a href="./download/">download</a>
|
|
||||||
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Have a look into the sources: <a href="http://www.hottis.de/cgi-bin/cvsweb.cgi/sender_verifier/">sources</a>
|
|
||||||
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>Requirements</h3>
|
|
||||||
|
|
||||||
<h4>sendmail</h4>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
sendmail-8.13 is required, since this thing uses the fresh introduced
|
|
||||||
socket map.
|
|
||||||
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Find it <a href="http://www.sendmail.org">here</a> on the sendmail homepage.
|
|
||||||
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Your need to build sendmail with support for the socket map. Include
|
|
||||||
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<pre class="example">
|
|
||||||
APPENDDEF(`confMAPDEF',`-DSOCKETMAP')
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
in your <code>site.config.m4</code>.
|
|
||||||
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h4>Python</h4>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Python 2.2 or 2.3 is required. If you have Python 2.3 you must delete
|
|
||||||
the <code>import timeoutsocket</code> line from <code>verifier.py</code>.
|
|
||||||
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Additionally the python package <code>python-dns</code> is required. Find it
|
|
||||||
<a href="http://pydns.sourceforge.net/">http://pydns.sourceforge.net</a>.
|
|
||||||
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>Configuration of sendmail</h3>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Include the FEATURE in your <code>sendmail.mc</code> file. You need to give two
|
|
||||||
parameters:
|
|
||||||
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<pre class="example">
|
|
||||||
FEATURE(`verifysender', `mode', `return')
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
For <code>mode</code> you must give either <code>white</code> or <code>black</code>.
|
|
||||||
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<dl>
|
|
||||||
<dt><code>white</code></dt>
|
|
||||||
<dd>
|
|
||||||
All sender addresses but those mentioned in the whitelist
|
|
||||||
file are verified. Complete addresses or just domains can be listed in
|
|
||||||
the file. The default location of the whitelist is
|
|
||||||
<code>/etc/mail/verify-white-list</code>. If you need a different location,
|
|
||||||
define it to <code>confVERIFIER_WHITELIST</code>.
|
|
||||||
</dd>
|
|
||||||
<dt><code>black</code></dt>
|
|
||||||
<dd>
|
|
||||||
only addresses or addresses within domains listed in the
|
|
||||||
blacklist file are verified. It is obviously only useful to mention
|
|
||||||
domains in the blacklist. The default location of the blacklist is
|
|
||||||
<code>/etc/mail/verify-black-list</code>. If you need a different location,
|
|
||||||
define it to <code>confVERIFIER_BLACKLIST</code>.
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Both the blacklist and the whitelist file are maps, they must be
|
|
||||||
created with <code>makemap</code>. Therefore the entries need a LHS (the address
|
|
||||||
or domain) and a RHS. The actual content of the RHS has NO meaning at
|
|
||||||
all.
|
|
||||||
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
The FEATURE defines a socket map. The default target of the map is
|
|
||||||
<code>inet:8884@127.0.0.1</code>, according to the default setting in
|
|
||||||
<code>Config.py</code>. If you need something different, define it to
|
|
||||||
<code>confVERIFIER_MAP</code>, but don't forget to also adjust <code>Config.py</code>.
|
|
||||||
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>Configuration of the verification daemon</h3>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
The configuration of the daemon is done in the file <code>Config.py</code>.
|
|
||||||
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
This is the default of this file:
|
|
||||||
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<pre class="example">
|
|
||||||
[Daemon]
|
|
||||||
Address: 127.0.0.1
|
|
||||||
Port: 8884
|
|
||||||
PidFile: smmapd.pid
|
|
||||||
Plugins: Verifier,Verifier2
|
|
||||||
|
|
||||||
[Logging]
|
|
||||||
ApplId: smmapd
|
|
||||||
|
|
||||||
[Verifier]
|
|
||||||
ContainerModule: VerifierHandler
|
|
||||||
ContainerClass: VerifierHandlerContainer
|
|
||||||
WorkerModule: VerifierHandler
|
|
||||||
WorkerClass: VerifierHandlerWorker
|
|
||||||
EnableCaching: 1
|
|
||||||
CacheExpiration: 20
|
|
||||||
SMTPTimeOut: 20
|
|
||||||
SMTPHeloParam: local
|
|
||||||
SMTPCheckSender: <>
|
|
||||||
|
|
||||||
[Verifier2]
|
|
||||||
ContainerModule: VerifierHandler
|
|
||||||
ContainerClass: VerifierHandlerContainer
|
|
||||||
WorkerModule: VerifierHandler
|
|
||||||
WorkerClass: VerifierHandlerWorker
|
|
||||||
EnableCaching: 1
|
|
||||||
CacheExpiration: 20
|
|
||||||
SMTPTimeOut: 20
|
|
||||||
SMTPHeloParam: hottis.de
|
|
||||||
SMTPCheckSender: <postmaster@hottis.de></pre>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<code>Port</code> and <code>Address</code> are specifying the socket the daemon should
|
|
||||||
listen to for communication with sendmail. These settings must be
|
|
||||||
reflected in the <code>confVERIFIER_MAP</code> if you change it.
|
|
||||||
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<code>SMTPTimeOut</code> is the timeout for the communication with the MX servers
|
|
||||||
when verifying addresses.
|
|
||||||
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<code>SMTPHeloParam</code> is the parameter the verifier will use with the <code>HELO</code>
|
|
||||||
command when verifying.
|
|
||||||
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<code>SMTPCheckSender</code> is the sender address used during
|
|
||||||
verifications. You should not change it unless you know what you do to
|
|
||||||
avoid verification loops.
|
|
||||||
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Since the verification is a time and resource consuming process,
|
|
||||||
results can be cached, which is enabled by default. Set
|
|
||||||
<code>EnableCaching</code> to 0 to disable it.
|
|
||||||
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<code>CacheExpiration</code> is the time in seconds an entry in the cache is
|
|
||||||
considered as valid. It should be much higher.
|
|
||||||
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>Operation</h3>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Configure sendmail and the daemon according to your needs. Start the
|
|
||||||
daemon:
|
|
||||||
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<pre class="example">
|
|
||||||
./verifier.py
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
<h3>Changes</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>According to a comment in comp.mail.sendmail I've introduced a class
|
|
||||||
<code>verifier_fix_white</code> in the FEATURE file, currently containing only
|
|
||||||
the string <code>postmaster</code>. Addresses with userpart in this class will
|
|
||||||
never ever be verified to avoid infinite verifying loops.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h4>2004-05-17</h4>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>support plugins
|
|
||||||
</li>
|
|
||||||
<li>separate container and worker object, thereby enable multiple
|
|
||||||
instances of the same plugins
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Page published by Emacs Wiki ends here -->
|
|
||||||
<div class="navfoot">
|
|
||||||
<hr>
|
|
||||||
<table width="100%" border="0" summary="Footer navigation">
|
|
||||||
<tr>
|
|
||||||
<td width="33%" align="left">
|
|
||||||
<span class="footdate">UPDATED: 2004-05-17</span>
|
|
||||||
</td>
|
|
||||||
<td width="34%" align="center">
|
|
||||||
<span class="foothome">
|
|
||||||
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td width="33%" align="right">
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,195 +0,0 @@
|
|||||||
#title Sender Address Verifier for Sendmail
|
|
||||||
#style /web/default.css
|
|
||||||
|
|
||||||
|
|
||||||
Author: Wolfgang Hottgenroth <woho@hottis.de>, 2004-05-17
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
This is the prototype of a sender address verifier for sendmail-8.13.
|
|
||||||
|
|
||||||
It consists of a m4 file containing a FEATURE to be included in your
|
|
||||||
=sendmail.mc= and a verifier daemon in a bit of python code.
|
|
||||||
|
|
||||||
|
|
||||||
By including the FEATURE in your sendmail.mc file and running the
|
|
||||||
verifier daemon, sendmail file verify either
|
|
||||||
|
|
||||||
- all sender addresses (with certain exceptions) or
|
|
||||||
- only certain sender addresses
|
|
||||||
|
|
||||||
This will be done by connecting to the best MX servers of the
|
|
||||||
particular domain, trying to send a mail to the particular address and
|
|
||||||
collect the replies.
|
|
||||||
|
|
||||||
Actually only the =HELO=, =MAIL= and =RCPT= commands are issued.
|
|
||||||
|
|
||||||
If a positive reply was found, the mail is considered as valid.
|
|
||||||
|
|
||||||
If a permanent negative reply was found, the mail is considered as
|
|
||||||
invalid.
|
|
||||||
|
|
||||||
If no MX entry was found, the mail is considered as invalid.
|
|
||||||
|
|
||||||
If a temporary negative reply was found, the mail is considered as
|
|
||||||
temporary invalid.
|
|
||||||
|
|
||||||
If there is more than one best MX server all of these servers are
|
|
||||||
connected in parallel and the first permanent reply (either positive
|
|
||||||
or negative) is returned.
|
|
||||||
|
|
||||||
|
|
||||||
** Download
|
|
||||||
|
|
||||||
The complete sources: [[./download/][download]]
|
|
||||||
|
|
||||||
Have a look into the sources: [[http://www.hottis.de/cgi-bin/cvsweb.cgi/sender_verifier/][sources]]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
** Requirements
|
|
||||||
|
|
||||||
*** sendmail
|
|
||||||
|
|
||||||
sendmail-8.13 is required, since this thing uses the fresh introduced
|
|
||||||
socket map.
|
|
||||||
|
|
||||||
Find it [[http://www.sendmail.org][here]] on the sendmail homepage.
|
|
||||||
|
|
||||||
Your need to build sendmail with support for the socket map. Include
|
|
||||||
|
|
||||||
<example>
|
|
||||||
APPENDDEF(`confMAPDEF',`-DSOCKETMAP')
|
|
||||||
</example>
|
|
||||||
|
|
||||||
in your =site.config.m4=.
|
|
||||||
|
|
||||||
|
|
||||||
*** Python
|
|
||||||
|
|
||||||
Python 2.2 or 2.3 is required. If you have Python 2.3 you must delete
|
|
||||||
the =import timeoutsocket= line from =verifier.py=.
|
|
||||||
|
|
||||||
Additionally the python package =python-dns= is required. Find it
|
|
||||||
[[http://pydns.sourceforge.net/][http://pydns.sourceforge.net]].
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
** Configuration of sendmail
|
|
||||||
|
|
||||||
Include the FEATURE in your =sendmail.mc= file. You need to give two
|
|
||||||
parameters:
|
|
||||||
|
|
||||||
<example>
|
|
||||||
FEATURE(`verifysender', `mode', `return')
|
|
||||||
</example>
|
|
||||||
|
|
||||||
For =mode= you must give either =white= or =black=.
|
|
||||||
|
|
||||||
=white= :: All sender addresses but those mentioned in the whitelist
|
|
||||||
file are verified. Complete addresses or just domains can be listed in
|
|
||||||
the file. The default location of the whitelist is
|
|
||||||
=/etc/mail/verify-white-list=. If you need a different location,
|
|
||||||
define it to =confVERIFIER_WHITELIST=.
|
|
||||||
|
|
||||||
=black= :: only addresses or addresses within domains listed in the
|
|
||||||
blacklist file are verified. It is obviously only useful to mention
|
|
||||||
domains in the blacklist. The default location of the blacklist is
|
|
||||||
=/etc/mail/verify-black-list=. If you need a different location,
|
|
||||||
define it to =confVERIFIER_BLACKLIST=.
|
|
||||||
|
|
||||||
Both the blacklist and the whitelist file are maps, they must be
|
|
||||||
created with =makemap=. Therefore the entries need a LHS (the address
|
|
||||||
or domain) and a RHS. The actual content of the RHS has NO meaning at
|
|
||||||
all.
|
|
||||||
|
|
||||||
The FEATURE defines a socket map. The default target of the map is
|
|
||||||
=inet:8884@127.0.0.1=, according to the default setting in
|
|
||||||
=Config.py=. If you need something different, define it to
|
|
||||||
=confVERIFIER_MAP=, but don't forget to also adjust =Config.py=.
|
|
||||||
|
|
||||||
|
|
||||||
** Configuration of the verification daemon
|
|
||||||
|
|
||||||
The configuration of the daemon is done in the file =Config.py=.
|
|
||||||
|
|
||||||
This is the default of this file:
|
|
||||||
|
|
||||||
<example>
|
|
||||||
[Daemon]
|
|
||||||
Address: 127.0.0.1
|
|
||||||
Port: 8884
|
|
||||||
PidFile: smmapd.pid
|
|
||||||
Plugins: Verifier,Verifier2
|
|
||||||
|
|
||||||
[Logging]
|
|
||||||
ApplId: smmapd
|
|
||||||
|
|
||||||
[Verifier]
|
|
||||||
ContainerModule: VerifierHandler
|
|
||||||
ContainerClass: VerifierHandlerContainer
|
|
||||||
WorkerModule: VerifierHandler
|
|
||||||
WorkerClass: VerifierHandlerWorker
|
|
||||||
EnableCaching: 1
|
|
||||||
CacheExpiration: 20
|
|
||||||
SMTPTimeOut: 20
|
|
||||||
SMTPHeloParam: local
|
|
||||||
SMTPCheckSender: <>
|
|
||||||
|
|
||||||
[Verifier2]
|
|
||||||
ContainerModule: VerifierHandler
|
|
||||||
ContainerClass: VerifierHandlerContainer
|
|
||||||
WorkerModule: VerifierHandler
|
|
||||||
WorkerClass: VerifierHandlerWorker
|
|
||||||
EnableCaching: 1
|
|
||||||
CacheExpiration: 20
|
|
||||||
SMTPTimeOut: 20
|
|
||||||
SMTPHeloParam: hottis.de
|
|
||||||
SMTPCheckSender: <postmaster@hottis.de></example>
|
|
||||||
|
|
||||||
=Port= and =Address= are specifying the socket the daemon should
|
|
||||||
listen to for communication with sendmail. These settings must be
|
|
||||||
reflected in the =confVERIFIER_MAP= if you change it.
|
|
||||||
|
|
||||||
=SMTPTimeOut= is the timeout for the communication with the MX servers
|
|
||||||
when verifying addresses.
|
|
||||||
|
|
||||||
=SMTPHeloParam= is the parameter the verifier will use with the =HELO=
|
|
||||||
command when verifying.
|
|
||||||
|
|
||||||
=SMTPCheckSender= is the sender address used during
|
|
||||||
verifications. You should not change it unless you know what you do to
|
|
||||||
avoid verification loops.
|
|
||||||
|
|
||||||
Since the verification is a time and resource consuming process,
|
|
||||||
results can be cached, which is enabled by default. Set
|
|
||||||
=EnableCaching= to 0 to disable it.
|
|
||||||
|
|
||||||
=CacheExpiration= is the time in seconds an entry in the cache is
|
|
||||||
considered as valid. It should be much higher.
|
|
||||||
|
|
||||||
|
|
||||||
** Operation
|
|
||||||
|
|
||||||
Configure sendmail and the daemon according to your needs. Start the
|
|
||||||
daemon:
|
|
||||||
|
|
||||||
<example>
|
|
||||||
./verifier.py
|
|
||||||
</example>
|
|
||||||
|
|
||||||
|
|
||||||
** Changes
|
|
||||||
|
|
||||||
- According to a comment in comp.mail.sendmail I've introduced a class
|
|
||||||
=verifier_fix_white= in the FEATURE file, currently containing only
|
|
||||||
the string =postmaster=. Addresses with userpart in this class will
|
|
||||||
never ever be verified to avoid infinite verifying loops.
|
|
||||||
|
|
||||||
*** 2004-05-17
|
|
||||||
- support plugins
|
|
||||||
- separate container and worker object, thereby enable multiple
|
|
||||||
instances of the same plugins
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
from distutils.core import setup
|
|
||||||
|
|
||||||
setup(name="smmapd",
|
|
||||||
version="0.1",
|
|
||||||
description="Framework for sendmail SocketMap handlers",
|
|
||||||
long_description = """
|
|
||||||
A framework to build handlers for the sendmail 8.13 SocketMap,
|
|
||||||
together with an implementation of a sender-address verifier,
|
|
||||||
together we a sendmail m4 feature file""",
|
|
||||||
author="Wolfgang Hottgenroth",
|
|
||||||
author_email="woho@hottis.de",
|
|
||||||
url="http://www.hottis.de/web/verifier/index.html",
|
|
||||||
py_modules = [ 'SendmailSocketMapHandler', 'Cache', 'Logging', 'VerifierHandler' ],
|
|
||||||
scripts = [ 'smmapd' ],
|
|
||||||
data_files = [('config', ['smmapd.ini']),
|
|
||||||
('cf/feature', ['verifysender.m4'])]
|
|
||||||
)
|
|
@ -1,65 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
|
|
||||||
import SocketServer
|
|
||||||
import socket
|
|
||||||
import os
|
|
||||||
import ConfigParser
|
|
||||||
import getopt
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from Logging import *
|
|
||||||
from SendmailSocketMapHandler import SendmailDispatcher
|
|
||||||
|
|
||||||
|
|
||||||
def usage():
|
|
||||||
print "Usage"
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
opts, args = getopt.getopt(sys.argv[1:], "hC:F", ["help", "ConfigFile=", "ForeGround"])
|
|
||||||
except getopt.GetoptError:
|
|
||||||
usage()
|
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
configFile = '/etc/smmapd.ini'
|
|
||||||
foreGround = None
|
|
||||||
for o, a in opts:
|
|
||||||
if o in ("-h", "--help"):
|
|
||||||
usage()
|
|
||||||
sys.exit()
|
|
||||||
if o in ("-C", "--ConfigFile"):
|
|
||||||
configFile = a
|
|
||||||
if o in ("-F", "--ForeGround"):
|
|
||||||
foreGround = 1
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
config = ConfigParser.ConfigParser()
|
|
||||||
config.read(configFile)
|
|
||||||
|
|
||||||
openlog(config)
|
|
||||||
|
|
||||||
if foreGround:
|
|
||||||
pid = 0
|
|
||||||
else:
|
|
||||||
pid = os.fork()
|
|
||||||
|
|
||||||
if pid:
|
|
||||||
pidFile = file(config.get('Daemon', 'PidFile'), mode='w')
|
|
||||||
pidFile.write("%i\n" % pid)
|
|
||||||
pidFile.close()
|
|
||||||
print "daemon started with pid ", pid
|
|
||||||
else:
|
|
||||||
log("daemon started")
|
|
||||||
|
|
||||||
SendmailDispatcher.registerAll(config)
|
|
||||||
|
|
||||||
try:
|
|
||||||
address = config.get('Daemon', 'Address')
|
|
||||||
port = int(config.get('Daemon', 'Port'))
|
|
||||||
srv = SocketServer.ThreadingTCPServer((address, port), SendmailDispatcher)
|
|
||||||
srv.serve_forever()
|
|
||||||
except socket.error, arg:
|
|
||||||
log("got a socket error: %s" % str(arg))
|
|
||||||
log("daemon died")
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
|||||||
[Daemon]
|
|
||||||
Address: 127.0.0.1
|
|
||||||
Port: 8884
|
|
||||||
PidFile: smmapd.pid
|
|
||||||
Plugins: verifier,verifier2,cyrusChecker
|
|
||||||
|
|
||||||
[Logging]
|
|
||||||
ApplId: smmapd
|
|
||||||
|
|
||||||
[verifier]
|
|
||||||
ContainerModule: VerifierHandler
|
|
||||||
ContainerClass: VerifierHandlerContainer
|
|
||||||
WorkerModule: VerifierHandler
|
|
||||||
WorkerClass: VerifierHandlerWorker
|
|
||||||
EnableCaching: yes
|
|
||||||
CacheExpiration: 20
|
|
||||||
SMTPTimeOut: 20
|
|
||||||
SMTPHeloParam: local
|
|
||||||
SMTPCheckSender: <>
|
|
||||||
|
|
||||||
[verifier2]
|
|
||||||
ContainerModule: VerifierHandler
|
|
||||||
ContainerClass: VerifierHandlerContainer
|
|
||||||
WorkerModule: VerifierHandler
|
|
||||||
WorkerClass: VerifierHandlerWorker
|
|
||||||
EnableCaching: yes
|
|
||||||
CacheExpiration: 20
|
|
||||||
SMTPTimeOut: 20
|
|
||||||
SMTPHeloParam: hottis.de
|
|
||||||
SMTPCheckSender: <postmaster@hottis.de>
|
|
||||||
|
|
||||||
[cyrusChecker]
|
|
||||||
ContainerModule: SendmailSocketMapHandler
|
|
||||||
ContainerClass: smmapBaseHandlerContainer
|
|
||||||
WorkerModule: CyrusChecker
|
|
||||||
WorkerClass: CyrusCheckerWorker
|
|
||||||
|
|
@ -1,424 +0,0 @@
|
|||||||
|
|
||||||
####
|
|
||||||
# Copyright 2000,2001 by Timothy O'Malley <timo@alum.mit.edu>
|
|
||||||
#
|
|
||||||
# All Rights Reserved
|
|
||||||
#
|
|
||||||
# Permission to use, copy, modify, and distribute this software
|
|
||||||
# and its documentation for any purpose and without fee is hereby
|
|
||||||
# granted, provided that the above copyright notice appear in all
|
|
||||||
# copies and that both that copyright notice and this permission
|
|
||||||
# notice appear in supporting documentation, and that the name of
|
|
||||||
# Timothy O'Malley not be used in advertising or publicity
|
|
||||||
# pertaining to distribution of the software without specific, written
|
|
||||||
# prior permission.
|
|
||||||
#
|
|
||||||
# Timothy O'Malley DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
|
|
||||||
# SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
||||||
# AND FITNESS, IN NO EVENT SHALL Timothy O'Malley BE LIABLE FOR
|
|
||||||
# ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
|
|
||||||
# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
|
|
||||||
# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
||||||
# PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
#
|
|
||||||
####
|
|
||||||
|
|
||||||
"""Timeout Socket
|
|
||||||
|
|
||||||
This module enables a timeout mechanism on all TCP connections. It
|
|
||||||
does this by inserting a shim into the socket module. After this module
|
|
||||||
has been imported, all socket creation goes through this shim. As a
|
|
||||||
result, every TCP connection will support a timeout.
|
|
||||||
|
|
||||||
The beauty of this method is that it immediately and transparently
|
|
||||||
enables the entire python library to support timeouts on TCP sockets.
|
|
||||||
As an example, if you wanted to SMTP connections to have a 20 second
|
|
||||||
timeout:
|
|
||||||
|
|
||||||
import timeoutsocket
|
|
||||||
import smtplib
|
|
||||||
timeoutsocket.setDefaultSocketTimeout(20)
|
|
||||||
|
|
||||||
|
|
||||||
The timeout applies to the socket functions that normally block on
|
|
||||||
execution: read, write, connect, and accept. If any of these
|
|
||||||
operations exceeds the specified timeout, the exception Timeout
|
|
||||||
will be raised.
|
|
||||||
|
|
||||||
The default timeout value is set to None. As a result, importing
|
|
||||||
this module does not change the default behavior of a socket. The
|
|
||||||
timeout mechanism only activates when the timeout has been set to
|
|
||||||
a numeric value. (This behavior mimics the behavior of the
|
|
||||||
select.select() function.)
|
|
||||||
|
|
||||||
This module implements two classes: TimeoutSocket and TimeoutFile.
|
|
||||||
|
|
||||||
The TimeoutSocket class defines a socket-like object that attempts to
|
|
||||||
avoid the condition where a socket may block indefinitely. The
|
|
||||||
TimeoutSocket class raises a Timeout exception whenever the
|
|
||||||
current operation delays too long.
|
|
||||||
|
|
||||||
The TimeoutFile class defines a file-like object that uses the TimeoutSocket
|
|
||||||
class. When the makefile() method of TimeoutSocket is called, it returns
|
|
||||||
an instance of a TimeoutFile.
|
|
||||||
|
|
||||||
Each of these objects adds two methods to manage the timeout value:
|
|
||||||
|
|
||||||
get_timeout() --> returns the timeout of the socket or file
|
|
||||||
set_timeout() --> sets the timeout of the socket or file
|
|
||||||
|
|
||||||
|
|
||||||
As an example, one might use the timeout feature to create httplib
|
|
||||||
connections that will timeout after 30 seconds:
|
|
||||||
|
|
||||||
import timeoutsocket
|
|
||||||
import httplib
|
|
||||||
H = httplib.HTTP("www.python.org")
|
|
||||||
H.sock.set_timeout(30)
|
|
||||||
|
|
||||||
Note: When used in this manner, the connect() routine may still
|
|
||||||
block because it happens before the timeout is set. To avoid
|
|
||||||
this, use the 'timeoutsocket.setDefaultSocketTimeout()' function.
|
|
||||||
|
|
||||||
Good Luck!
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
__version__ = "$Revision$"
|
|
||||||
__author__ = "Timothy O'Malley <timo@alum.mit.edu>"
|
|
||||||
|
|
||||||
#
|
|
||||||
# Imports
|
|
||||||
#
|
|
||||||
import select, string
|
|
||||||
import socket
|
|
||||||
if not hasattr(socket, "_no_timeoutsocket"):
|
|
||||||
_socket = socket.socket
|
|
||||||
else:
|
|
||||||
_socket = socket._no_timeoutsocket
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Set up constants to test for Connected and Blocking operations.
|
|
||||||
# We delete 'os' and 'errno' to keep our namespace clean(er).
|
|
||||||
# Thanks to Alex Martelli and G. Li for the Windows error codes.
|
|
||||||
#
|
|
||||||
import os
|
|
||||||
if os.name == "nt":
|
|
||||||
_IsConnected = ( 10022, 10056 )
|
|
||||||
_ConnectBusy = ( 10035, )
|
|
||||||
_AcceptBusy = ( 10035, )
|
|
||||||
else:
|
|
||||||
import errno
|
|
||||||
_IsConnected = ( errno.EISCONN, )
|
|
||||||
_ConnectBusy = ( errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK )
|
|
||||||
_AcceptBusy = ( errno.EAGAIN, errno.EWOULDBLOCK )
|
|
||||||
del errno
|
|
||||||
del os
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Default timeout value for ALL TimeoutSockets
|
|
||||||
#
|
|
||||||
_DefaultTimeout = None
|
|
||||||
def setDefaultSocketTimeout(timeout):
|
|
||||||
global _DefaultTimeout
|
|
||||||
_DefaultTimeout = timeout
|
|
||||||
def getDefaultSocketTimeout():
|
|
||||||
return _DefaultTimeout
|
|
||||||
|
|
||||||
#
|
|
||||||
# Exceptions for socket errors and timeouts
|
|
||||||
#
|
|
||||||
Error = socket.error
|
|
||||||
class Timeout(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Factory function
|
|
||||||
#
|
|
||||||
from socket import AF_INET, SOCK_STREAM
|
|
||||||
def timeoutsocket(family=AF_INET, type=SOCK_STREAM, proto=None):
|
|
||||||
if family != AF_INET or type != SOCK_STREAM:
|
|
||||||
if proto:
|
|
||||||
return _socket(family, type, proto)
|
|
||||||
else:
|
|
||||||
return _socket(family, type)
|
|
||||||
return TimeoutSocket( _socket(family, type), _DefaultTimeout )
|
|
||||||
# end timeoutsocket
|
|
||||||
|
|
||||||
#
|
|
||||||
# The TimeoutSocket class definition
|
|
||||||
#
|
|
||||||
class TimeoutSocket:
|
|
||||||
"""TimeoutSocket object
|
|
||||||
Implements a socket-like object that raises Timeout whenever
|
|
||||||
an operation takes too long.
|
|
||||||
The definition of 'too long' can be changed using the
|
|
||||||
set_timeout() method.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_copies = 0
|
|
||||||
_blocking = 1
|
|
||||||
|
|
||||||
def __init__(self, sock, timeout):
|
|
||||||
self._sock = sock
|
|
||||||
self._timeout = timeout
|
|
||||||
# end __init__
|
|
||||||
|
|
||||||
def __getattr__(self, key):
|
|
||||||
return getattr(self._sock, key)
|
|
||||||
# end __getattr__
|
|
||||||
|
|
||||||
def get_timeout(self):
|
|
||||||
return self._timeout
|
|
||||||
# end set_timeout
|
|
||||||
|
|
||||||
def set_timeout(self, timeout=None):
|
|
||||||
self._timeout = timeout
|
|
||||||
# end set_timeout
|
|
||||||
|
|
||||||
def setblocking(self, blocking):
|
|
||||||
self._blocking = blocking
|
|
||||||
return self._sock.setblocking(blocking)
|
|
||||||
# end set_timeout
|
|
||||||
|
|
||||||
def connect_ex(self, addr):
|
|
||||||
errcode = 0
|
|
||||||
try:
|
|
||||||
self.connect(addr)
|
|
||||||
except Error, why:
|
|
||||||
errcode = why[0]
|
|
||||||
return errcode
|
|
||||||
# end connect_ex
|
|
||||||
|
|
||||||
def connect(self, addr, port=None, dumbhack=None):
|
|
||||||
# In case we were called as connect(host, port)
|
|
||||||
if port != None: addr = (addr, port)
|
|
||||||
|
|
||||||
# Shortcuts
|
|
||||||
sock = self._sock
|
|
||||||
timeout = self._timeout
|
|
||||||
blocking = self._blocking
|
|
||||||
|
|
||||||
# First, make a non-blocking call to connect
|
|
||||||
try:
|
|
||||||
sock.setblocking(0)
|
|
||||||
sock.connect(addr)
|
|
||||||
sock.setblocking(blocking)
|
|
||||||
return
|
|
||||||
except Error, why:
|
|
||||||
# Set the socket's blocking mode back
|
|
||||||
sock.setblocking(blocking)
|
|
||||||
|
|
||||||
# If we are not blocking, re-raise
|
|
||||||
if not blocking:
|
|
||||||
raise
|
|
||||||
|
|
||||||
# If we are already connected, then return success.
|
|
||||||
# If we got a genuine error, re-raise it.
|
|
||||||
errcode = why[0]
|
|
||||||
if dumbhack and errcode in _IsConnected:
|
|
||||||
return
|
|
||||||
elif errcode not in _ConnectBusy:
|
|
||||||
raise
|
|
||||||
|
|
||||||
# Now, wait for the connect to happen
|
|
||||||
# ONLY if dumbhack indicates this is pass number one.
|
|
||||||
# If select raises an error, we pass it on.
|
|
||||||
# Is this the right behavior?
|
|
||||||
if not dumbhack:
|
|
||||||
r,w,e = select.select([], [sock], [], timeout)
|
|
||||||
if w:
|
|
||||||
return self.connect(addr, dumbhack=1)
|
|
||||||
|
|
||||||
# If we get here, then we should raise Timeout
|
|
||||||
raise Timeout("Attempted connect to %s timed out." % str(addr) )
|
|
||||||
# end connect
|
|
||||||
|
|
||||||
def accept(self, dumbhack=None):
|
|
||||||
# Shortcuts
|
|
||||||
sock = self._sock
|
|
||||||
timeout = self._timeout
|
|
||||||
blocking = self._blocking
|
|
||||||
|
|
||||||
# First, make a non-blocking call to accept
|
|
||||||
# If we get a valid result, then convert the
|
|
||||||
# accept'ed socket into a TimeoutSocket.
|
|
||||||
# Be carefult about the blocking mode of ourselves.
|
|
||||||
try:
|
|
||||||
sock.setblocking(0)
|
|
||||||
newsock, addr = sock.accept()
|
|
||||||
sock.setblocking(blocking)
|
|
||||||
timeoutnewsock = self.__class__(newsock, timeout)
|
|
||||||
timeoutnewsock.setblocking(blocking)
|
|
||||||
return (timeoutnewsock, addr)
|
|
||||||
except Error, why:
|
|
||||||
# Set the socket's blocking mode back
|
|
||||||
sock.setblocking(blocking)
|
|
||||||
|
|
||||||
# If we are not supposed to block, then re-raise
|
|
||||||
if not blocking:
|
|
||||||
raise
|
|
||||||
|
|
||||||
# If we got a genuine error, re-raise it.
|
|
||||||
errcode = why[0]
|
|
||||||
if errcode not in _AcceptBusy:
|
|
||||||
raise
|
|
||||||
|
|
||||||
# Now, wait for the accept to happen
|
|
||||||
# ONLY if dumbhack indicates this is pass number one.
|
|
||||||
# If select raises an error, we pass it on.
|
|
||||||
# Is this the right behavior?
|
|
||||||
if not dumbhack:
|
|
||||||
r,w,e = select.select([sock], [], [], timeout)
|
|
||||||
if r:
|
|
||||||
return self.accept(dumbhack=1)
|
|
||||||
|
|
||||||
# If we get here, then we should raise Timeout
|
|
||||||
raise Timeout("Attempted accept timed out.")
|
|
||||||
# end accept
|
|
||||||
|
|
||||||
def send(self, data, flags=0):
|
|
||||||
sock = self._sock
|
|
||||||
if self._blocking:
|
|
||||||
r,w,e = select.select([],[sock],[], self._timeout)
|
|
||||||
if not w:
|
|
||||||
raise Timeout("Send timed out")
|
|
||||||
return sock.send(data, flags)
|
|
||||||
# end send
|
|
||||||
|
|
||||||
def recv(self, bufsize, flags=0):
|
|
||||||
sock = self._sock
|
|
||||||
if self._blocking:
|
|
||||||
r,w,e = select.select([sock], [], [], self._timeout)
|
|
||||||
if not r:
|
|
||||||
raise Timeout("Recv timed out")
|
|
||||||
return sock.recv(bufsize, flags)
|
|
||||||
# end recv
|
|
||||||
|
|
||||||
def makefile(self, flags="r", bufsize=-1):
|
|
||||||
self._copies = self._copies +1
|
|
||||||
return TimeoutFile(self, flags, bufsize)
|
|
||||||
# end makefile
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
if self._copies <= 0:
|
|
||||||
self._sock.close()
|
|
||||||
else:
|
|
||||||
self._copies = self._copies -1
|
|
||||||
# end close
|
|
||||||
|
|
||||||
# end TimeoutSocket
|
|
||||||
|
|
||||||
|
|
||||||
class TimeoutFile:
|
|
||||||
"""TimeoutFile object
|
|
||||||
Implements a file-like object on top of TimeoutSocket.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, sock, mode="r", bufsize=4096):
|
|
||||||
self._sock = sock
|
|
||||||
self._bufsize = 4096
|
|
||||||
if bufsize > 0: self._bufsize = bufsize
|
|
||||||
if not hasattr(sock, "_inqueue"): self._sock._inqueue = ""
|
|
||||||
|
|
||||||
# end __init__
|
|
||||||
|
|
||||||
def __getattr__(self, key):
|
|
||||||
return getattr(self._sock, key)
|
|
||||||
# end __getattr__
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
self._sock.close()
|
|
||||||
self._sock = None
|
|
||||||
# end close
|
|
||||||
|
|
||||||
def write(self, data):
|
|
||||||
self.send(data)
|
|
||||||
# end write
|
|
||||||
|
|
||||||
def read(self, size=-1):
|
|
||||||
_sock = self._sock
|
|
||||||
_bufsize = self._bufsize
|
|
||||||
while 1:
|
|
||||||
datalen = len(_sock._inqueue)
|
|
||||||
if datalen >= size >= 0:
|
|
||||||
break
|
|
||||||
bufsize = _bufsize
|
|
||||||
if size > 0:
|
|
||||||
bufsize = min(bufsize, size - datalen )
|
|
||||||
buf = self.recv(bufsize)
|
|
||||||
if not buf:
|
|
||||||
break
|
|
||||||
_sock._inqueue = _sock._inqueue + buf
|
|
||||||
data = _sock._inqueue
|
|
||||||
_sock._inqueue = ""
|
|
||||||
if size > 0 and datalen > size:
|
|
||||||
_sock._inqueue = data[size:]
|
|
||||||
data = data[:size]
|
|
||||||
return data
|
|
||||||
# end read
|
|
||||||
|
|
||||||
def readline(self, size=-1):
|
|
||||||
_sock = self._sock
|
|
||||||
_bufsize = self._bufsize
|
|
||||||
while 1:
|
|
||||||
idx = string.find(_sock._inqueue, "\n")
|
|
||||||
if idx >= 0:
|
|
||||||
break
|
|
||||||
datalen = len(_sock._inqueue)
|
|
||||||
if datalen >= size >= 0:
|
|
||||||
break
|
|
||||||
bufsize = _bufsize
|
|
||||||
if size > 0:
|
|
||||||
bufsize = min(bufsize, size - datalen )
|
|
||||||
buf = self.recv(bufsize)
|
|
||||||
if not buf:
|
|
||||||
break
|
|
||||||
_sock._inqueue = _sock._inqueue + buf
|
|
||||||
|
|
||||||
data = _sock._inqueue
|
|
||||||
_sock._inqueue = ""
|
|
||||||
if idx >= 0:
|
|
||||||
idx = idx + 1
|
|
||||||
_sock._inqueue = data[idx:]
|
|
||||||
data = data[:idx]
|
|
||||||
elif size > 0 and datalen > size:
|
|
||||||
_sock._inqueue = data[size:]
|
|
||||||
data = data[:size]
|
|
||||||
return data
|
|
||||||
# end readline
|
|
||||||
|
|
||||||
def readlines(self, sizehint=-1):
|
|
||||||
result = []
|
|
||||||
data = self.read()
|
|
||||||
while data:
|
|
||||||
idx = string.find(data, "\n")
|
|
||||||
if idx >= 0:
|
|
||||||
idx = idx + 1
|
|
||||||
result.append( data[:idx] )
|
|
||||||
data = data[idx:]
|
|
||||||
else:
|
|
||||||
result.append( data )
|
|
||||||
data = ""
|
|
||||||
return result
|
|
||||||
# end readlines
|
|
||||||
|
|
||||||
def flush(self): pass
|
|
||||||
|
|
||||||
# end TimeoutFile
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Silently replace the socket() builtin function with
|
|
||||||
# our timeoutsocket() definition.
|
|
||||||
#
|
|
||||||
if not hasattr(socket, "_no_timeoutsocket"):
|
|
||||||
socket._no_timeoutsocket = socket.socket
|
|
||||||
socket.socket = timeoutsocket
|
|
||||||
del socket
|
|
||||||
socket = timeoutsocket
|
|
||||||
# Finis
|
|
@ -1,110 +0,0 @@
|
|||||||
VERSIONID(`$Id$')
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
divert(-1)
|
|
||||||
|
|
||||||
define(`_USAGE_', `dnl
|
|
||||||
errprint(`*** ERROR: missing argument for FEATURE(verifysender):
|
|
||||||
Usage: FEATURE(`verifysender', `_mode_', `_return_')
|
|
||||||
_mode_: black or white
|
|
||||||
_return_: temp or perm
|
|
||||||
found: $1
|
|
||||||
')')
|
|
||||||
|
|
||||||
ifelse(_ARG_, `black', `', `
|
|
||||||
ifelse(_ARG_, `white', `', `
|
|
||||||
_USAGE_(`_mode_: ('_ARG_`)
|
|
||||||
')
|
|
||||||
')')
|
|
||||||
|
|
||||||
ifelse(_ARG2_, `temp', `', `
|
|
||||||
ifelse(_ARG2_, `perm', `', `
|
|
||||||
_USAGE_(`_return_: ('_ARG2_`)
|
|
||||||
')
|
|
||||||
')')
|
|
||||||
|
|
||||||
define(`_mode_', _ARG_)
|
|
||||||
define(`_return_', _ARG2_)
|
|
||||||
|
|
||||||
errprint(`*** _mode_: '_mode_`
|
|
||||||
')
|
|
||||||
errprint(`*** _return_: '_return_`
|
|
||||||
')
|
|
||||||
|
|
||||||
|
|
||||||
define(`_T_DSN_', `4.1.0')
|
|
||||||
define(`_T_REPLY', `451')
|
|
||||||
ifelse(_return_, `temp', `
|
|
||||||
define(`_DSN_', _T_DSN_)
|
|
||||||
define(`_REPLY_', _T_REPLY)', `dnl
|
|
||||||
define(`_DSN_', `5.1.0')
|
|
||||||
define(`_REPLY_', `550')')
|
|
||||||
|
|
||||||
ifelse(defn(`confVERIFIER_MAP'), `', `
|
|
||||||
define(`_VERIFIER_MAP_', `inet:8884@127.0.0.1')', `
|
|
||||||
define(`_VERIFIER_MAP_', confVERIFIER_MAP)')
|
|
||||||
|
|
||||||
ifelse(defn(`confVERIFIER_BLACKLIST'), `', `
|
|
||||||
define(`_VERIFIER_BLACKLIST_', `/etc/mail/verifier-black-list')', `
|
|
||||||
define(`_VERIFIER_BLACKLIST_', confVERIFIER_BLACKLIST)')
|
|
||||||
|
|
||||||
ifelse(defn(`confVERIFIER_WHITELIST'), `', `
|
|
||||||
define(`_VERIFIER_WHITELIST_', `/etc/mail/verifier-white-list')', `
|
|
||||||
define(`_VERIFIER_WHITELIST_', confVERIFIER_WHITELIST)')
|
|
||||||
|
|
||||||
divert(0)
|
|
||||||
|
|
||||||
LOCAL_CONFIG
|
|
||||||
# Adjust the port
|
|
||||||
Kverifier socket -T<temp> _VERIFIER_MAP_
|
|
||||||
ifelse(_mode_, `white', `dnl
|
|
||||||
Kverifier_helper hash -o _VERIFIER_WHITELIST_', `dnl
|
|
||||||
Kverifier_helper hash _VERIFIER_BLACKLIST_')
|
|
||||||
C{verifier_fix_white} postmaster
|
|
||||||
|
|
||||||
LOCAL_RULESETS
|
|
||||||
# This ruleset can be used to test the verifier in -bt mode
|
|
||||||
Svt
|
|
||||||
R$+ $: < $(verifier $1 $:none $) >
|
|
||||||
|
|
||||||
Sverifier0
|
|
||||||
R< $={verifier_fix_white} @ $+ > $@ < ok >
|
|
||||||
R< $+ @ $+ > $: < $2 > < $(verifier_helper $1 @ $2 $: $) >
|
|
||||||
R< $+ > < > $: < $(verifier_helper $1 $: $) >
|
|
||||||
ifelse(_mode_, `white', `dnl
|
|
||||||
dnl if we found nothing in the whitelist, we continue with checking
|
|
||||||
R< > $@ < cont >
|
|
||||||
dnl if we found something in the whitelist, we skip further verifications
|
|
||||||
R< $+ > $@ < ok >', `dnl
|
|
||||||
dnl if we found nothing in the blacklist, we skip further verifications
|
|
||||||
R< > $@ < ok >
|
|
||||||
dnl if we found something in the blacklist, we continue with checking
|
|
||||||
R< $+ > $@ < cont >')
|
|
||||||
|
|
||||||
Sverifier1
|
|
||||||
R< $+ > $: < $(verifier $1 $:none $) >
|
|
||||||
R< $* < temp > > $#error $@ _T_DSN_ $: "_T_REPLY_ Sender Address could currently not be verified (1)"
|
|
||||||
R< none > $#error $@ _T_DSN_ $: "_T_REPLY_ Sender Address could currently not be verified (2)"
|
|
||||||
R< <OK> $* > $@ < ok >
|
|
||||||
R< <NOK> < $* > > $#error $@ _DSN_ $: "_REPLY_ Sender Address was verified as bad: " $1
|
|
||||||
R< <TNOK> < $* > > $#error $@ _T_DSN_ $: "_T_REPLY_ Sender Address could currently not be verified (3): " $1
|
|
||||||
dnl if we get here, some is wrong with our code
|
|
||||||
R$* $#error $@ 4.7.1 $: "451 Local configuration error <sv1>"
|
|
||||||
|
|
||||||
|
|
||||||
SLocal_check_mail
|
|
||||||
dnl MAILER-DAEMON address must not be verified
|
|
||||||
R<> $@ <>
|
|
||||||
dnl try to focus
|
|
||||||
R$+ $: <> $>3 $1
|
|
||||||
R<> $+ < @ $+ . > $: < $1 @ $2 >
|
|
||||||
R<> $+ < @ $+ > $: < $1 @ $2 >
|
|
||||||
dnl if unable to focus, rest of check_mail should take care (may be we should reject)
|
|
||||||
R<> $* $@ OK
|
|
||||||
R< $+ @ $+ > $: < $1 @ $2 > $>verifier0 < $1 @ $2 >
|
|
||||||
R< $+ @ $+ > < cont > $: < $1 @ $2 > $>verifier1 < $1 @ $2 >
|
|
||||||
R< $+ @ $+ > $# $* $# $3
|
|
||||||
R< $+ @ $+ > < ok > $@ OK
|
|
||||||
dnl if we get here, some is wrong with our code
|
|
||||||
R$* $#error $@ 4.7.1 $: "451 Local configuration error <sv2>"
|
|
Loading…
x
Reference in New Issue
Block a user