241 lines
7.9 KiB
Python
241 lines
7.9 KiB
Python
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
|
|
|