initial upload

This commit is contained in:
whottgen 2004-09-20 19:34:09 +00:00
parent 5de3c72eab
commit 6f06324736
41 changed files with 5416 additions and 0 deletions

37
smmapd_prototype/Cache.py Normal file
View File

@ -0,0 +1,37 @@
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()

View File

@ -0,0 +1,27 @@
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>"

View File

@ -0,0 +1,14 @@
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)

View File

@ -0,0 +1,8 @@
Cache.py
Logging.py
SendmailSocketMapHandler.py
VerifierHandler.py
setup.py
smmapd
smmapd.ini
verifysender.m4

View File

@ -0,0 +1,190 @@
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

View File

@ -0,0 +1,240 @@
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

327
smmapd_prototype/index.html Normal file
View File

@ -0,0 +1,327 @@
<!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: &lt;&gt;
[Verifier2]
ContainerModule: VerifierHandler
ContainerClass: VerifierHandlerContainer
WorkerModule: VerifierHandler
WorkerClass: VerifierHandlerWorker
EnableCaching: 1
CacheExpiration: 20
SMTPTimeOut: 20
SMTPHeloParam: hottis.de
SMTPCheckSender: &lt;postmaster@hottis.de&gt;</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>

195
smmapd_prototype/index.wiki Normal file
View File

@ -0,0 +1,195 @@
#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

19
smmapd_prototype/setup.py Normal file
View File

@ -0,0 +1,19 @@
#!/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'])]
)

65
smmapd_prototype/smmapd Executable file
View File

@ -0,0 +1,65 @@
#!/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")

View File

@ -0,0 +1,37 @@
[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

View File

@ -0,0 +1,424 @@
####
# 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

View File

@ -0,0 +1,110 @@
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>"

69
smmapdfw/Makefile Normal file
View File

@ -0,0 +1,69 @@
MAIN_OBJ = smmapd.o containers.o queue.o count.o config.o safe_write.o
MOD_OBJS = test_workers.so
ALLDEPEND = Makefile
COMMON_CFLAGS = -g
COMMON_LDFLAGS = -g
OS = $(shell uname)
RELEASE_ID = $(shell ./extract_release_id)
ifeq ($(OS), FreeBSD)
BDB_INC ?= /usr/local/include/db41
BDB_LIB ?=
CFLAGS = $(COMMON_CFLAGS) -DFREEBSD -D_THREAD_SAFE -I$(BDB_INC)
LDFLAGS = $(COMMON_LDFLAGS) -pthread
SHARED = -shared
endif
ifeq ($(OS), Linux)
BDB_INC ?= /usr/include
BDB_LIB ?= /usr/lib/libdb.a
CFLAGS = $(COMMON_CFLAGS) -DLINUX -I$(BDB_INC)
LDFLAGS = $(COMMON_LDFLAGS) -lresolv -pthread -ldl
SHARED = -shared
endif
ifeq ($(OS), SunOS)
BDB_INC ?= /prod/BrklyDB4/current/include
BDB_LIB ?= /prod/BrklyDB4/current/lib/libdb.a
CFLAGS = $(COMMON_CFLAGS) -DSUNOS -D_REENTRANT -I$(BDB_INC)
LDFLAGS = $(COMMON_LDFLAGS) -lresolv -lpthread -ldl -lsocket -lnsl
SHARED = -G
endif
LIBS =
MOD_LIBS =
CC = gcc
all: smmapd test_workers.so verify_worker.so cyrus_worker.so
smmapd: $(MAIN_OBJ)
$(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
test_workers.so: test_workers.o config_public.o
$(CC) $(LDFLAGS) $(SHARED) -o $@ $^
verify_worker.so: verify_worker.o config_public.o dns.o queue.o smtp.o $(BDB_LIB)
$(CC) $(LDFLAGS) $(SHARED) -o $@ $^
cyrus_worker.so: cyrus_worker.o config_public.o smtp.o dns.o
$(CC) $(LDFLAGS) $(SHARED) -o $@ $^
clean:
-rm -f *o *so smmapd
distclean: clean
-rm -r smmapd-$(RELEASE_ID)
-rm smmapd-$(RELEASE_ID).tar.gz
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
tar:
mkdir smmapd-$(RELEASE_ID) && \
cp *.c *.h *.m4 smmapd.ini Makefile README smmapd-$(RELEASE_ID) && \
chmod u+w smmapd-$(RELEASE_ID)/* && \
tar -czf smmapd-$(RELEASE_ID).tar.gz smmapd-$(RELEASE_ID)

158
smmapdfw/README Normal file
View File

@ -0,0 +1,158 @@
$Id$
smmapd - A framework for workers for the sendmail socket map
------------------------------------------------------------
With release 8.13 sendmail introduces the socket map type. This is an
easy-to-use interface to other processes, performing tasks using data
provided by sendmail through the map and returning data to sendmail,
influencing the routing process.
smmapd is completely written in C, after a prototype written in Python
was used to prove the concept. (The prototype can be found here:
http://www.hottis.de/web/verifier/index.html)
An interface to plugin workers is provided. These workers perform the
"interesting" work.
The framework itself does actually nothing than communicating with
sendmail. It receives requests from sendmail, dispatches the requests
to the workers and sends back the data returned from the selected
worker to sendmail.
Thereby, the workers can completely concentrate on their particular
task and completely ignore the communication with sendmail, including
server-stuff, communication protocol and so on.
Currently two plugins are included in the tarball: an address verifier
(comparable to exim's or postfix's callback functionality) and a Cyrus
IMAP mailbox verifier. For both plugins m4 files to be used in the
sendmail configuration are included too.
Building smmapd
---------------
Currently only a Makefile is available, no autoconf and so on.
On Debian and FreeBSD it should build out-of-the-box, on Solaris it
builds also. Have a look into the Makefile and adapt it to your
environment.
The installation is currently not supported by the Makefile. Copy the
smmapd binary, the *.so files (plugins) and the smmapd.ini
configuration file into the desired directories.
Configuration
-------------
# every configuration not related to a particular
# plugin is in the global section
[global]
# shall we run in background
do_fork = 1
# where to place the pidfile
pid_file = smmapd.pid
# on which address shall we listen (currently
# IN_ANYADDR can not be configured)
address = 127.0.0.1
# on which port shall we listen
port = 8887
# from shall we load plugins (currently only one
# location)
plugin_dir = /home/who/Sources/private/smmapd
# which plugins should be loaded
plugins = test_worker1 test_worker2 verifier cyruscheck
# the section identifier of a plugin must match to the map
# name in the sendmail.cf
[verifier]
# the shared object file containing the plugin
obj = verify_worker.so
# verifier specific configuration begins here
# how long should we wait for an result before
# answering to sendmail (time in seconds)
timeout_result = 5
# timeout for the smtp dialog when verifying an
# address
timeout_dialog = 20
# should results be cached
cache_enabled = 1
# how long should an result be kept in the cache
# (time in seconds)
cache_expiry = 86400
# sender address for the smtp dialog (angle brackets
# are required
sender_address = <>
# argument for the ehlo in the smtp dialog
ehlo_arg = local
# what is the smtp port?
smtp_port = 25
# for each best MX of a domain one checker thread
# will be started when verifying an address, how
# many should be started at maximum
max_checker_threads = 100
[cyruscheck]
obj = cyrus_worker.so
# how long should we wait for an answer of the depot
timeout = 10
# sender address for the lmtp dialog (angle brackets
# are required)
sender_address = <testsender>
# argument for the lhlo command
lhlo_arg = local
# what is your lmtp port?
lmtp_port = 24
Running smmapd
--------------
The following commandline options are supported:
-F run in foreground, overwrites do_fork from configuration
file
-p location of the pidfile, overwrites pidfile option from
the configuration file
-f location of the configuration file
-v print version and release information
-h print help screen
Programming interface
---------------------
will be documented later
Wolfgang Hottgenroth <woho@hottis.de>

262
smmapdfw/config.c Normal file
View File

@ -0,0 +1,262 @@
#include <stdio.h>
#include <stdlib.h>
#include <syslog.h>
#include "config.h"
#ifdef SUNOS
#include "sunos_comp.h"
#endif
#define LF 10
#define STATE_START 0
#define STATE_COMMENT 1
#define STATE_SECTION 2
#define STATE_NAME 3
#define STATE_EQUAL 4
#define STATE_VALUESTART 5
#define STATE_VALUE 6
#define BUFSIZE 1024
config_section_t *readcfg(char *cfgfile) {
FILE *f;
char buffer[BUFSIZE+1];
char c;
int state = STATE_START;
int i = 0;
char tmp;
config_section_t *cfg = NULL;
config_section_t *cs_head;
config_section_t *cs;
config_item_t *ci_head;
config_item_t *ci;
f = fopen(cfgfile, "r");
if (NULL == f) {
syslog(LOG_ERR, "readcfg: unable to open config file");
return NULL;
}
while (EOF != (c = fgetc(f))) {
/* printf("read: (%d) (%d) (%c)\n", state, c, c); */
switch (state) {
case STATE_START:
if ((';' == c) || ('#' == c)) {
state = STATE_COMMENT;
break;
} else if ('[' == c) {
state = STATE_SECTION;
i = 0;
break;
} else if (LF == c) {
break;
} else {
buffer[i++] = c;
state = STATE_NAME;
}
case STATE_COMMENT:
if (LF == c)
state = STATE_START;
break;
case STATE_SECTION:
if (']' == c) {
buffer[i] = '\0';
/* printf("section: (%s)\n", buffer); */
state = STATE_START;
i = 0;
cs = (config_section_t*) malloc(sizeof(config_section_t));
cs->next = NULL;
cs->name = (char*) malloc(strlen(buffer)+1);
strcpy(cs->name, buffer);
cs->item = NULL;
if (NULL == cfg) {
cfg = cs;
cs_head = cfg;
} else {
cs_head->next = cs;
cs_head = cs;
}
break;
} else if (LF == c) {
syslog(LOG_ERR, "readcfg: unexpected EOL in section");
freecfg(cfg);
fclose(f);
return NULL;
} else {
buffer[i++] = c;
break;
}
case STATE_NAME:
if (isblank(c)) {
buffer[i] = '\0';
/* printf("name: (%s)\n", buffer); */
state = STATE_EQUAL;
i = 0;
ci = (config_item_t*) malloc(sizeof(config_item_t));
ci->next = NULL;
ci->name = (char*) malloc(strlen(buffer)+1);
strcpy(ci->name, buffer);
ci->value = NULL;
if (NULL == cs->item) {
cs->item = ci;
ci_head = ci;
} else {
ci_head->next = ci;
ci_head = ci;
}
break;
} else if (LF == c) {
syslog(LOG_ERR, "readcfg: unexpected EOL in name");
freecfg(cfg);
fclose(f);
return NULL;
} else {
buffer[i++] = c;
break;
}
case STATE_EQUAL:
if ('=' == c) {
state = STATE_VALUESTART;
break;
} else if (isblank(c)) {
break;
} else {
syslog(LOG_ERR, "readcfg: unexpected character in equal");
freecfg(cfg);
fclose(f);
return NULL;
}
case STATE_VALUESTART:
if (isblank(c)) {
break;
} else if (LF == c) {
syslog(LOG_ERR, "readcfg: unexpected EOL in valuestart");
freecfg(cfg);
fclose(f);
return NULL;
} else {
buffer[i++] = c;
state = STATE_VALUE;
break;
}
case STATE_VALUE:
if (LF == c) {
buffer[i] = '\0';
/* printf("value: (%s)\n", buffer); */
state = STATE_START;
i = 0;
ci->value = (char*) malloc(strlen(buffer)+1);
strcpy(ci->value, buffer);
break;
} else {
buffer[i++] = c;
break;
}
}
}
fclose(f);
if (STATE_START != state) {
syslog(LOG_ERR, "readcfg: unexpected EOF");
freecfg(cfg);
fclose(f);
return NULL;
}
return cfg;
}
void printcfg(config_section_t *cfg) {
config_section_t *cs;
config_item_t *ci;
for (cs = cfg; cs != NULL; cs = cs->next) {
printf("section: %s\n", cs->name);
for (ci = cs->item; ci != NULL; ci = ci->next) {
printf(" item: %s -> %s\n", ci->name, ci->value);
}
}
}
void freecfg(config_section_t *cfg) {
config_section_t *cs, *csf;
config_item_t *ci, *cif;
cs = cfg;
while (NULL != cs) {
ci = cs->item;
while (NULL != ci) {
cif = ci;
ci = ci->next;
free(cif->name);
free(cif->value);
free(cif);
}
csf = cs;
cs = cs->next;
free(csf->name);
free(csf);
}
}
config_item_t *findcfgsection(config_section_t *cfg, char *section) {
config_section_t *cs;
for (cs = cfg; cs != NULL; cs = cs->next)
if (0 == strcmp(section, cs->name))
return cs->item;
return NULL;
}
char *findcfg(config_section_t *cfg, char *section, char *name) {
config_item_t *ci;
for (ci = findcfgsection(cfg, section); ci != NULL; ci = ci->next)
if (0 == strcmp(name, ci->name))
return ci->value;
return NULL;
}
char *findcfgx(config_section_t *cfg, char *section, char *name, char *default_value) {
char *r = findcfg(cfg, section, name);
return (NULL != r) ? r : default_value;
}
#ifdef _TEST_MODE_
int main() {
config_section_t *cfg = readcfg("smmapd.ini");
printcfg(cfg);
printf("test1: %s\n", findcfg(cfg, "test_worker1", "obj"));
printf("test2: %s\n", findcfg(cfg, "global", "port"));
printf("test3: %s\n", findcfg(cfg, "nix", "obj"));
printf("test4: %s\n", findcfg(cfg, "global", "nix"));
printf("test5: %s\n", findcfgx(cfg, "global", "test", "test default"));
freecfg(cfg);
}
#endif /* _TEST_MODE_ */

