connection reusing; testing docs

This commit is contained in:
Dominik Chilla 2020-08-09 23:28:53 +02:00
parent 6656e7b81a
commit 7bfc3884d8
3 changed files with 86 additions and 13 deletions

View File

@ -8,8 +8,7 @@ import random
import re import re
import dns.resolver import dns.resolver
from ldap3 import ( from ldap3 import (
Server, Connection, NONE, ALL, set_config_parameter, ALL_ATTRIBUTES, Server, Connection, NONE, set_config_parameter
ALL_OPERATIONAL_ATTRIBUTES, MODIFY_REPLACE, HASHED_NONE, HASHED_SALTED_SHA,
) )
from ldap3.core.exceptions import LDAPException from ldap3.core.exceptions import LDAPException
@ -20,7 +19,6 @@ g_milter_reject_message = 'Security policy violation!'
g_milter_tmpfail_message = 'Service temporarily not available! Please try again later.' g_milter_tmpfail_message = 'Service temporarily not available! Please try again later.'
g_re_domain = re.compile(r'^.*@(\S+)$', re.IGNORECASE) g_re_domain = re.compile(r'^.*@(\S+)$', re.IGNORECASE)
g_re_spf_regex = re.compile(r'.*', re.IGNORECASE) g_re_spf_regex = re.compile(r'.*', re.IGNORECASE)
g_re_expected_txt_data = ''
g_loglevel = logging.INFO g_loglevel = logging.INFO
g_milter_mode = 'test' g_milter_mode = 'test'
g_ignored_next_hops = {} g_ignored_next_hops = {}
@ -31,6 +29,10 @@ g_ldap_bindpw = ''
class SOSMilter(Milter.Base): class SOSMilter(Milter.Base):
# Each new connection is handled in an own thread # Each new connection is handled in an own thread
def __init__(self): def __init__(self):
self.reset()
def reset(self):
self.client_ip = None
self.is_null_sender = False self.is_null_sender = False
self.env_from = None self.env_from = None
self.env_from_domain = None self.env_from_domain = None
@ -42,6 +44,7 @@ class SOSMilter(Milter.Base):
self.mconn_id = g_milter_name + ': ' + ''.join( self.mconn_id = g_milter_name + ': ' + ''.join(
random.choice(string.ascii_lowercase + string.digits) for _ in range(8) random.choice(string.ascii_lowercase + string.digits) for _ in range(8)
) )
logging.debug(self.mconn_id + " RESET")
# Not registered/used callbacks # Not registered/used callbacks
@Milter.nocallback @Milter.nocallback
@ -64,6 +67,18 @@ class SOSMilter(Milter.Base):
return Milter.CONTINUE return Milter.CONTINUE
def envfrom(self, mailfrom, *str): def envfrom(self, mailfrom, *str):
# Instance member values remain within reused SMTP-connections!
if self.client_ip is not None:
# Milter connection reused!
logging.debug(self.mconn_id + "/FROM connection reused!")
self.reset()
self.client_ip = self.getsymval('{client_addr}')
if self.client_ip is None:
logging.error(self.mconn_id + " FROM exception: could not retrieve milter-macro ({client_addr})!")
self.setreply('450','4.7.1', g_milter_tmpfail_message)
return Milter.TEMPFAIL
else:
logging.debug(self.mconn_id + "/FROM client_ip={0}".format(self.client_ip))
try: try:
# DSNs/bounces are not relevant # DSNs/bounces are not relevant
if(mailfrom == '<>'): if(mailfrom == '<>'):
@ -90,7 +105,7 @@ class SOSMilter(Milter.Base):
try: try:
g_ldap_conn.search(os.environ['LDAP_SEARCH_BASE'], g_ldap_conn.search(os.environ['LDAP_SEARCH_BASE'],
filter, filter,
attributes=[ALL_ATTRIBUTES] attributes=[]
) )
if len(g_ldap_conn.entries) != 0: if len(g_ldap_conn.entries) != 0:
self.is_env_from_domain_in_ldap = True self.is_env_from_domain_in_ldap = True
@ -105,9 +120,7 @@ class SOSMilter(Milter.Base):
try: try:
dns_response = dns.resolver.resolve(self.env_from_domain, 'TXT') dns_response = dns.resolver.resolve(self.env_from_domain, 'TXT')
except dns.resolver.NoAnswer as e: except dns.resolver.NoAnswer as e:
logging.warning(self.mconn_id + logging.warning(self.mconn_id + " /FROM " + e.msg)
" /FROM " + e.msg
)
# accept message if DNS-resolver fails # accept message if DNS-resolver fails
return Milter.CONTINUE return Milter.CONTINUE
except dns.resolver.NXDOMAIN as e: except dns.resolver.NXDOMAIN as e:
@ -185,7 +198,7 @@ class SOSMilter(Milter.Base):
"Passing message due to ignored next-hop=" + self.next_hop "Passing message due to ignored next-hop=" + self.next_hop
) )
return Milter.CONTINUE return Milter.CONTINUE
if self.is_env_from_domain_in_ldap: if self.is_env_from_domain_in_ldap and g_milter_mode != 'reject':
logging.info(self.mconn_id + '/' + self.queue_id + "/EOM " + logging.info(self.mconn_id + '/' + self.queue_id + "/EOM " +
"5321_from_domain={0} (LDAP) has a broken SPF-record!".format(self.env_from_domain) "5321_from_domain={0} (LDAP) has a broken SPF-record!".format(self.env_from_domain)
) )
@ -199,11 +212,11 @@ class SOSMilter(Milter.Base):
"addheader() failed: " + traceback.format_exc() "addheader() failed: " + traceback.format_exc()
) )
ex = str( ex = str(
" SPF-record (-all) of 5321_from_domain=" "SPF-record (-all) of 5321_from_domain="
+ self.env_from_domain + " does not permit us to relay this message!" + self.env_from_domain + " does not permit us to relay this message!"
) )
logging.info(self.mconn_id + '/' + self.queue_id + "/EOM " + logging.info(self.mconn_id + '/' + self.queue_id + "/EOM " +
"mode=" + g_milter_mode + ' ' + ex "mode=" + g_milter_mode + ' client=' + self.client_ip + ' ' + ex
) )
if g_milter_mode == 'reject': if g_milter_mode == 'reject':
self.setreply('550','5.7.1', self.setreply('550','5.7.1',
@ -218,11 +231,13 @@ class SOSMilter(Milter.Base):
def abort(self): def abort(self):
# Client disconnected prematurely # Client disconnected prematurely
logging.debug(self.mconn_id + "/ABORT")
return Milter.CONTINUE return Milter.CONTINUE
def close(self): def close(self):
# Always called, even when abort is called. # Always called, even when abort is called.
# Clean up any external resources here. # Clean up any external resources here.
logging.debug(self.mconn_id + "/CLOSE")
return Milter.CONTINUE return Milter.CONTINUE
if __name__ == "__main__": if __name__ == "__main__":

23
tests/README.md Normal file
View File

@ -0,0 +1,23 @@
# prepare testing env
```
export TLD=de
export SLD=domain
export MILTER_MODE=reject
export MILTER_SOCKET=inet:12345
export LOG_LEVEL=debug
export SPF_REGEX="^.*include:_spf\.blah\.blub.*$"
export LDAP_ENABLED=yepp
export LDAP_SERVER_URI="ldap://ldap-master-staging.int.${SLD}.${TLD}"
export LDAP_SEARCH_BASE="ou=domains,dc=${SLD},dc=${TLD}"
export LDAP_QUERY_FILTER='(dc=%d)'
export IGNORED_NEXT_HOPS=test.next-host
```
# start milter
`python3 app/sos-milter.py`
# execute `miltertest`
First of all install the `miltertest` binary. Under debian based distros
it´s located in the `opendkim-tools` package.
`miltertest -v -D socket=inet:12345@127.0.0.111 -s tests/miltertest.lua`

View File

@ -1,8 +1,8 @@
-- https://mopano.github.io/sendmail-filter-api/constant-values.html#com.sendmail.milter.MilterConstants -- https://mopano.github.io/sendmail-filter-api/constant-values.html#com.sendmail.milter.MilterConstants
-- http://www.opendkim.org/miltertest.8.html -- http://www.opendkim.org/miltertest.8.html
--conn = mt.connect("inet:8020@10.42.50.2") -- socket must be defined as miltertest global variable (-D)
conn = mt.connect("inet:12345@127.0.0.1") conn = mt.connect(socket)
if conn == nil then if conn == nil then
error "mt.connect() failed" error "mt.connect() failed"
end end
@ -10,7 +10,42 @@ end
mt.set_timeout(3) mt.set_timeout(3)
-- 5321.FROM + MACROS -- 5321.FROM + MACROS
mt.macro(conn, SMFIC_MAIL, "i", "test-id",'{rcpt_host}', "test.next-hostx") mt.macro(conn, SMFIC_MAIL, '{client_addr}', "127.128.129.130", "i", "TestQueueId",'{rcpt_host}', "test.next-host")
if mt.mailfrom(conn, "dominik@dc-it-con.de") ~= nil then
error "mt.mailfrom() failed"
end
if mt.getreply(conn) ~= SMFIR_CONTINUE then
error "mt.mailfrom() unexpected reply"
end
-- 5321.RCPT
if mt.rcptto(conn, "info@dc-it-con.de") ~= nil then
error "mt.rcptto() failed"
end
if mt.getreply(conn) ~= SMFIR_CONTINUE then
error "mt.rcptto() unexpected reply"
end
-- EOM
if mt.eom(conn) ~= nil then
error "mt.eom() failed"
end
mt.echo("EOM: " .. mt.getreply(conn))
if mt.getreply(conn) == SMFIR_CONTINUE then
mt.echo("EOM-continue")
elseif mt.getreply(conn) == SMFIR_REPLYCODE then
mt.echo("EOM-reject")
end
if not mt.eom_check(conn, MT_HDRADD, "X-SOS-Milter") then
mt.echo("no header added")
else
mt.echo("X-SOS-Milter header added -> LDAP-Domain with broken SPF")
end
--asdf
-- 5321.FROM + MACROS
mt.macro(conn, SMFIC_MAIL, '{client_addr}', "127.128.129.130", "i", "TestQueueId",'{rcpt_host}', "test.next-hostx")
if mt.mailfrom(conn, "dominik@dc-it-con.de") ~= nil then if mt.mailfrom(conn, "dominik@dc-it-con.de") ~= nil then
error "mt.mailfrom() failed" error "mt.mailfrom() failed"
end end