37
smmapdfw/config.h Normal file
View File

@ -0,0 +1,37 @@
#ifndef _CONFIG_H_
#define _CONFIG_H_
struct config_item_s {
char *name;
char *value;
struct config_item_s *next;
};
typedef struct config_item_s config_item_t;
struct config_section_s {
char *name;
config_item_t *item;
struct config_section_s *next;
};
typedef struct config_section_s config_section_t;
typedef config_section_t cfg_t;
typedef config_item_t cfgl_t;
config_section_t *readcfg(char *cfgfile);
void freecfg(config_section_t *cfg);
char *findcfg(config_section_t *cfg, char *section, char *name);
char *findcfgx(config_section_t *cfg, char *section, char *name, char *default_value);
config_item_t *findcfgsection(config_section_t *cfg, char *section);
extern char *findcfgl(config_item_t *cfg, char *name);
extern char *findcfglx(config_item_t *cfg, char *name, char *default_value);
#endif /* _CONFIG_H_ */

17
smmapdfw/config_public.c Normal file
View File

@ -0,0 +1,17 @@
#include <string.h>
#include "config.h"
char *findcfgl(config_item_t *cfg, char *name) {
config_item_t *ci;
for (ci = cfg; ci != NULL; ci = ci->next)
if (0 == strcmp(name, ci->name))
return ci->value;
return NULL;
}
char *findcfglx(config_item_t *cfg, char *name, char *default_value) {
char *r = findcfgl(cfg, name);
return (NULL != r) ? r : default_value;
}

252
smmapdfw/containers.c Normal file
View File

@ -0,0 +1,252 @@
#include <syslog.h>
#include <string.h>
#include <stdlib.h>
#include <dlfcn.h>
#include "containers.h"
#include "config.h"
#include "smmapd.h"
#define CFG_SECTION_GLOBAL "global"
#define CFG_NAME_PLUGIN_DIR "plugin_dir"
#define CFG_NAME_PLUGINS "plugins"
#define CFG_PLUGINS_DELIMITER " "
#define CFG_NAME_OBJ "obj"
extern cfg_t *cfg;
classes_t classes_root = {NULL, 0, NULL, NULL};
classes_t *classes_head = &classes_root;
int containers_setup(container_handle_t **ch) {
*ch = (container_handle_t*)malloc(sizeof(container_handle_t));
(*ch)->worker_handle_root.next = NULL;
return 0;
}
int containers_destroy(container_handle_t *ch) {
worker_handle_t *wh, *wh2;
classes_t *classes;
int err;
syslog(LOG_DEBUG, "containers_destroy: going to free container_handle, first the worker handles");
wh = ch->worker_handle_root.next;
while (NULL != wh) {
for (classes = classes_root.next; classes != NULL; classes = classes->next) {
if (classes->id == wh->id) {
if (NULL != classes->descr->work_destroy_function) {
syslog(LOG_DEBUG, "containers_destroy: calling work_destroy_function for class %s (%d)",
classes->descr->name, classes->id);
err = (*classes->descr->work_destroy_function)(classes->handle, wh->handle);
}
}
}
wh2 = wh;
wh = wh->next;
free(wh2);
}
free(ch);
syslog(LOG_DEBUG, "containers_destroy: all freed");
}
int containers_dispatcher(container_handle_t *ch, char *input, char *output) {
char *class;
char *data;
classes_t *classes;
int result, err;
worker_handle_t *wh, *wh_last, *wh2;
syslog(LOG_DEBUG, "dispatcher: input: %s", input);
data = strchr(input, ' ');
if (NULL == data) {
syslog(LOG_ERR, "dispatcher: illegal input (%s)", input);
return SMM_ILLEGAL_INPUT;
}
data++; /* skip the blank */
class = input;
class[strlen(class)-strlen(data)-1] = '\0';
syslog(LOG_DEBUG, "dispatcher: class: %s, data: %s", class, data);
for (classes = classes_root.next; classes != NULL; classes = classes->next) {
if (0 == strcmp(class, classes->descr->name)) {
syslog(LOG_DEBUG, "dispatcher: yes, we support it, it's id=%d", classes->id);
for (wh = ch->worker_handle_root.next, wh_last = &ch->worker_handle_root, wh2 = NULL;
wh != NULL;
wh = wh->next) {
wh_last = wh;
if (wh->id == classes->id) {
syslog(LOG_DEBUG, "dispatcher: we already have a worker handle");
wh2 = wh;
break;
}
}
if ((NULL == wh_last->next) && (NULL == wh2)) {
syslog(LOG_DEBUG, "dispatcher: we haven't one, we create one");
wh2 = (worker_handle_t*)malloc(sizeof(worker_handle_t));
wh2->id = classes->id;
if (NULL != classes->descr->work_setup_function) {
err = (*classes->descr->work_setup_function)(classes->handle, &(wh2->handle));
} else {
wh2->handle = NULL;
}
wh2->next = NULL;
wh_last->next = wh2;
}
result = (*classes->descr->work_function)(classes->handle, wh2->handle, data, output);
syslog(LOG_DEBUG, "dispatcher: worker output: (%d, %s)", result, output);
break;
}
}
if (NULL == classes) {
syslog(LOG_ERR, "dispatcher: unknown class: %s", class);
return SMM_UNKNOWN_CLASS;
}
return result;
}
static int register_class(int id, class_descriptor_t *class_descriptor) {
int result = 0;
cfgl_t *c;
classes_t *w;
syslog(LOG_DEBUG, "register_class: registering class %s", class_descriptor->name);
c = findcfgsection(cfg, class_descriptor->name);
if (NULL == c) {
syslog(LOG_ERR, "register_class: no configuration section for this plugin available");
return -1;
}
w = (classes_t *) malloc(sizeof(classes_t));
if (NULL == w) {
syslog(LOG_ERR, "register_class: unable to alloc memory");
return -2;
}
if (NULL != class_descriptor->init_function) {
syslog(LOG_DEBUG, "register_class: initializing class %s", class_descriptor->name);
result = (*class_descriptor->init_function)(c, &w->handle);
if (0 != result) {
syslog(LOG_ERR, "register_class: unable to init class %s, exit", class_descriptor->name);
return -3;
}
} else {
w->handle = NULL;
}
w->descr = class_descriptor;
w->id = id;
w->next = NULL;
classes_head->next = w;
classes_head = w;
return 0;
}
static int unregister_class(classes_t *class) {
int result = 0;
syslog(LOG_DEBUG, "unregister_class: unregistering class %s", class->descr->name);
if (NULL != class->descr->destroy_function) {
syslog(LOG_DEBUG, "unregister_class: destroying class");
result = (*class->descr->destroy_function)(&class->handle);
syslog(LOG_ERR, "unregister_class: destroy function returns %d", result);
}
return 0;
}
int unregister_all() {
classes_t *classes, *cf;
classes = classes_root.next;
while (NULL != classes) {
cf = classes;
classes = classes->next;
unregister_class(cf);
free(cf);
}
}
int register_all() {
void *dl_handle;
class_descriptor_t * class_descriptor;
char *cfg_plugin_dir, *cfg_plugins, *cfg_plugin, *cfg_obj;
char *obj_name;
const char *err_msg;
int err;
int id = 0;
cfg_plugin_dir = findcfg(cfg, CFG_SECTION_GLOBAL, CFG_NAME_PLUGIN_DIR);
if (NULL == cfg_plugin_dir) {
syslog(LOG_ERR, "register_all: no plugin dir configured");
return -1;
}
cfg_plugins = findcfg(cfg, CFG_SECTION_GLOBAL, CFG_NAME_PLUGINS);
if (NULL == cfg_plugins) {
syslog(LOG_ERR, "register_all: no plugins configured");
return -2;
}
while (NULL != (cfg_plugin = strtok(cfg_plugins, CFG_PLUGINS_DELIMITER))) {
cfg_plugins = NULL; /* this is for subsequence calls of strtok */
dlerror(); /* this is to clear old errors */
cfg_obj = findcfg(cfg, cfg_plugin, CFG_NAME_OBJ);
if (NULL == cfg_obj) {
syslog(LOG_ERR, "register_all: obj for plugin %s not configured", cfg_plugin);
return -3;
}
obj_name = (char*) malloc(strlen(cfg_plugin_dir) + strlen(cfg_obj) + 5);
obj_name[0] = '\0';
strcat(obj_name, cfg_plugin_dir);
strcat(obj_name, "/");
strcat(obj_name, cfg_obj);
dl_handle = dlopen(obj_name, RTLD_NOW);
if (NULL == dl_handle) {
syslog(LOG_ERR, "register_all: error when dlopen: %s", dlerror());
free(obj_name);
return -4;
}
free(obj_name);
class_descriptor = (class_descriptor_t*) dlsym(dl_handle, cfg_plugin);
if (NULL != (err_msg = dlerror())) {
syslog(LOG_ERR, "register_all: plugin %s not found, error %s", cfg_plugin, err_msg);
return -5;
}
err = register_class(id++, class_descriptor);
if (0 != err) {
syslog(LOG_ERR, "register_all: unable to initialize plugin %s, error %d", cfg_plugin, err);
return -6;
}
}
return 0;
}

39
smmapdfw/containers.h Normal file
View File

@ -0,0 +1,39 @@
#ifndef _CONTAINERS_H_
#define _CONTAINERS_H_
#define _SMMAPD_
#include "containers_public.h"
struct classes_s {
class_descriptor_t *descr;
int id;
void *handle;
struct classes_s *next;
};
typedef struct classes_s classes_t;
struct worker_handle_s {
int id;
void *handle;
struct worker_handle_s *next;
};
typedef struct worker_handle_s worker_handle_t;
struct container_handle_s {
worker_handle_t worker_handle_root;
};
typedef struct container_handle_s container_handle_t;
int containers_setup(container_handle_t **ch);
int containers_destroy(container_handle_t *ch);
int containers_dispatcher(container_handle_t *ch, char *input, char *output);
int register_all();
#endif /* _CONTAINERS_H_ */

View File

@ -0,0 +1,19 @@
#ifndef _CONTAINERS_PUBLIC_H_
#define _CONTAINERS_PUBLIC_H_
#include "config.h"
struct class_descriptor_s {
char *name;
int (*init_function)(cfgl_t *cfg, void **handle);
int (*destroy_function)(void *handle);
int (*work_setup_function)(void *handle, void **work_handle);
int (*work_function)(void *handle, void *work_handle, char *input, char *output);
int (*work_destroy_function)(void *handle, void *work_handle);
};
typedef struct class_descriptor_s class_descriptor_t;
#endif /* _CONTAINERS_PUBLIC_H_ */

41
smmapdfw/count.c Normal file
View File

@ -0,0 +1,41 @@
#include "count.h"
void count_init(count_t *c) {
c->cnt = 0;
pthread_mutex_init(&c->mutex, NULL);
}
void count_destroy(count_t *c) {
pthread_mutex_destroy(&c->mutex);
}
int count_inc(count_t *c) {
int i;
pthread_mutex_lock(&c->mutex);
i = ++c->cnt;
pthread_mutex_unlock(&c->mutex);
return i;
}
int count_dec(count_t *c) {
int i;
pthread_mutex_lock(&c->mutex);
i = --c->cnt;
pthread_mutex_unlock(&c->mutex);
return i;
}
int count_get(count_t *c) {
int i;
pthread_mutex_lock(&c->mutex);
i = c->cnt;
pthread_mutex_unlock(&c->mutex);
return i;
}

21
smmapdfw/count.h Normal file
View File

@ -0,0 +1,21 @@
#ifndef _COUNT_H_
#define _COUNT_H_
#include <pthread.h>
struct count_s {
int cnt;
pthread_mutex_t mutex;
};
typedef struct count_s count_t;
void count_init(count_t *c);
void count_destroy(count_t *c);
int count_inc(count_t *c);
int count_dec(count_t *c);
int count_get(count_t *c);
#endif /* _COUNT_H_ */

230
smmapdfw/cyrus_worker.c Normal file
View File

@ -0,0 +1,230 @@
#include <stdlib.h>
#include <syslog.h>
#include <string.h>
#include "containers_public.h"
#include "smmapd.h"
#include "smtp.h"
#include "dns.h"
#define SMM_LOCAL_PERM_NOK 101
#define SMM_LOCAL_TEMP_NOK 102
#define SMM_LOCAL_OK 103
struct cyrus_container_handle_s {
cfgl_t *cfg;
int timeout;
char *sender_address;
char *lhlo_arg;
int lmtp_port;
};
typedef struct cyrus_container_handle_s cyrus_container_handle_t;
struct cyrus_work_handle_s {
int id;
cyrus_container_handle_t *cch;
};
typedef struct cyrus_work_handle_s cyrus_work_handle_t;
int cyrus_init(cfgl_t *cfg, void **handle);
int cyrus_destroy(void *handle);
/* int cyrus_work_setup(void *handle, void **work_handle); */
int cyrus_work(void *handle, void *work_handle, char *input, char *output);
/* int cyrus_work_destroy(void *handle, void *work_handle); */
class_descriptor_t cyruscheck = {
"cyruscheck",
&cyrus_init,
&cyrus_destroy,
NULL, /* &verify_work_setup, */
&cyrus_work,
NULL /* &cyrus_work_destroy */
};
int cyrus_init(cfgl_t *cfg, void **handle) {
cyrus_container_handle_t *cch;
cch = (cyrus_container_handle_t*) malloc(sizeof(cyrus_container_handle_t));
cch->cfg = cfg;
cch->timeout = atoi(findcfglx(cch->cfg, "timeout", "5"));
cch->sender_address = findcfglx(cch->cfg, "sender_address", "<>");
cch->lhlo_arg = findcfglx(cch->cfg, "lhlo_arg", "local");
cch->lmtp_port = atoi(findcfglx(cch->cfg, "lmtp_port", "24"));
*handle = cch;
return 0;
}
int cyrus_destroy(void *handle) {
cyrus_container_handle_t *cch = (cyrus_container_handle_t*)handle;
free(cch);
return 0;
}
int cyrus_work(void *handle, void *work_handle, char *input, char *output) {
static const char *DEFAULT_ANSWER = "default answer";
static const char *ILLEGAL_INPUT = "illegal input (must be 'depot_uid depot_host')";
static const char *UNEXPECTED_ERROR = "unexpected error in lmtp dialog";
static const char *TIMEOUT_ERROR = "timeout on lmtp dialog";
static const char *GO_AHEAD = "go ahead";
static const char *DEPOT_DNS_ERROR = "depot could not be found in dns";
cyrus_container_handle_t *cch = (cyrus_container_handle_t*) handle;
int result = SMM_TEMP_NOK;
char *depot_uid, *depot_host, *response_text, *tmp_arg;
smtp_t *lmtp;
a_rdata_t **a_rdata;
int ip_address, done=0, err;
enum {
CONNECT, LHLO, MAILFROM, RCPTTO, RSET, QUIT, END
} state = CONNECT;
syslog(LOG_DEBUG, "cyrus_work: going to check %s", input);
depot_uid = input;
if (NULL == (depot_host = strchr(depot_uid, ' '))) {
snprintf(output, ANSWER_BUFSIZE, ILLEGAL_INPUT);
return SMM_PERM_NOK;
}
*depot_host = '\0';
depot_host++;
a_rdata = get_a_rrs(depot_host);
if (NULL == a_rdata) {
syslog(LOG_DEBUG, "cyrus_work: depot_host %s could not be found in dns",
depot_host);
snprintf(output, ANSWER_BUFSIZE, DEPOT_DNS_ERROR);
return SMM_TEMP_NOK;
}
ip_address = (*a_rdata)->address;
free_rrs((void**)a_rdata);
syslog(LOG_DEBUG, "cyrus_work: depot_uid %s, depot_host %s", depot_uid, depot_host);
lmtp = smtp_init(ip_address, cch->lmtp_port, cch->timeout);
while ((END != state) && (0 == done)) {
syslog(LOG_DEBUG, "cyrus_work, lmtp dialog state %d", state);
switch(state) {
case CONNECT:
err = smtp_connect(lmtp);
break;
case LHLO:
err = smtp_lhlo(lmtp, cch->lhlo_arg);
break;
case MAILFROM:
err = smtp_mailfrom(lmtp, cch->sender_address);
break;
case RCPTTO:
tmp_arg = (char*) malloc(sizeof(char) * (strlen(depot_uid)+5));
*tmp_arg = '\0';
strcat(tmp_arg, "<");
strcat(tmp_arg, depot_uid);
strcat(tmp_arg, ">");
err = smtp_rcptto(lmtp, tmp_arg);
free(tmp_arg);
break;
case RSET:
err = smtp_rset(lmtp);
break;
case QUIT:
err = smtp_quit(lmtp);
break;
}
state++;
switch(err) {
case SMTP_TIMEOUT:
syslog(LOG_DEBUG, "cyrus_work, timeout in lmtp dialog");
result = SMM_LOCAL_TEMP_NOK;
response_text = (char*)TIMEOUT_ERROR;
done = 1;
break;
case 0:
/* evaluate smtp_response, return or continue */
err = smtp_response(lmtp, &response_text);
if (-1 == err) {
syslog(LOG_DEBUG, "cyrus_work, response could not be parsed");
result = SMM_LOCAL_TEMP_NOK;
response_text = (char*)UNEXPECTED_ERROR;
done = 1;
break;
}
syslog(LOG_DEBUG, "cyrus_work, response: %d, %s", err, response_text);
switch(err/100) {
case 4:
result = SMM_LOCAL_TEMP_NOK;
done = 1;
break;
case 5:
result = SMM_LOCAL_PERM_NOK;
done = 1;
break;
case 2:
if (END == state) {
result = SMM_LOCAL_OK;
response_text = (char*)GO_AHEAD;
done = 1;
break;
}
}
break;
default:
syslog(LOG_DEBUG, "cyrus_work, unexpected error in lmtp dialog");
result = SMM_LOCAL_TEMP_NOK;
response_text = (char*)UNEXPECTED_ERROR;
done = 1;
break;
}
}
smtp_close(lmtp);
switch (result) {
case SMM_LOCAL_TEMP_NOK:
snprintf(output, ANSWER_BUFSIZE, "<TNOK><%s>", response_text);
result = SMM_OK;
break;
case SMM_LOCAL_PERM_NOK:
snprintf(output, ANSWER_BUFSIZE, "<NOK><%s>", response_text);
result = SMM_OK;
break;
case SMM_LOCAL_OK:
snprintf(output, ANSWER_BUFSIZE, "<OK><%s>", response_text);
result = SMM_OK;
break;
default:
snprintf(output, ANSWER_BUFSIZE, response_text);
break;
}
smtp_destroy(lmtp);
return result;
}

54
smmapdfw/cyruscheck.m4 Normal file
View File

@ -0,0 +1,54 @@
VERSIONID(`$Id$')
divert(-1)
define(`_USAGE_', `dnl
errprint(`*** ERROR: missing argument for FEATURE(cyruscheck):
Usage: FEATURE(`cyruscheck', `_dummy_')
_dummy_: active (actually returning errors) or dummy (just log what it would return)
found: $1
')')
ifelse(_ARG_, `dummy', `', `
ifelse(_ARG_, `active', `', `
_USAGE_(`_dummy_: ('_ARG_`)
')
')')
define(`_dummy_', _ARG_)
dnl errprint(`*** _dummy_: '_dummy_`
dnl ')
ifelse(defn(`confCYRUSCHECK_MAP'), `', `
define(`_CYRUSCHECK_MAP_', `inet:8884@127.0.0.1')', `
define(`_CYRUSCHECK_MAP_', confCYRUSCHECK_MAP)')
divert(0)
LOCAL_CONFIG
# Adjust the port
Kcyruscheck socket -T<temp> _CYRUSCHECK_MAP_
Kcht_logger syslog
LOCAL_RULESETS
# This ruleset can be used to test the verifier in -bt mode
Scc
R< $+ > < $+ > $: < $(cyruscheck $1 $2 $:none $) >
Scyruscheck
R< $+ > < $+ > $: < $1 > < $2 > < $(cyruscheck $1 $2 $:none $) >
ifelse(_dummy_, `dummy', `dnl
dnl dummy
R< $+ > < $+ > < $+ > $@ < ok > $(cht_logger $1 -- $2 --- $3 $)', `dnl
dnl active
R< $+ > < $+ > < $+ > $: < $3 >
R< <OK> $* > $@ < ok >
R< <NOK> $* > $#error $@ 5.0.0 $: "500 Depot returns error: " $1
R< <TNOK> $* > $#error $@ 4.0.0 $: "400 Depot returns error: " $1
R$* $#error $@ 4.7.1 $: "451 Local configuration error <cc1>"')

309
smmapdfw/dns.c Normal file
View File

@ -0,0 +1,309 @@
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/nameser.h>
#include <resolv.h>
#include <stdlib.h>
#include <syslog.h>
#include <stdio.h>
#include <strings.h>
#include <errno.h>
#include <assert.h>
#include "dns.h"
extern int h_errno;
/* extern struct state _res; */
union answer_u {
HEADER hdr;
u_char buf[PACKETSZ+1];
};
typedef union answer_u answer_t;
#define get32_x(b, o) htonl((b[o+3] << 24) + (b[o+2] << 16) + (b[o+1] << 8) + b[o])
#define get32(b, o) ((b[o] << 24) + (b[o+1] << 16) + (b[o+2] << 8) + b[o+3])
#define get16(b, o) ((b[o] << 8) + b[o+1])
static int get_domain_name(answer_t *answer, int offset, char **name) {
int start, len, i, offset2;
char *name_buf, *name_buf2;
name_buf = NULL;
while (0 != (len = answer->buf[offset++])) {
if (0xC0 == (len & 0xC0)) {
if (NULL != name) { /* if we don't need the result, we don't need to recurse, since a ... */
offset2 = ((len & ~0xC0) << 8) + answer->buf[offset++];
get_domain_name(answer, offset2, &name_buf2);
name_buf = (char*) realloc(name_buf, ((NULL != name_buf) ? strlen(name_buf) : 0) + strlen(name_buf2) + 1);
strcat(name_buf, name_buf2);
free(name_buf2);
*name = name_buf;
} else {
offset++; /* ... a recursion pointer is always two octets long ... */
}
return offset; /* ... and is always the final part of a name */
} else {
start = offset;
offset += len;
if (NULL != name) {
i = (NULL != name_buf) ? strlen(name_buf) : 0;
name_buf = (char*) realloc(name_buf, i+len+2);
strncpy(name_buf + i, answer->buf + start, len);
name_buf[i+len] = '\0';
strcat(name_buf, ".");
}
}
}
if (NULL != name) {
name_buf[strlen(name_buf)-1] = '\0'; /* remove the final dot */
*name = name_buf;
}
return offset;
}
static int get_mx_rdata(answer_t *answer, int offset, int len, mx_rdata_t **resp) {
*resp = (mx_rdata_t*) malloc(sizeof(mx_rdata_t));
(*resp)->preference = get16(answer->buf, offset);
get_domain_name(answer, offset+2, &(*resp)->exchange);
(*resp)->type = T_MX;
return 0;
}
static void free_mx_rdata(mx_rdata_t *resp) {
free(resp->exchange);
free(resp);
}
static int get_a_rdata(answer_t *answer, int offset, int len, a_rdata_t **resp) {
*resp = (a_rdata_t*) malloc(sizeof(a_rdata_t));
(*resp)->address = htonl(get32(answer->buf, offset));
(*resp)->type = T_A;
return 0;
}
static void free_a_rdata(a_rdata_t *resp) {
free(resp);
}
static int get_rdata(answer_t *answer, int type, int offset, int len, void **resp) {
switch (type) {
case T_MX:
get_mx_rdata(answer, offset, len, (mx_rdata_t**)resp);
break;
case T_A:
get_a_rdata(answer, offset, len, (a_rdata_t**)resp);
break;
default:
syslog(LOG_ERR, "type %d unsupported\n", type);
}
return 0;
}
static void free_rdata(void *resp) {
rdata_t *d = (rdata_t*)resp;
switch (d->type) {
case T_MX:
free_mx_rdata(resp);
break;
case T_A:
free_a_rdata(resp);
break;
default:
syslog(LOG_ERR, "type %d unsupported\n", d->type);
}
}
void free_rrs(void **resp) {
void **rdata;
for (rdata = resp; *rdata != NULL; rdata++) {
free_rdata(*rdata);
}
free(resp);
}
static void** get_rrs(char *domain, int qtype) {
unsigned int res, i, cnt, len, offset, x, y, rdlength;
answer_t answer;
unsigned int class, type, ttl;
char *name;
void **rdata, **rdata2;
res = res_search(domain, C_IN, qtype, (u_char*) &answer, sizeof(answer_t));
if (-1 == res) {
return NULL;
}
cnt = sizeof(HEADER);
/* query section */
for (y = 0; y < ntohs(answer.hdr.qdcount); y++) {
cnt = get_domain_name(&answer, cnt, NULL);
type = get16(answer.buf, cnt);
cnt += 2;
class = get16(answer.buf, cnt);
cnt += 2;
}
/* answer section */
rdata = (void**)malloc(sizeof(void*) * (ntohs(answer.hdr.ancount)+1));
for (y = 0; y < ntohs(answer.hdr.ancount); y++) {
cnt = get_domain_name(&answer, cnt, NULL);
type = get16(answer.buf, cnt);
assert(type==qtype);
cnt += 2;
class = get16(answer.buf, cnt);
cnt += 2;
ttl = get32(answer.buf, cnt);
cnt += 4;
rdlength = get16(answer.buf, cnt);
cnt += 2;
rdata2 = rdata+y;
get_rdata(&answer, type, cnt, rdlength, rdata2);
cnt += rdlength;
}
rdata2 = rdata+y;
*((void**)rdata2) = NULL;
return rdata;
}
mx_rdata_t** get_mx_rrs(char *domain) {
return (mx_rdata_t**) get_rrs(domain, T_MX);
}
a_rdata_t** get_a_rrs(char *domain) {
return (a_rdata_t**) get_rrs(domain, T_A);
}
#define min(A,B) ((A<B) ? A : B)
mx_rdata_t** get_best_mx_rrs(char *domain) {
mx_rdata_t **all_mx_rrs, **mx_rdata2, **best_mx_rrs;
int min_pref = 10000000;
int all_cnt = 0;
int best_cnt = 0;
int i = 0;
all_mx_rrs = get_mx_rrs(domain);
if (NULL == all_mx_rrs)
return NULL;
/* how much are there at all and what is the minimum preference */
for (mx_rdata2 = all_mx_rrs; *mx_rdata2 != NULL; mx_rdata2++) {
all_cnt++;
min_pref = min(min_pref, (*mx_rdata2)->preference);
}
/* how much are there of the minimum preference */
for (mx_rdata2 = all_mx_rrs; *mx_rdata2 != NULL; mx_rdata2++)
if ((*mx_rdata2)->preference == min_pref)
best_cnt++;
if (all_cnt == best_cnt) {
/* all of them are minimum */
return all_mx_rrs;
} else {
/* space for the minimum pref rr's */
best_mx_rrs = (mx_rdata_t**) malloc(sizeof(mx_rdata_t*) * (best_cnt+1));
for (mx_rdata2 = all_mx_rrs; *mx_rdata2 != NULL; mx_rdata2++) {
if ((*mx_rdata2)->preference == min_pref) {
/* is a minimum one, keep it */
*(best_mx_rrs+i) = *mx_rdata2;
i++;
} else {
/* it's not, free it */
free_mx_rdata(*mx_rdata2);
}
}
/* free the old container */
free(all_mx_rrs);
/* terminate the new container */
*((int**)(best_mx_rrs+i)) = NULL;
return best_mx_rrs;
}
}
/* #define _TEST_MODE_ */
#ifdef _TEST_MODE_
int main(int argc, char **argv) {
char default_domain[] = "test.de";
char *domain = default_domain;
mx_rdata_t **mx_rdata, **mx_rdata2;
a_rdata_t **a_rdata, **a_rdata2;
int a;
if (argc > 1) {
domain = argv[1];
}
printf("before get_mx_rrs: %s\n", domain);
mx_rdata = get_mx_rrs(domain);
for (mx_rdata2 = mx_rdata; *mx_rdata2 != NULL; mx_rdata2++) {
printf("preference: %d, exchange: %s\n", (*mx_rdata2)->preference, (*mx_rdata2)->exchange);
}
free_rrs((void**)mx_rdata);
printf("------------------\n");
mx_rdata = get_best_mx_rrs(domain);
for (mx_rdata2 = mx_rdata; *mx_rdata2 != NULL; mx_rdata2++) {
printf("preference: %d, exchange: %s\n", (*mx_rdata2)->preference, (*mx_rdata2)->exchange);
a_rdata = get_a_rrs((*mx_rdata2)->exchange);
for (a_rdata2 = a_rdata; *a_rdata2 != NULL; a_rdata2++) {
a = (*a_rdata2)->address;
printf("address: %04x\n", a);
printf(" %d.%d.%d.%d\n", (a&0xff000000)>>24, (a&0x00ff0000)>>16, (a&0x0000ff00)>>8, a&0x000000ff);
}
free_rrs((void**)a_rdata);
}
free_rrs((void**)mx_rdata);
/* printf("------------------\n"); */
/* a_rdata = get_a_rrs("www.microsoft.com.nsatc.net"); */
/* for (a_rdata2 = a_rdata; *a_rdata2 != NULL; a_rdata2++) { */
/* a = (*a_rdata2)->address; */
/* printf("address: %04x\n", a); */
/* printf(" %d.%d.%d.%d\n", (a&0xff000000)>>24, (a&0x00ff0000)>>16, (a&0x0000ff00)>>8, a&0x000000ff); */
/* } */
/* free_rrs((void**)a_rdata); */
}
#endif /* _TEST_MODE_ */

40
smmapdfw/dns.h Normal file
View File

@ -0,0 +1,40 @@
#ifndef _DNS_H_
#define _DNS_H_
struct mx_rdata_s {
int type;
int preference;
char *exchange;
};
typedef struct mx_rdata_s mx_rdata_t;
struct a_rdata_s {
int type;
unsigned int address;
};
typedef struct a_rdata_s a_rdata_t;
struct rdata_s {
int type;
union {
mx_rdata_t mx;
a_rdata_t a;
} data;
};
typedef struct rdata_s rdata_t;
void free_rrs(void **resp);
mx_rdata_t** get_mx_rrs(char *domain);
mx_rdata_t** get_best_mx_rrs(char *domain);
a_rdata_t** get_a_rrs(char *domain);
#endif /* _DNS_H_ */

2
smmapdfw/extract_release_id Executable file
View File

@ -0,0 +1,2 @@
#!/bin/sh
cat smmapd.c | grep '^#define RELEASE_ID' | awk '{print $3}' | sed 's/\"//g'

253
smmapdfw/queue.c Normal file
View File

@ -0,0 +1,253 @@
#include "queue.h"
#ifdef _TEST_MODE_
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#endif /* _TEST_MODE_ */
void queue_init(ht_queue_t *q) {
q->head = NULL;
q->tail = NULL;
pthread_mutex_init(&q->mutex, NULL);
pthread_cond_init(&q->cond, NULL);
}
void queue_destroy(ht_queue_t *q) {
pthread_mutex_destroy(&q->mutex);
pthread_cond_destroy(&q->cond);
}
int queue_put(ht_queue_t *q, void *d) {
queue_entry_t *entry;
entry = (queue_entry_t *) malloc(sizeof(queue_entry_t));
if (NULL == entry)
return -1;
entry->data = d;
entry->next = NULL;
entry->prev = NULL;
pthread_mutex_lock(&q->mutex);
if (NULL != q->tail) {
entry->next = q->tail;
q->tail->prev = entry;
}
if (NULL == q->head) {
q->head = entry;
}
q->tail = entry;
pthread_cond_signal(&q->cond);
pthread_mutex_unlock(&q->mutex);
return 0;
}
void *queue_get_wait(ht_queue_t *q) {
queue_entry_t *entry = NULL;
void *d = NULL;
pthread_mutex_lock(&q->mutex);
while ((NULL == q->head) && (NULL == q->tail)) {
pthread_cond_wait(&q->cond, &q->mutex);
}
entry = q->head;
if (NULL != q->head) {
q->head = q->head->prev;
}
if (NULL == q->head) {
q->tail = NULL;
} else {
q->head->next = NULL;
}
if (NULL != entry) {
d = entry->data;
free(entry);
}
pthread_mutex_unlock(&q->mutex);
return d;
}
#ifdef _TEST_MODE_
#define TEST_STRING_1 "test1"
#define TEST_STRING_2 "test2"
#define TEST_STRING_3 "test2"
int main(int argc, char **argv) {
ht_queue_t q;
char *s;
printf("queue test\n");
printf("\none-item queue: put, get\n");
queue_init(&q);
if (0 != queue_put(&q, (void*)TEST_STRING_1)) {
printf("- queue_put failed\n");
exit(1);
}
printf("+ queue_put passed\n");
s = (char*) queue_get_wait(&q);
if (0 != strcmp(s, TEST_STRING_1)) {
printf("- queue_get_wait returned something wrong\n");
exit(1);
}
printf("+ queue_get_wait for one-item queue passed\n");
queue_destroy(&q);
printf("\ntwo-item queue: put, put, get, get\n");
queue_init(&q);
if (0 != queue_put(&q, (void*)TEST_STRING_1)) {
printf("- queue_put failed\n");
exit(1);
}
printf("+ queue_put passed\n");
if (0 != queue_put(&q, (void*)TEST_STRING_2)) {
printf("- queue_put failed\n");
exit(1);
}
printf("+ queue_put passed\n");
s = (char*) queue_get_wait(&q);
if (0 != strcmp(s, TEST_STRING_1)) {
printf("- queue_get_wait returned something wrong\n");
exit(1);
}
printf("+ queue_get_wait (first) for two-item queue passed\n");
s = (char*) queue_get_wait(&q);
if (0 != strcmp(s, TEST_STRING_2)) {
printf("- queue_get_wait returned something wrong\n");
exit(1);
}
printf("+ queue_get_wait (second) for two-item queue passed\n");
queue_destroy(&q);
printf("\nthree-item queue: put, put, put, get, get, get\n");
queue_init(&q);
if (0 != queue_put(&q, (void*)TEST_STRING_1)) {
printf("- queue_put failed\n");
exit(1);
}
printf("+ queue_put passed\n");
if (0 != queue_put(&q, (void*)TEST_STRING_2)) {
printf("- queue_put failed\n");
exit(1);
}
printf("+ queue_put passed\n");
if (0 != queue_put(&q, (void*)TEST_STRING_3)) {
printf("- queue_put failed\n");
exit(1);
}
printf("+ queue_put passed\n");
s = (char*) queue_get_wait(&q);
if (0 != strcmp(s, TEST_STRING_1)) {
printf("- queue_get_wait returned something wrong\n");
exit(1);
}
printf("+ queue_get_wait (first) for three-item queue passed\n");
s = (char*) queue_get_wait(&q);
if (0 != strcmp(s, TEST_STRING_2)) {
printf("- queue_get_wait returned something wrong\n");
exit(1);
}
printf("+ queue_get_wait (second) for three-item queue passed\n");
s = (char*) queue_get_wait(&q);
if (0 != strcmp(s, TEST_STRING_3)) {
printf("- queue_get_wait returned something wrong\n");
exit(1);
}
printf("+ queue_get_wait (third) for three-item queue passed\n");
queue_destroy(&q);
printf("\nthree-item queue, different sequence: put, get, put, put, get, get\n");
queue_init(&q);
if (0 != queue_put(&q, (void*)TEST_STRING_1)) {
printf("- queue_put failed\n");
exit(1);
}
printf("+ queue_put passed\n");
s = (char*) queue_get_wait(&q);
if (0 != strcmp(s, TEST_STRING_1)) {
printf("- queue_get_wait returned something wrong\n");
exit(1);
}
printf("+ queue_get_wait (first) for three-item queue passed\n");
if (0 != queue_put(&q, (void*)TEST_STRING_2)) {
printf("- queue_put failed\n");
exit(1);
}
printf("+ queue_put passed\n");
if (0 != queue_put(&q, (void*)TEST_STRING_3)) {
printf("- queue_put failed\n");
exit(1);
}
printf("+ queue_put passed\n");
s = (char*) queue_get_wait(&q);
if (0 != strcmp(s, TEST_STRING_2)) {
printf("- queue_get_wait returned something wrong\n");
exit(1);
}
printf("+ queue_get_wait (second) for three-item queue passed\n");
s = (char*) queue_get_wait(&q);
if (0 != strcmp(s, TEST_STRING_3)) {
printf("- queue_get_wait returned something wrong\n");
exit(1);
}
printf("+ queue_get_wait (third) for three-item queue passed\n");
queue_destroy(&q);
exit(0);
}
#endif /* _TEST_MODE_ */

31
smmapdfw/queue.h Normal file
View File

@ -0,0 +1,31 @@
#ifndef _QUEUE_H_
#define _QUEUE_H_
#include <pthread.h>
struct queue_entry_s {
void *data;
struct queue_entry_s *next;
struct queue_entry_s *prev;
};
typedef struct queue_entry_s queue_entry_t;
struct queue_s {
queue_entry_t *tail;
queue_entry_t *head;
pthread_mutex_t mutex;
pthread_cond_t cond;
};
typedef struct queue_s ht_queue_t;
void queue_init(ht_queue_t *q);
void queue_destroy(ht_queue_t *q);
int queue_put(ht_queue_t *q, void *d);
void *queue_get_wait(ht_queue_t *q);
#endif /* _QUEUE_H_ */

46
smmapdfw/safe_write.c Normal file
View File

@ -0,0 +1,46 @@
#include <syslog.h>
#include <sys/select.h>
#include <errno.h>
#include "safe_write.h"
ssize_t safe_write(int fd, const void *buf, size_t count) {
int res;
fd_set rdfs, wrfs;
struct timeval tv = {0, 0};
char read_buf[_SAFE_WRITE_READ_BUF_SIZE+1];
FD_ZERO(&rdfs);
FD_ZERO(&wrfs);
FD_SET(fd, &rdfs);
FD_SET(fd, &wrfs);
while (1) {
res = select(fd+1, &rdfs, &wrfs, NULL, &tv);
if (-1 == res) {
syslog(LOG_DEBUG, "_safe_write: error in select: %d, %s", errno, strerror(errno));
return -1;
} else {
if (FD_ISSET(fd, &rdfs)) {
/* set to read: something is wrong */
syslog(LOG_DEBUG, "_safe_write, set to read");
res = read(fd, read_buf, _SAFE_WRITE_READ_BUF_SIZE);
syslog(LOG_DEBUG, "_safe_write, read res: %d", res);
if (0 == res) {
syslog(LOG_DEBUG, "_safe_write, read returns 0, socket probably closed");
return -1;
}
} else if (FD_ISSET(fd, &wrfs)) {
/* set to write */
syslog(LOG_DEBUG, "_safe_write, set to write");
res = write(fd, buf, count);
return res;
} else {
syslog(LOG_DEBUG, "_safe_write: whether read nor write ready, strange");
return -1;
}
}
}
}

17
smmapdfw/safe_write.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef _SAFE_WRITE_H_
#define _SAFE_WRITE_H_
#include <unistd.h>
#define _SAFE_WRITE_READ_BUF_SIZE 256
/* _safe_write avoids SIGPIPE when writing but it skips a part of an possible
in between input. This is safe here, since no pipelining is supported.
*/
ssize_t safe_write(int fd, const void *buf, size_t count);
#endif /* _SAFE_WRITE_H_ */

336
smmapdfw/smmapd.c Normal file
View File

@ -0,0 +1,336 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <syslog.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <assert.h>
#include "queue.h"
#include "count.h"
#include "containers.h"
#include "config.h"
#include "safe_write.h"
#define _SMMAPD_C_
#include "smmapd.h"
#ifdef SUNOS
#include "sunos_comp.h"
#endif
#define CFG_SECTION_GLOBAL "global"
#define CFG_NAME_ADDRESS "address"
#define CFG_NAME_PORT "port"
#define DEFAULT_PORT "8888"
#define CVS_ID "$Id$"
#define RELEASE_ID "0.9"
struct networkerThread_s {
int result;
struct sockaddr_in clientAddr;
socklen_t clientAddrLen;
int clientSock;
pthread_t pthread;
};
typedef struct networkerThread_s networkerThread_t;
ht_queue_t terminated_networker_queue;
pthread_t cleanerThread;
count_t thread_counter;
cfg_t *cfg;
void * cleaner(void * arg) {
networkerThread_t *t;
int joinRes;
pthread_detach(pthread_self());
while (1) {
syslog(LOG_DEBUG, "cleaner: Waiting for terminated networker, running threads: %d",
count_get(&thread_counter));
t = (networkerThread_t*) queue_get_wait(&terminated_networker_queue);
count_dec(&thread_counter);
syslog(LOG_DEBUG, "cleaner: Networker serving %d (address: %s, port: %d), result %d",
t->clientSock, inet_ntoa(t->clientAddr.sin_addr),
ntohs(t->clientAddr.sin_port), t->result);
joinRes = pthread_join(t->pthread, NULL);
syslog(LOG_DEBUG, "cleaner: joinRes %d", joinRes);
free(t);
t = NULL;
}
}
#define OUTPUT_BUFSIZE ANSWER_BUFSIZE+25 /* a bit more than answer for the result text */
void * networker(void * arg) {
char buffer[BUFSIZE+1];
char *input;
char answer[ANSWER_BUFSIZE+1];
char output[OUTPUT_BUFSIZE+1];
const char *answer_ptr;
const char *result_text;
int err, cnt, i = 0;
unsigned int len;
int dispatcher_result;
container_handle_t *container_handle;
networkerThread_t * thread = (networkerThread_t *) arg;
thread->pthread = pthread_self();
syslog(LOG_DEBUG, "networker: serving %d (address: %s, port: %d) started",
thread->clientSock, inet_ntoa(thread->clientAddr.sin_addr),
ntohs(thread->clientAddr.sin_port));
containers_setup(&container_handle);
syslog(LOG_DEBUG, "networker: got a container handle");
while(0 != (cnt = read(thread->clientSock, buffer, BUFSIZE))) {
/* temporary, until netstring is introduced */
if ((buffer[cnt-1] == 10) && (buffer[cnt-2] == 13)) /* ^M */
buffer[cnt-2] = '\0';
else
buffer[cnt] = '\0';
len = strtol(buffer, &input, 10);
if ((0 == len) && (buffer == input)) {
syslog(LOG_DEBUG, "networker: netstring unparsable, no length found: %s", buffer);
dispatcher_result = SMM_NETSTRING_UNPARSABLE;
} else if (*input++ != ':') {
syslog(LOG_DEBUG, "networker: netstring unparsable, no colon found: %s", buffer);
dispatcher_result = SMM_NETSTRING_UNPARSABLE;
} else if (strlen(input)-1 != len) {
syslog(LOG_DEBUG, "networker: netstring unparsable, length does not match: %s", buffer);
dispatcher_result = SMM_NETSTRING_UNPARSABLE;
} else if (input[strlen(input)-1] != ',') {
syslog(LOG_DEBUG, "networker: netstring unparsable, no terminating comma: %s", buffer);
dispatcher_result = SMM_NETSTRING_UNPARSABLE;
} else {
input[strlen(input)-1] = '\0'; /* strip off the comma */
answer[0] = '\0';
dispatcher_result = containers_dispatcher(container_handle, input, answer);
syslog(LOG_DEBUG, "networker: dispatcher result: %d, answer: %s", dispatcher_result, answer);
}
result_text = (dispatcher_result > SMM_MAX_NUM) ?
T_SMM_RESULTS[0] :
T_SMM_RESULTS[dispatcher_result];
answer_ptr = (dispatcher_result > SMM_MAX_NUM) ?
T_SMM_RESULT_INFOS[0] :
T_SMM_RESULT_INFOS[dispatcher_result];
if (NULL == answer_ptr)
answer_ptr = answer;
snprintf(output, OUTPUT_BUFSIZE, "%d:%s%s%s,",
strlen(result_text) + strlen(answer_ptr) + ((strlen(answer_ptr) > 0) ? 1 : 0),
result_text,
(strlen(answer_ptr) > 0) ? " " : "",
answer_ptr);
safe_write(thread->clientSock, output, strlen(output));
}
close(thread->clientSock);
containers_destroy(container_handle);
syslog(LOG_DEBUG, "networker: destroyed the container handle");
thread->result = 1;
queue_put(&terminated_networker_queue, thread);
}
int server() {
int serverSock;
int res;
struct sockaddr_in servAddr;
struct sockaddr_in clientAddr;
socklen_t clientAddrLen = sizeof(clientAddr);
int clientSock;
networkerThread_t *thread;
pthread_t tid;
int optval = 1;
char *cfg_address, *cfg_port;
serverSock = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == serverSock) {
syslog(LOG_ERR, "server: failure when creating server socket");
return -1;
}
res = setsockopt(serverSock, SOL_SOCKET, SO_REUSEADDR, (void*)&optval, sizeof(optval));
if (-1 == res) {
syslog(LOG_ERR, "server: failure when setting socket options");
return -1;
}
memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;
cfg_address = findcfg(cfg, CFG_SECTION_GLOBAL, CFG_NAME_ADDRESS);
servAddr.sin_addr.s_addr = (NULL == cfg_address) ? htonl(INADDR_ANY) : inet_addr(cfg_address);
cfg_port = findcfgx(cfg, CFG_SECTION_GLOBAL, CFG_NAME_PORT, DEFAULT_PORT);
servAddr.sin_port = htons(atoi(cfg_port));
res = bind(serverSock, (struct sockaddr *) &servAddr, sizeof(servAddr));
if (-1 == res) {
syslog(LOG_ERR, "server: failure when binding on server socket, errno: %d, errtxt: %s",
errno, strerror(errno));
return -1;
}
res = listen(serverSock, 32);
if (-1 == res) {
syslog(LOG_ERR, "server: failure when listening on server socket, errno: %d, errtxt: %s",
errno, strerror(errno));
return -1;
}
count_init(&thread_counter);
queue_init(&terminated_networker_queue);
pthread_create(&cleanerThread, NULL, &cleaner, NULL);
while (1) {
syslog(LOG_DEBUG, "server: Waiting for connection");
clientSock = accept(serverSock, (struct sockaddr *) &clientAddr, &clientAddrLen);
syslog(LOG_DEBUG, "server: Got a connection %d", clientSock);
if (-1 == clientSock) {
syslog(LOG_ERR, "server: failure when accepting connection");
return -1;
}
thread = (networkerThread_t *) malloc(sizeof(networkerThread_t)+10);
if (NULL == thread) {
syslog(LOG_ERR, "server: unable to alloc memory for networker");
break;
}
thread->clientSock = clientSock;
thread->result = 0;
thread->clientAddr = clientAddr;
thread->clientAddrLen = clientAddrLen;
count_inc(&thread_counter);
res = pthread_create(&tid, NULL, &networker, thread);
if (0 != res) {
syslog(LOG_ERR, "server: unable to start networker thread");
free(thread);
count_dec(&thread_counter);
break;
}
}
return 0;
}
void usage() {
printf("smmapd [-F] [-p pidfile] [-f cfgfile] [-h]\n");
printf(" -F run in foreground, overwrite smmapd.ini setting\n");
printf(" -p pidfile, overwrite smmapd.ini setting\n");
printf(" -f cfgfile\n");
printf(" -v version and release info\n");
printf(" -h print this help\n\n");
}
int main(int argc, char **argv) {
#define DEFAULT_SMMAPD_INI "smmapd.ini"
int err, pid, do_fork, foreground=0, c;
char *pid_file=NULL, *cfg_file=NULL;
FILE *f;
while ((c = getopt(argc, argv,"Fp:f:hv")) != -1)
switch (c) {
case 'F':
foreground = 1;
break;
case 'f':
cfg_file = strdup(optarg);
break;
case 'p':
pid_file = strdup(optarg);
break;
case 'v':
printf("\nsmmapd - Wolfgang Hottgenroth <woho@hottis.de>\n");
printf(" cvs_id: %s\n", CVS_ID);
printf(" release_id: %s\n\n", RELEASE_ID);
exit(1);
case 'h':
default:
usage();
exit(1);
}
openlog("smmapd", LOG_PID, LOG_LOCAL2);
syslog(LOG_INFO, "main: started");
cfg = readcfg((cfg_file == NULL) ? DEFAULT_SMMAPD_INI : cfg_file);
if (0 != (err = register_all())) {
syslog(LOG_ERR, "main: register_all fails: %d", err);
exit(1);
}
do_fork = atoi(findcfgx(cfg, "global", "do_fork", "1"));
pid = ((1 == do_fork) && (0 == foreground)) ? fork() : 0;
if (-1 == pid) {
syslog(LOG_ERR, "Error when forking smmapd: %d, %s", errno, strerror(errno));
exit(1);
} else if (0 != pid) {
syslog(LOG_INFO, "smmapd successfully forked, child's pid is %d", pid);
if (NULL == pid_file)
pid_file = findcfgx(cfg, "global", "pid_file", "smmapd.pid");
if ((f = fopen(pid_file, "w")) == NULL) {
syslog(LOG_ERR, "unable to open pid_file %s for pid %d", pid_file, pid);
} else {
fprintf(f, "%d", pid);
fclose(f);
}
exit(0);
}
if (0 != (err = server())) {
syslog(LOG_ERR, "main: server fails: %d", err);
exit(1);
}
syslog(LOG_INFO, "main: finished");
closelog();
}

49
smmapdfw/smmapd.h Normal file
View File

@ -0,0 +1,49 @@
#ifndef _SMMAPD_H_
#define _SMMAPD_H_
#define SMM_OK 1
#define SMM_TEMP_NOK 2
#define SMM_PERM_NOK 3
#define SMM_NOT_FOUND_NOK 4
#define SMM_ILLEGAL_INPUT 5
#define SMM_UNKNOWN_CLASS 6
#define SMM_TIMEOUT_NOK 7
#define SMM_NETSTRING_UNPARSABLE 8
#define SMM_MAX_NUM 9
#ifdef _SMMAPD_C_
const char *T_SMM_RESULTS[] = {
"PERM",
"OK",
"TEMP",
"PERM",
"NOTFOUND",
"PERM",
"PERM",
"TIMEOUT",
"PERM"
};
const char *T_SMM_RESULT_INFOS[] = {
"unsupported result code",
NULL,
NULL,
NULL,
NULL,
"illegal input",
"unknown class",
NULL,
"netstring unparsable"
};
#endif
#define BUFSIZE 2048
#define ANSWER_BUFSIZE BUFSIZE
#endif /* _SMMAPD_H_ */

32
smmapdfw/smmapd.ini Normal file
View File

@ -0,0 +1,32 @@
[global]
do_fork = 1
pid_file = smmapd.pid
address = 127.0.0.1
port = 8887
plugin_dir = /home/who/Sources/private/smmapd
plugins = test_worker1 test_worker2 verifier cyruscheck
[test_worker1]
obj = test_workers.so
text = test worker1 handle text
[test_worker2]
obj = test_workers.so
[verifier]
obj = verify_worker.so
timeout_result = 5
timeout_dialog = 20
cache_enabled = 1
cache_expiry = 86400
sender_address = <>
ehlo_arg = local
smtp_port = 25
max_checker_threads = 100
[cyruscheck]
obj = cyrus_worker.so
timeout = 10
sender_address = <testsender>
lhlo_arg = local
lmtp_port = 24

404
smmapdfw/smtp.c Normal file
View File

@ -0,0 +1,404 @@
#include <stdlib.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <syslog.h>
#include <string.h>
#include "smtp.h"
#define BUFSIZE 1024
#define BUFSIZE2 1024
smtp_t *smtp_init(unsigned int address, int port, int timeout) {
smtp_t *handle = (smtp_t*) malloc(sizeof(smtp_t));
handle->address = address;
handle->port = port;
handle->timeout = timeout;
handle->response_text = NULL;
handle->response_code = 0;
handle->got_timeout = 0;
handle->buffer = (char*) malloc(BUFSIZE+5);
return handle;
}
smtp_t *smtp_init2(char *host_address, int port, int timeout) {
unsigned int a = inet_addr(host_address);
if (-1 == a) {
syslog(LOG_DEBUG, "smtp_init2: invalid host_address %s", host_address);
return NULL;
}
return smtp_init(a, port, timeout);
}
void smtp_destroy(smtp_t *handle) {
free(handle->buffer);
free(handle);
}
static int _smtp_strip_crlf(char *s) {
char *c;
int cnt = 0;
if ((10 == s[strlen(s)-1]) && (13 == s[strlen(s)-2]))
s[strlen(s)-2] = '\0';
/* for (c = s; *c != '\0'; c++) { */
/* if ((10 == *c) || (13 == *c)) { */
/* *c = 'X'; */
/* cnt++; */
/* } */
/* } */
return cnt;
}
static int _smtp_read(smtp_t *handle) {
int res;
fd_set rdfs;
struct timeval tv;
FD_ZERO(&rdfs);
FD_SET(handle->socket, &rdfs);
tv.tv_sec = handle->timeout;
tv.tv_usec = 0;
res = select(handle->socket+1, &rdfs, NULL, NULL, &tv);
if (0 == res) {
syslog(LOG_DEBUG, "_smtp_read: timeout in select");
handle->got_timeout = 1;
return SMTP_TIMEOUT;
} else if (-1 == res) {
syslog(LOG_DEBUG, "_smtp_read: error in select, errno: %d (%s)", errno, strerror(errno));
return -1;
} else {
if (FD_ISSET(handle->socket, &rdfs)) {
res = read(handle->socket, handle->buffer, BUFSIZE);
if (-1 == res) {
syslog(LOG_DEBUG, "read error: %d, %s\n", errno, strerror(errno));
return -1;
}
handle->buffer[res] = '\0';
return 0;
}
}
return -1;
}
static int _smtp_write(smtp_t *handle) {
int res;
fd_set wrfs;
struct timeval tv;
FD_ZERO(&wrfs);
FD_SET(handle->socket, &wrfs);
tv.tv_sec = handle->timeout;
tv.tv_usec = 0;
res = select(handle->socket+1, NULL, &wrfs, NULL, &tv);
if (0 == res) {
syslog(LOG_DEBUG, "_smtp_write: timeout in select");
handle->got_timeout = 1;
return SMTP_TIMEOUT;
} else if (-1 == res) {
syslog(LOG_DEBUG, "_smtp_write: error in select, errno: %d (%s)", errno, strerror(errno));
return -1;
} else {
if (FD_ISSET(handle->socket, &wrfs)) {
if (-1 == write(handle->socket, handle->buffer, strlen(handle->buffer))) {
syslog(LOG_DEBUG, "read error: %d, %s\n", errno, strerror(errno));
return -1;
}
return 0;
}
}
return -1;
}
static int _smtp_parse_response(smtp_t *handle) {
char *first_space;
char *last_crlf;
int l;
_smtp_strip_crlf(handle->buffer);
if (NULL == (last_crlf = strrchr(handle->buffer, 10)))
last_crlf = handle->buffer;
if (NULL == (first_space = strchr(last_crlf, ' ')))
return -1;
*first_space = '\0';
handle->response_code = atoi(last_crlf);
*first_space = ' ';
handle->response_text = handle->buffer;
return 0;
}
int smtp_response(smtp_t *handle, char **response) {
if (handle->got_timeout) {
handle->got_timeout = 0;
return SMTP_TIMEOUT;
}
if (0 != _smtp_parse_response(handle)) {
syslog(LOG_DEBUG, "smtp_response: failed to parse response\n");
return -1;
}
if (NULL != response)
*response = handle->response_text;
return handle->response_code;
}
int smtp_command(smtp_t *handle, char *command, char *arg) {
int err;
*handle->buffer = '\0';
strcat(handle->buffer, command);
if (NULL != arg) {
/* strcat(handle->buffer, " "); */
strcat(handle->buffer, arg);
}
strcat(handle->buffer, "\r\n");
if (0 != (err = _smtp_write(handle))) {
syslog(LOG_DEBUG, "smtp_command: failed to send %s\n", handle->buffer);
return err;
}
if (0 != (err = _smtp_read(handle))) {
syslog(LOG_DEBUG, "smtp_command: failed to read response\n");
return err;
}
return 0;
}
int smtp_connect(smtp_t *handle) {
int err;
int c;
int res;
fd_set wrfs;
struct timeval tv;
handle->socket = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == handle->socket) {
syslog(LOG_DEBUG, "smtp_connect: unable to create socket");
return -1;
}
c = fcntl(handle->socket, F_GETFL);
if (-1 == c) {
syslog(LOG_DEBUG, "smtp_connect: unable to get flags, errno: %d (%s)", errno, strerror(errno));
return -1;
}
err = fcntl(handle->socket, F_SETFL, c | O_NONBLOCK);
if (-1 == err) {
syslog(LOG_DEBUG, "smtp_connect: unable to set O_NONBLOCK flag, errno: %d (%s)", errno, strerror(errno));
return -1;
}
memset(&handle->addr, 0, sizeof(handle->addr));
handle->addr.sin_addr.s_addr = handle->address;
handle->addr.sin_port = htons(handle->port);
handle->addr.sin_family = AF_INET;
c = connect(handle->socket, (struct sockaddr *) &handle->addr, sizeof(handle->addr));
if (-1 == c) {
if (EINPROGRESS == errno) {
FD_ZERO(&wrfs);
FD_SET(handle->socket, &wrfs);
tv.tv_sec = handle->timeout;
tv.tv_usec = 0;
res = select(handle->socket+1, NULL, &wrfs, NULL, &tv);
if (0 == res) {
syslog(LOG_DEBUG, "smtp_connect: timeout in select");
handle->got_timeout = 1;
return SMTP_TIMEOUT;
} else if (-1 == res) {
syslog(LOG_DEBUG, "smtp_connect: error in select, errno: %d (%s)", errno, strerror(errno));
return -1;
} else {
if (FD_ISSET(handle->socket, &wrfs)) {
// debug("smtp_connect: here we go");
}
}
} else {
syslog(LOG_DEBUG, "smtp_connect: unable to connect to socket, errno: %d (%s)", errno, strerror(errno));
return -1;
}
}
err = _smtp_read(handle);
if (0 != err)
return -1;
return 0;
}
int smtp_helo(smtp_t *handle, char *arg) {
return smtp_command(handle, "helo ", arg);
}
int smtp_ehlo(smtp_t *handle, char *arg) {
return smtp_command(handle, "ehlo ", arg);
}
int smtp_lhlo(smtp_t *handle, char *arg) {
return smtp_command(handle, "lhlo ", arg);
}
int smtp_mailfrom(smtp_t *handle, char *arg) {
return smtp_command(handle, "mail from:", arg);
}
int smtp_rcptto(smtp_t *handle, char *arg) {
return smtp_command(handle, "rcpt to:", arg);
}
int smtp_data(smtp_t *handle) {
return smtp_command(handle, "data", NULL);
}
int smtp_datasend(smtp_t *handle, char *data) {
char *d = data;
int cnt;
int ssize;
while (d < data + strlen(data)) {
ssize = (strlen(d) < BUFSIZE2) ? strlen(d) : BUFSIZE2;
if (-1 == write(handle->socket, d, ssize))
return -1;
d += BUFSIZE2;
}
return 0;
}
int smtp_dataend(smtp_t *handle) {
return smtp_command(handle, "\r\n.", NULL);
}
int smtp_rset(smtp_t *handle) {
return smtp_command(handle, "rset", NULL);
}
int smtp_quit(smtp_t *handle) {
return smtp_command(handle, "quit", NULL);
}
int smtp_close(smtp_t *handle) {
close(handle->socket);
}
#ifdef _TEST_MODE_
int main() {
int res;
char *res_text;
char data[] = "Hallo Wolfgang\r\n"
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n"
"abaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n"
"aacaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n"
"aaadaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n"
"aaaaeaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n"
"aaaaafaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n"
"aaaaaagaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n"
"aaaaaaahaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n"
"aaaaaaaaiaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n"
"aaaaaaaaajaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n"
"aaaaaaaaaakaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n"
"aaaaaaaaaaalaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n"
"aaaaaaaaaaaamaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n"
"aaaaaaaaaaaaanaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n"
"aaaaaaaaaaaaaaoaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n"
"aaaaaaaaaaaaaaapaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n"
"aaaaaaaaaaaaaaaaqaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n"
"aaaaaaaaaaaaaaaaaraaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n"
"aaaaaaaaaaaaaaaaaasaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n"
"aaaaaaaaaaaaaaaaaaataaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n"
"aaaaaaaaaaaaaaaaaaaauaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n"
"aaaaaaaaaaaaaaaaaaaaavaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n"
"aaaaaaaaaaaaaaaaaaaaaawaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n"
"aaaaaaaaaaaaaaaaaaaaaaaxaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n"
"\r\n"
"dies ist eine Testmail.\r\n"
"\r\n";
smtp_t *h = smtp_init2("213.70.191.212", 25, 10);
printf("got handle\n");
res = smtp_connect(h);
printf("connected: %d\n", res);
res = smtp_response(h, &res_text);
printf("response: %d, %s\n", res, res_text);
res = smtp_helo(h, "local");
printf("ehlo: %d\n", res);
res = smtp_response(h, &res_text);
printf("response: %d, %s\n", res, res_text);
res = smtp_mailfrom(h, "<xyx@test.de>");
printf("mail from: %d\n", res);
res = smtp_response(h, &res_text);
printf("response: %d, %s\n", res, res_text);
res = smtp_rcptto(h, "<wn@kja-essen.de>");
printf("rcpt to: %d\n", res);
res = smtp_response(h, &res_text);
printf("response: %d, %s\n", res, res_text);
res = smtp_data(h);
printf("data: %d\n", res);
res = smtp_response(h, &res_text);
printf("response: %d, %s\n", res, res_text);
res = smtp_datasend(h, data);
printf("datasend: %d\n", res);
res = smtp_dataend(h);
printf("dataend: %d\n", res);
res = smtp_response(h, &res_text);
printf("response: %d, %s\n", res, res_text);
smtp_close(h);
smtp_destroy(h);
}
#endif /* _TEST_MODE_ */

52
smmapdfw/smtp.h Normal file
View File

@ -0,0 +1,52 @@
#ifndef _SMTP_H_
#define _SMTP_H_
#include <netinet/in.h>
struct smtp_s {
/* char *host_address; /\* IP address, no hostname!! *\/ */
unsigned int address;
int port;
int timeout;
char *buffer;
char *response_text;
int response_code;
int got_timeout;
int socket;
struct sockaddr_in addr;
};
typedef struct smtp_s smtp_t;
#define SMTP_TIMEOUT -99
smtp_t *smtp_init2(char *host_address, int port, int timeout);
smtp_t *smtp_init(unsigned int address, int port, int timeout);
void smtp_destroy(smtp_t *handle);
int smtp_response(smtp_t *handle, char **response);
int smtp_command(smtp_t *handle, char *command, char *arg);
int smtp_connect(smtp_t *handle);
int smtp_close(smtp_t *handle);
int smtp_helo(smtp_t *handle, char *arg);
int smtp_ehlo(smtp_t *handle, char *arg);
int smtp_lhlo(smtp_t *handle, char *arg);
int smtp_mailfrom(smtp_t *handle, char *arg);
int smtp_rcptto(smtp_t *handle, char *arg);
int smtp_data(smtp_t *handle);
int smtp_datasend(smtp_t *handle, char *data);
int smtp_dataend(smtp_t *handle);
int smtp_rset(smtp_t *handle);
int smtp_quit(smtp_t *handle);
#endif /* _SMTP_H_ */

10
smmapdfw/sunos_comp.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef _SUNOS_COMP_H_
#define _SUNOS_COMP_H_
#define socklen_t size_t
#define isblank(A) ((A==' ') || (A=='\t'))
#endif /* _SUNOS_COMP_H_ */

69
smmapdfw/test_workers.c Normal file
View File

@ -0,0 +1,69 @@
#include <stdlib.h>
#include <syslog.h>
#include "containers_public.h"
int test_worker1_init(cfgl_t *cfg, void **handle);
int test_worker1_setup(void *handle, void **work_handle);
int test_worker1_destroy(void *handle, void *work_handle);
int test_worker1_work(void *handle, void *work_handle, char *input, char *output);
int test_worker2_work(void *handle, void *work_handle, char *input, char *output);
class_descriptor_t test_worker1 = {
"test_worker1",
&test_worker1_init,
NULL,
&test_worker1_setup,
&test_worker1_work,
&test_worker1_destroy
};
class_descriptor_t test_worker2 = {
"test_worker2",
NULL,
NULL,
NULL,
&test_worker2_work,
NULL
};
struct test_worker1_handle_s {
int counter;
};
typedef struct test_worker1_handle_s test_worker1_handle_t;
int test_worker1_init(cfgl_t *cfg, void **handle) {
*handle = (char*) findcfgl(cfg, "text");
syslog(LOG_DEBUG, "test_worker1_init: %s", *handle);
if (NULL == *handle) {
syslog(LOG_ERR, "test_worker1_init: text not configured");
return -1;
}
return 0;
}
int test_worker1_setup(void *handle, void **work_handle) {
test_worker1_handle_t *twh = (test_worker1_handle_t*)malloc(sizeof(test_worker1_handle_t));
twh->counter = 0;
*work_handle = twh;
return 0;
}
int test_worker1_destroy(void *handle, void *work_handle) {
syslog(LOG_DEBUG, "test_worker1_destroy: freeing the worker handle");
free(work_handle);
}
int test_worker1_work(void *handle, void *work_handle, char *input, char *output) {
sprintf(output, "Test-Worker 1 receives %s (handle %s) (called %d)\n",
input, (char*)handle, (((test_worker1_handle_t*)work_handle)->counter)++);
return 0;
}
int test_worker2_work(void *handle, void *work_handle, char *input, char *output) {
sprintf(output, "Test-Worker 2 receives %s\n", input);
return 0;
}

745
smmapdfw/verify_worker.c Normal file
View File

@ -0,0 +1,745 @@
#include <stdlib.h>
#include <syslog.h>
#include <pthread.h>
#include <errno.h>
#include <time.h>
#include <string.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#define DB_DBM_HSEARCH 1
#include <db.h>
#include "containers_public.h"
#include "smmapd.h"
#include "dns.h"
#include "queue.h"
#include "smtp.h"
#define SMM_LOCAL_PERM_NOK 101
#define SMM_LOCAL_TEMP_NOK 102
#define SMM_LOCAL_OK 103
struct verify_container_handle_s {
cfgl_t *cfg;
int timeout_result;
int timeout_dialog;
int cache_enabled;
int cache_expiry;
pthread_mutex_t *cache_mutex;
char *cache_file;
char *sender_address;
char *ehlo_arg;
int smtp_port;
int max_checker_threads;
};
typedef struct verify_container_handle_s verify_container_handle_t;
struct verify_result_s {
int id;
int result;
char *output;
};
typedef struct verify_result_s verify_result_t;
struct verify_work_handle_s {
int id;
pthread_mutex_t *result_mutex;
pthread_cond_t *result_cond;
verify_result_t *result;
ht_queue_t *terminator_queue;
verify_container_handle_t *vch;
};
typedef struct verify_work_handle_s verify_work_handle_t;
struct checker_thread_s {
pthread_t thread;
int id;
int ip_address;
char *email_address;
char *output;
int result;
ht_queue_t *checker_terminator_queue;
verify_work_handle_t *vwh;
};
typedef struct checker_thread_s checker_thread_t;
struct worker_thread_s {
pthread_t thread;
int id;
char *input;
char *output;
pthread_mutex_t *mutex;
pthread_cond_t *cond;
verify_result_t *result;
ht_queue_t *checker_terminator_queue;
int checker_cnt;
ht_queue_t *terminator_queue;
verify_work_handle_t *vwh;
};
typedef struct worker_thread_s worker_thread_t;
struct mydata_s {
int result;
time_t timestamp;
char output[1];
};
typedef struct mydata_s mydata_t;
int verify_init(cfgl_t *cfg, void **handle);
int verify_destroy(void *handle);
int verify_work_setup(void *handle, void **work_handle);
int verify_work(void *handle, void *work_handle, char *input, char *output);
int verify_work_destroy(void *handle, void *work_handle);
static void *worker_thread(void *arg);
static unsigned int *get_mx_ip_addresses(char *domain);
class_descriptor_t verifier = {
"verifier",
&verify_init,
&verify_destroy,
&verify_work_setup,
&verify_work,
&verify_work_destroy
};
/* verify_init will be called when the class is loaded, directly at start-up */
/* It will be called definitely only once, there is definitely only one */
/* container handle for each class in the application. */
int verify_init(cfgl_t *cfg, void **handle) {
verify_container_handle_t *vch;
vch = (verify_container_handle_t*) malloc(sizeof(verify_container_handle_t));
vch->cfg = cfg;
vch->timeout_result = atoi(findcfglx(vch->cfg, "timeout_result", "5"));
vch->timeout_dialog = atoi(findcfglx(vch->cfg, "timeout_dialog", "20"));
vch->sender_address = findcfglx(vch->cfg, "sender_address", "<>");
vch->ehlo_arg = findcfglx(vch->cfg, "ehlo_arg", "local");
vch->smtp_port = atoi(findcfglx(vch->cfg, "smtp_port", "25"));
vch->max_checker_threads = atoi(findcfglx(vch->cfg, "max_checker_threads", "25"));
vch->cache_enabled = atoi(findcfglx(vch->cfg, "cache_enabled", "1"));
vch->cache_expiry = atoi(findcfglx(vch->cfg, "cache_expiry", "86400"));
vch->cache_file = findcfglx(vch->cfg, "cache_file", "verifier_cache");
if (1 == vch->cache_enabled) {
vch->cache_mutex = (pthread_mutex_t*) malloc(sizeof(pthread_mutex_t));
pthread_mutex_init(vch->cache_mutex, NULL);
pthread_mutex_unlock(vch->cache_mutex);
} else {
vch->cache_mutex = NULL;
}
*handle = vch;
return 0;
}
/* currently this will be called never. It would be called when the server */
/* is gracefully shutted down, which is currently not supported */
int verify_destroy(void *handle) {
verify_container_handle_t *vch = (verify_container_handle_t*)handle;
if (1 == vch->cache_enabled) {
pthread_mutex_destroy(vch->cache_mutex);
free(vch->cache_mutex);
}
free(vch);
return 0;
}
int verify_work_setup(void *handle, void **work_handle) {
verify_work_handle_t *vwh;
vwh = (verify_work_handle_t*)malloc(sizeof(verify_work_handle_t));
vwh->id = 0;
vwh->result = (verify_result_t*) malloc(sizeof(verify_result_t));
vwh->result_mutex = (pthread_mutex_t*) malloc(sizeof(pthread_mutex_t));
pthread_mutex_init(vwh->result_mutex, NULL);
pthread_mutex_unlock(vwh->result_mutex);
vwh->result_cond = (pthread_cond_t*) malloc(sizeof(pthread_cond_t));
pthread_cond_init(vwh->result_cond, NULL);
vwh->terminator_queue = (ht_queue_t*) malloc(sizeof(ht_queue_t));
queue_init(vwh->terminator_queue);
vwh->vch = (verify_container_handle_t*)handle;
*work_handle = vwh;
return 0;
}
int verify_work_destroy(void *handle, void *work_handle) {
verify_work_handle_t *vwh = (verify_work_handle_t*)work_handle;
worker_thread_t *wt;
checker_thread_t *ct;
int cnt, cnt2, err;
syslog(LOG_DEBUG, "verify_work_destroy: was %d times used", vwh->id);
/* The terminator_queue must be drained until id_counter is zero again */
cnt = vwh->id;
while (cnt != 0) {
syslog(LOG_DEBUG, "verify_work_destroy, %d thread in queue, waiting for it", cnt);
wt = (worker_thread_t*) queue_get_wait(vwh->terminator_queue);
cnt2 = wt->checker_cnt;
while (cnt2 != 0) {
ct = (checker_thread_t*) queue_get_wait(wt->checker_terminator_queue);
/* clean up the checker stuff */
pthread_join(ct->thread, NULL);
syslog(LOG_DEBUG, "verify_work_destroy, checker_thread (id=%d) joined", ct->id);
free(ct->output);
free(ct);
cnt2--;
}
err = pthread_join(wt->thread, NULL);
syslog(LOG_DEBUG, "verify_work_destroy, worker_thread (id=%d) joined", wt->id);
free(wt->input);
/* this will always be a pointer to something const or allocated, which
will be freed somewhere else */
/* free(wt->output); */
queue_destroy(wt->checker_terminator_queue);
free(wt->checker_terminator_queue);
free(wt);
cnt--;
}
/* Free the members of the work_handle */
pthread_mutex_destroy(vwh->result_mutex);
free(vwh->result_mutex);
pthread_cond_destroy(vwh->result_cond);
free(vwh->result_cond);
free(vwh->result);
queue_destroy(vwh->terminator_queue);
free(vwh->terminator_queue);
free(vwh);
return 0;
}
void cache_insert(verify_container_handle_t *vch, const char *address, int result, const char *output) {
DBM *cache;
datum data, key;
int ret;
mydata_t *mydata;
if (1 == vch->cache_enabled) {
syslog(LOG_DEBUG, "cache_insert: inserting %s -> %d, %s", address, result, output);
key.dsize = strlen(address) + 1; /* one more for the terminating \0 */
key.dptr = (char*) address;
data.dsize = (sizeof(mydata_t) + (sizeof(char) * (strlen(output) + 1)));
mydata = (mydata_t *) malloc(data.dsize);
mydata->result = result;
mydata->timestamp = time(NULL);
strcpy(mydata->output, output);
data.dptr = (char*) mydata;
pthread_mutex_lock(vch->cache_mutex);
if (NULL != (cache = dbm_open(vch->cache_file, O_RDWR | O_CREAT, 0644))) {
ret = dbm_store(cache, key, data, DBM_INSERT);
if (ret != 0) {
syslog(LOG_DEBUG, "cache_insert: couldn't insert record");
} else {
syslog(LOG_DEBUG, "cache_insert: record inserted");
}
dbm_close(cache);
}
pthread_mutex_unlock(vch->cache_mutex);
free(mydata);
}
}
int cache_lookup(verify_container_handle_t *vch, const char* address, int *result, char **output) {
DBM *cache;
datum data, key;
mydata_t *mydata;
int ret, res = -1;
if (1 == vch->cache_enabled) {
syslog(LOG_DEBUG, "cache_lookup: looking up %s, expiry %d",
address, vch->cache_expiry);
if (NULL != (cache = dbm_open(vch->cache_file, O_RDONLY, 0644))) {
key.dsize = strlen(address) + 1; /* one more for the terminating \0 */
key.dptr = (char*) address;
data = dbm_fetch(cache, key);
if (NULL == data.dptr) {
syslog(LOG_DEBUG, "cache_lookup: nothing found");
dbm_close(cache);
} else {
mydata = (mydata_t *) data.dptr;
syslog(LOG_DEBUG, "cache_lookup: found: %s -> %d, %d, %s",
address, mydata->result, mydata->timestamp, mydata->output);
if ((mydata->timestamp + vch->cache_expiry) > time(NULL)) {
syslog(LOG_DEBUG, "cache_lookup: not yet expired");
*result = mydata->result;
*output = (char*) malloc(sizeof(char) * (strlen(mydata->output) + 1));
strcpy(*output, mydata->output);
free(data.dptr);
dbm_close(cache);
res = 0;
} else {
syslog(LOG_DEBUG, "cache_lookup: expired, will be deleted from cache");
free(data.dptr);
dbm_close(cache);
pthread_mutex_lock(vch->cache_mutex);
if (NULL != (cache = dbm_open(vch->cache_file, O_RDWR, 0644))) {
ret = dbm_delete(cache, key);
if (ret != 0) {
syslog(LOG_DEBUG, "cache_insert: couldn't delete record");
} else {
syslog(LOG_DEBUG, "cache_insert: record deleted");
}
dbm_close(cache);
}
pthread_mutex_unlock(vch->cache_mutex);
}
}
}
}
return res;
}
/* Each time verify_work is called, it starts a worker thread. This
thread gets the address to check, an id, a worker termination queue
and a pointer the a result structure as argument.
The id is just a number which is increased each time a new worker
thread is started. The id is also part of the result structure.
verify_work waits a certain amount of time for the result of a
worker thread, afterwards it returns with temporary failure.
A worker thread returns its result by putting it into the result
structure, but it does so only if the id in the result structure is
equal to the id it go as argument. If a worker thread finds an id
in the result structure different from its own one, verify_work has
started a new worker thread in between and is not longer interested
in the result of the current thread.
A thread puts its own argument structure into the termination
queue, just before actually terminating.
verify_work_destroy will drain termination queue, joins each thread
it takes out of it an frees the argument structure. It does so
until the thread count (id) goes to zero.
*/
#define PERM_NOK_RETURN(msg) \
syslog(LOG_ERR, "verify_work: %s", msg); \
snprintf(output, ANSWER_BUFSIZE, "verify_work: %s", msg); \
return SMM_PERM_NOK;
#define TEMP_NOK_RETURN(msg) \
syslog(LOG_ERR, "verify_work: %s", msg); \
snprintf(output, ANSWER_BUFSIZE, "verify_work: %s", msg); \
return SMM_TEMP_NOK;
int verify_work(void *handle, void *work_handle, char *input, char *output) {
int err;
pthread_t tid;
worker_thread_t *wt;
verify_container_handle_t *vch = (verify_container_handle_t*) handle;
verify_work_handle_t *vwh = (verify_work_handle_t*) work_handle;
struct timespec ts;
syslog(LOG_DEBUG, "verify_work: going to verify %s\n", input);
vwh->id += 1;
vwh->result->id = vwh->id;
wt = (worker_thread_t*) malloc(sizeof(worker_thread_t));
wt->id = vwh->id;
wt->terminator_queue = vwh->terminator_queue;
wt->input = (char*) malloc(sizeof(char) * (strlen(input)+1));
strcpy(wt->input, input);
wt->output = NULL;
wt->mutex = vwh->result_mutex;
wt->cond = vwh->result_cond;
wt->result = vwh->result;
wt->checker_terminator_queue = (ht_queue_t*) malloc(sizeof(ht_queue_t));
queue_init(wt->checker_terminator_queue);
wt->checker_cnt = 0;
wt->vwh = work_handle;
syslog(LOG_DEBUG, "verify_work: going to start worker thread, id=%d", vwh->id);
err = pthread_create(&tid, NULL, &worker_thread, wt);
if (-1 == err) {
free(wt->input);
free(wt);
PERM_NOK_RETURN("unable to create worker thread");
}
syslog(LOG_DEBUG, "verify_work: waiting for result");
pthread_mutex_lock(vwh->result_mutex);
ts.tv_sec = time(0) + vch->timeout_result;
ts.tv_nsec = 0;
err = pthread_cond_timedwait(vwh->result_cond, vwh->result_mutex, &ts);
pthread_mutex_unlock(vwh->result_mutex);
if (ETIMEDOUT == err) {
TEMP_NOK_RETURN("worker thread timed out");
}
snprintf(output, ANSWER_BUFSIZE, vwh->result->output);
free(vwh->result->output);
return vwh->result->result;
}
static unsigned int *get_mx_ip_addresses(char *domain) {
mx_rdata_t **mx_rdata, **mx_rdata2;
a_rdata_t **a_rdata, **a_rdata2;
int i = 0;
unsigned int *addresses = NULL;
mx_rdata = get_best_mx_rrs(domain);
if (NULL == mx_rdata) {
syslog(LOG_DEBUG, "get_mx_ip_address: no mx at all");
return NULL;
}
for (mx_rdata2 = mx_rdata; *mx_rdata2 != NULL; mx_rdata2++) {
syslog(LOG_DEBUG, "get_mx_ip_address: %s", (*mx_rdata2)->exchange);
a_rdata = get_a_rrs((*mx_rdata2)->exchange);
if (NULL != a_rdata) {
for (a_rdata2 = a_rdata; *a_rdata2 != NULL; a_rdata2++) {
addresses = (unsigned int*)realloc(addresses, sizeof(unsigned int)*(i+2)); /* 2 since first i==0 and
we always need one more
for the termination */
*(addresses+i) = (*a_rdata2)->address;
i++;
}
free_rrs((void**)a_rdata);
}
}
if (NULL != addresses)
*(addresses+i) = 0; /* termination */
free_rrs((void**)mx_rdata);
return addresses;
}
static void *checker_thread(void *arg) {
static const char *UNEXPECTED_ERROR = "unexpected error in smtp dialog";
static const char *TIMEOUT_ERROR = "timeout on smtp dialog";
static const char *ADDRESS_VALID = "address is valid";
checker_thread_t *ct = (checker_thread_t*) arg;
int timeout = ct->vwh->vch->timeout_dialog;
char *sender_address = ct->vwh->vch->sender_address;
char *ehlo_arg = ct->vwh->vch->ehlo_arg;
int port = ct->vwh->vch->smtp_port;
smtp_t *smtp;
char *response_text, *tmp_arg;
int err, done = 0;
enum {
CONNECT, EHLO, MAILFROM, RCPTTO, RSET, QUIT, END
} state = CONNECT;
syslog(LOG_DEBUG, "checker_thread (id=%d) started, %08x %s", ct->id, ct->ip_address, ct->email_address);
ct->thread = pthread_self();
/*
Connect to ct->ip_address using ct->email_address,
put the result text in ct->output, TBD: by copy or pointer,
evaluate whether it is SMM_TEMP_NOK, SMM_PERM_NOK or SMM_OK
*/
smtp = smtp_init(ct->ip_address, port, timeout);
while ((END != state) && (0 == done)) {
syslog(LOG_DEBUG, "checker_thread (id=%d), smtp dialog state %d", ct->id, state);
switch(state) {
case CONNECT:
err = smtp_connect(smtp);
break;
case EHLO:
err = smtp_ehlo(smtp, ehlo_arg);
break;
case MAILFROM:
err = smtp_mailfrom(smtp, sender_address);
break;
case RCPTTO:
tmp_arg = (char*) malloc(sizeof(char) * (strlen(ct->email_address)+5));
*tmp_arg = '\0';
strcat(tmp_arg, "<");
strcat(tmp_arg, ct->email_address);
strcat(tmp_arg, ">");
err = smtp_rcptto(smtp, tmp_arg);
free(tmp_arg);
break;
case RSET:
err = smtp_rset(smtp);
break;
case QUIT:
err = smtp_quit(smtp);
break;
}
state++;
switch(err) {
case SMTP_TIMEOUT:
syslog(LOG_DEBUG, "checker_thread (id=%d), timeout in smtp dialog", ct->id);
ct->result = SMM_LOCAL_TEMP_NOK;
response_text = (char*)TIMEOUT_ERROR;
done = 1;
break;
case 0:
/* evaluate smtp_response, return or continue */
err = smtp_response(smtp, &response_text);
if (-1 == err) {
syslog(LOG_DEBUG, "checker_thread (id=%d), response could not be parsed", ct->id);
ct->result = SMM_LOCAL_TEMP_NOK;
response_text = (char*)UNEXPECTED_ERROR;
done = 1;
break;
}
syslog(LOG_DEBUG, "checker_thread (id=%d), response: %d, %s (%d)", ct->id, err, response_text, strlen(response_text));
switch(err/100) {
case 4:
ct->result = SMM_LOCAL_TEMP_NOK;
done = 1;
break;
case 5:
ct->result = SMM_LOCAL_PERM_NOK;
done = 1;
break;
case 2:
if (END == state) {
ct->result = SMM_LOCAL_OK;
response_text = (char*)ADDRESS_VALID;
done = 1;
break;
}
}
break;
default:
syslog(LOG_DEBUG, "checker_thread (id=%d), unexpected error in smtp dialog", ct->id);
ct->result = SMM_LOCAL_TEMP_NOK;
response_text = (char*)UNEXPECTED_ERROR;
done = 1;
break;
}
}
smtp_close(smtp);
ct->output = (char*) malloc(sizeof(char) * (strlen(response_text)+1));
strcpy(ct->output, response_text);
smtp_destroy(smtp);
syslog(LOG_DEBUG, "checker_thread (id=%d) goes to terminator queue", ct->id);
queue_put(ct->checker_terminator_queue, ct);
}
static void *worker_thread(void *arg) {
worker_thread_t *wt = (worker_thread_t*) arg;
char *domain_part;
unsigned int *mx_ip_addresses;
int result = SMM_TEMP_NOK;
int i, err;
pthread_t tid;
checker_thread_t *ct = NULL;
char *cached_output = NULL;
static const char *NOT_AN_ADDRESS = "not an email-address, no @ in it";
static const char *DEFAULT_ANSWER = "default answer";
static const char *NO_MX_AVAILABLE = "no MX available";
static const char *NO_PERM_RESULT = "no checker returned permanent result";
syslog(LOG_DEBUG, "worker_thread %d started, %s", wt->id, wt->input);
wt->thread = pthread_self();
/* separate domain part, some sanity checks */
domain_part = strchr(wt->input, '@');
if (NULL == domain_part) {
wt->output = (char*) NOT_AN_ADDRESS;
result = SMM_LOCAL_PERM_NOK;
} else {
if (0 == cache_lookup(wt->vwh->vch, wt->input, &result, &cached_output)) {
syslog(LOG_DEBUG, "worker_thread: got a cached result for %s -> %d, %s",
wt->input, result, cached_output);
wt->output = cached_output;
} else {
domain_part += 1;
syslog(LOG_DEBUG, "worker_thread: looking up %s", domain_part);
mx_ip_addresses = get_mx_ip_addresses(domain_part);
if (NULL == mx_ip_addresses) {
wt->output = (char*) NO_MX_AVAILABLE;
result = SMM_LOCAL_PERM_NOK;
} else {
for (i = 0; (*(mx_ip_addresses+i) != 0) && (i < wt->vwh->vch->max_checker_threads); i++) {
unsigned int address = *(mx_ip_addresses+i);
syslog(LOG_DEBUG, "worker_thread: starting checker thread to %d.%d.%d.%d, for email-address %s",
(address&0xff000000)>>24, (address&0x00ff0000)>>16,
(address&0x0000ff00)>>8, (address&0x000000ff),
wt->input);
ct = (checker_thread_t*) malloc(sizeof(checker_thread_t));
ct->id = i+1; /* id of ct should start with 1, same as id of wt */
ct->ip_address = address;
ct->email_address = wt->input;
ct->output = NULL;
ct->result = SMM_TEMP_NOK;
ct->checker_terminator_queue = wt->checker_terminator_queue;
ct->vwh = wt->vwh;
err = pthread_create(&tid, NULL, &checker_thread, ct);
if (-1 == err) {
syslog(LOG_ERR, "worker_thread: unable to create checker thread");
free(ct);
} else {
wt->checker_cnt += 1;
}
}
free(mx_ip_addresses);
while (wt->checker_cnt > 0) {
ct = (checker_thread_t*) queue_get_wait(wt->checker_terminator_queue);
wt->checker_cnt -= 1;
syslog(LOG_DEBUG, "worker_thread: got checker result for %d.%d.%d.%d: %s -> %d, %s",
((ct->ip_address)&0xff000000)>>24, ((ct->ip_address)&0x00ff0000)>>16,
((ct->ip_address)&0x0000ff00)>>8, ((ct->ip_address)&0x000000ff),
wt->input, ct->result, ct->output);
pthread_join(ct->thread, NULL);
syslog(LOG_DEBUG, "worker_thread: checker thread joined");
if ((SMM_LOCAL_TEMP_NOK != ct->result) &&
(SMM_TEMP_NOK != ct->result)) {
syslog(LOG_DEBUG, "worker_thread: this is a permanent result, returning");
wt->output = ct->output;
result = ct->result;
cache_insert(wt->vwh->vch, wt->input, ct->result, ct->output);
/* ct will be freed later, since its output is needed below */
break; /* exit from the ct-collecting while loop, leave the rest of the ct's
for the cleanup */
} else {
syslog(LOG_DEBUG, "worker_thread: this is a temporary result, continue to wait");
wt->output = (char*) NO_PERM_RESULT;
result = SMM_LOCAL_TEMP_NOK;
/* we've collected the ct but its output is not longer need, so free it here */
free(ct->output);
free(ct);
ct = NULL;
}
} /* ct-collecting while */
} /* else: cache lookup */
} /* else: no mx available */
} /* else: not an address */
/* -------------------------------------------------------------------- */
syslog(LOG_DEBUG, "worker_thread %d waiting for mutex", wt->id);
pthread_mutex_lock(wt->mutex);
if (wt->result->id == wt->id) {
syslog(LOG_DEBUG, "worker_thread %d returned result", wt->id);
/* we can write the result */
wt->result->output = (char*) malloc(sizeof(char) * (strlen(wt->output)+15)); /* enough for the output
plus <><> and prefix */
switch (result) {
case SMM_LOCAL_TEMP_NOK:
sprintf(wt->result->output, "<TNOK><%s>", wt->output);
result = SMM_OK;
break;
case SMM_LOCAL_PERM_NOK:
sprintf(wt->result->output, "<NOK><%s>", wt->output);
result = SMM_OK;
break;
case SMM_LOCAL_OK:
sprintf(wt->result->output, "<OK><%s>", wt->output);
result = SMM_OK;
break;
default:
strcpy(wt->result->output, wt->output);
break;
}
wt->result->result = result;
pthread_cond_signal(wt->cond);
} else {
syslog(LOG_DEBUG, "worker_thread %d drops result", wt->id);
/* result not longer interested */
}
pthread_mutex_unlock(wt->mutex);
/* we've collected the ct, so we need to free it.
free only if not NULL to avoid double-free in case of
no permenant result at all */
if (NULL != ct) {
free(ct->output);
free(ct);
}
/* if cached_output is not NULL, it was allocated for us and
we need to free it */
if (NULL != cached_output) {
free(cached_output);
}
syslog(LOG_DEBUG, "worker_thread %d goes to terminator queue", wt->id);
queue_put(wt->terminator_queue, wt);
}

129
smmapdfw/verifysender.m4 Normal file
View File

@ -0,0 +1,129 @@
VERSIONID(`$Id$')
divert(-1)
define(`_USAGE_', `dnl
errprint(`*** ERROR: missing argument for FEATURE(verifysender):
Usage: FEATURE(`verifysender', `_mode_', `_return_', `_dummy_')
_mode_: black or white
_return_: temp or perm
_dummy_: active (actually returning errors) or dummy (just log what it would return)
found: $1
')')
ifelse(_ARG_, `black', `', `
ifelse(_ARG_, `white', `', `
_USAGE_(`_mode_: ('_ARG_`)
')
')')
ifelse(_ARG2_, `temp', `', `
ifelse(_ARG2_, `perm', `', `
_USAGE_(`_return_: ('_ARG2_`)
')
')')
ifelse(_ARG3_, `active', `', `
ifelse(_ARG3_, `dummy', `', `
_USAGE_(`_return_: ('_ARG3_`)
')
')')
define(`_mode_', _ARG_)
define(`_return_', _ARG2_)
define(`_dummy_', _ARG3_)
dnl errprint(`*** _mode_: '_mode_`
dnl ')
dnl errprint(`*** _return_: '_return_`
dnl ')
dnl errprint(`*** _dummy_: '_dummy_`
dnl ')
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_', `hash -o /etc/mail/verifier-black-list')', `
define(`_VERIFIER_BLACKLIST_', confVERIFIER_BLACKLIST)')
ifelse(defn(`confVERIFIER_WHITELIST'), `', `
define(`_VERIFIER_WHITELIST_', `hash -o /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 _VERIFIER_WHITELIST_', `dnl
Kverifier_helper _VERIFIER_BLACKLIST_')
Kvht_logger syslog
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< $+ > $: < $1 > < $(verifier $1 $:none $) >
ifelse(_dummy_, `dummy', `dnl
dnl dummy
R< $* > < $* > $@ < ok > $(vht_logger $1 --- $2 $)', `dnl
dnl active
R< $* > < $* > $: < $2 >
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
dnl if we get here, some is wrong with our code
R$* $#error $@ 4.7.1 $: "451 Local configuration error <sv2>"