pep8 and a few other cleanups

This commit is contained in:
Scott Kitterman
2018-03-10 02:45:35 -05:00
parent 6348bdcdc7
commit 70606ac58c
3 changed files with 361 additions and 316 deletions
+45 -23
View File
@@ -42,6 +42,7 @@ from dkimpy_milter.util import fold
__version__ = "0.9.5"
FWS = re.compile(r'\r?\n[ \t]+')
class dkimMilter(Milter.Base):
"Milter to check and sign DKIM. Each connection gets its own instance."
@@ -70,7 +71,8 @@ class dkimMilter(Milter.Base):
if milterconfig['InternalHostsObj']:
if milterconfig['InternalHostsObj'].match(ipaddr):
self.internal_connection = True
else: ipaddr = ''
else:
ipaddr = ''
self.connectip = ipaddr
if milterconfig.get('MacroList') and not self.internal_connection:
macrolist = milterconfig.get('MacroList')
@@ -78,8 +80,8 @@ class dkimMilter(Milter.Base):
macroname = macro.split('|')[0]
macroname = '{' + macroname + '}'
macroresult = self.getsymval(macroname)
if (len(macro.split('|')) == 1 and macroresult) or macroresult in \
macro.split('|')[1:]:
if ((len(macro.split('|')) == 1 and macroresult) or macroresult
in macro.split('|')[1:]):
self.internal_connection = True
if milterconfig.get('MacroListVerify'):
macrolist = milterconfig.get('MacroListVerify')
@@ -87,15 +89,16 @@ class dkimMilter(Milter.Base):
macroname = macro.split('|')[0]
macroname = '{' + macroname + '}'
macroresult = self.getsymval(macroname)
if (len(macro.split('|')) == 1 and macroresult) or macroresult in \
macro.split('|')[1:]:
if ((len(macro.split('|')) == 1 and macroresult) or macroresult
in macro.split('|')[1:]):
self.external_connection = True
if self.internal_connection:
connecttype = 'INTERNAL'
else:
connecttype = 'EXTERNAL'
if milterconfig.get('Syslog'):
syslog.syslog("connect from {0} at {1} {2}".format(hostname,hostaddr,connecttype))
syslog.syslog("connect from {0} at {1} {2}"
.format(hostname, hostaddr, connecttype))
return Milter.CONTINUE
# multiple messages can be received on a single connection
@@ -108,9 +111,9 @@ class dkimMilter(Milter.Base):
self.fp = StringIO.StringIO()
self.mailfrom = f
t = parse_addr(f)
if len(t) == 2: t[1] = t[1].lower()
if len(t) == 2:
t[1] = t[1].lower()
self.canon_from = '@'.join(t)
self.user = self.getsymval('{auth_authen}')
self.has_dkim = 0
self.author = None
self.arheaders = []
@@ -156,7 +159,8 @@ class dkimMilter(Milter.Base):
for i, val in enumerate(self.arheaders, 1):
# FIXME: don't delete A-R headers from trusted MTAs
try:
ar = authres.AuthenticationResultsHeader.parse_value(FWS.sub('',val))
ar = (authres.AuthenticationResultsHeader
.parse_value(FWS.sub('', val)))
if ar.authserv_id == self.AuthservID:
self.chgheader('authentication-results', i, '')
if milterconfig.get('Syslog'):
@@ -170,14 +174,18 @@ class dkimMilter(Milter.Base):
domain = milterconfig.get('Domain')
else:
domain = ''
if (self.fdomain in domain) and not milterconfig.get('Mode') == 'v' and not self.external_connection:
if ((self.fdomain in domain) and not milterconfig.get('Mode') == 'v'
and not self.external_connection):
txt = self.fp.read()
self.sign_dkim(txt)
if (self.has_dkim) and (not self.internal_connection) and (milterconfig.get('Mode') == 'v' or milterconfig.get('Mode') == 'sv'):
if ((self.has_dkim) and (not self.internal_connection) and
(milterconfig.get('Mode') == 'v' or
milterconfig.get('Mode') == 'sv')):
txt = self.fp.read()
self.check_dkim(txt)
if self.arresults:
h = authres.AuthenticationResultsHeader(authserv_id = self.AuthservID,
h = authres.AuthenticationResultsHeader(authserv_id=
self.AuthservID,
results=self.arresults)
h = fold(str(h))
if milterconfig.get('Syslog'):
@@ -199,14 +207,17 @@ class dkimMilter(Milter.Base):
try:
if privateRSA:
d = dkim.DKIM(txt)
h = d.sign(milterconfig.get('Selector'), self.fdomain, privateRSA,
canonicalize=(canonicalize[0], canonicalize[1]))
h = d.sign(milterconfig.get('Selector'), self.fdomain,
privateRSA, canonicalize=(canonicalize[0],
canonicalize[1]))
name, val = h.split(': ', 1)
self.addheader(name, val.strip().replace('\r\n', '\n'), 0)
if privateEd25519:
d = dkim.DKIM(txt)
h = d.sign(milterconfig.get('SelectorEd25519'), self.fdomain, privateEd25519,
canonicalize=(canonicalize[0], canonicalize[1]), signature_algorithm='ed25519-sha256')
h = d.sign(milterconfig.get('SelectorEd25519'), self.fdomain,
privateEd25519, canonicalize=(canonicalize[0],
canonicalize[1]),
signature_algorithm='ed25519-sha256')
name, val = h.split(': ', 1)
self.addheader(name, val.strip().replace('\r\n', '\n'), 0)
except dkim.DKIMException as x:
@@ -224,9 +235,13 @@ class dkimMilter(Milter.Base):
try:
res = d.verify(idx=y)
if res:
self.dkim_comment = 'Good {0} bit {1} signature.'.format(d.keysize, d.signature_fields.get(b'a'))
self.dkim_comment = ('Good {0} bit {1} signature.'
.format(d.keysize,
d.signature_fields.get(b'a')))
else:
self.dkim_comment = 'Bad {0} bit {1} signature.'.format(d.keysize, d.signature_fields.get(b'a'))
self.dkim_comment = ('Bad {0} bit {1} signature.'
.format(d.keysize,
d.signature_fields.get(b'a')))
except dkim.DKIMException as x:
self.dkim_comment = str(x)
if milterconfig.get('Syslog'):
@@ -248,7 +263,8 @@ class dkimMilter(Milter.Base):
with os.fdopen(fd, "w+b") as fp:
fp.write(txt)
if milterconfig.get('Syslog'):
syslog.syslog('DKIM: Fail (saved as {0})'.format(fname))
syslog.syslog('DKIM: Fail (saved as {0})'
.format(fname))
else:
syslog.syslog('DKIM: Fail ({0})'.format(d.domain))
if res:
@@ -258,11 +274,15 @@ class dkimMilter(Milter.Base):
res = False
self.arresults.append(
authres.DKIMAuthenticationResult(result=result,
header_i = self.header_i, header_d = self.header_d, header_a = self.header_a,
result_comment = self.dkim_comment)
header_i=self.header_i,
header_d=self.header_d,
header_a=self.header_a,
result_comment=
self.dkim_comment)
)
return
def main():
# Ugh, but there's no easy way around this.
global milterconfig
@@ -278,7 +298,8 @@ def main():
configFile = sys.argv[1]
milterconfig = config._processConfigFile(filename=configFile)
if milterconfig.get('Syslog'):
facility = eval("syslog.LOG_{0}".format(milterconfig.get('SyslogFacility').upper()))
facility = eval("syslog.LOG_{0}"
.format(milterconfig.get('SyslogFacility').upper()))
syslog.openlog(os.path.basename(sys.argv[0]), syslog.LOG_PID, facility)
setExceptHook()
pid = write_pid(milterconfig)
@@ -291,7 +312,8 @@ def main():
miltername = 'dkimpy-filter'
socketname = milterconfig.get('Socket')
if milterconfig.get('Syslog'):
syslog.syslog('dkimpy-milter started:{0} user:{1}'.format(pid,milterconfig.get('UserID')))
syslog.syslog('dkimpy-milter started:{0} user:{1}'
.format(pid, milterconfig.get('UserID')))
sys.stdout.flush()
Milter.runmilter(miltername, socketname, 240)
own_socketfile(milterconfig)
+61 -45
View File
@@ -52,12 +52,11 @@ defaultConfigData = {
}
#################################
class ConfigException(Exception):
'''Exception raised when there's a configuration file error.'''
pass
#################################
class HostsDataset(object):
'''Hold a group of host related dataset objects'''
@@ -86,34 +85,41 @@ class HostsDataset(object):
self.negative = True
try:
self.item = ipaddress.ip_address(unicode(self.item, "utf-8"))
if isinstance(self.item, ipaddress.IPv4Address): self.isipv4 = True
elif isinstance(self.item, ipaddress.IPv6Address): self.isipv6 = True
if isinstance(self.item, ipaddress.IPv4Address):
self.isipv4 = True
elif isinstance(self.item, ipaddress.IPv6Address):
self.isipv6 = True
except ValueError as e:
try:
self.item = ipaddress.ip_network(unicode(self.item, "utf-8"), strict=False)
if isinstance(self.item, ipaddress.IPv4Network): self.isipv4cidr = True
elif isinstance(self.item, ipaddress.IPv6Network): self.isipv6cidr = True
self.item = ipaddress.ip_network(unicode
(self.item, "utf-8"),
strict=False)
if isinstance(self.item, ipaddress.IPv4Network):
self.isipv4cidr = True
elif isinstance(self.item, ipaddress.IPv6Network):
self.isipv6cidr = True
except ValueError as e2:
if self.item[0] == '.' and len(self.item.split('.')) > 2:
self.isdomain = True
elif len(self.item.split('.')) > 1: # It has a '.' in it
self.ishostname = True
else:
raise ConfigException('Unknown dataset item: {0}'.format(item))
raise ConfigException('Unknown dataset item: {0}'
.format(item))
def match(self, connectip):
'''Check if the connect IP is part of the dataset'''
source = ipaddress.ip_address(unicode(connectip, "utf-8"))
for item in self.dataset:
if item.isdomain or item.ishostname:
result = self.matchname(source) # Match host/domain names first
result = self.matchname(source) # Match host/domains first
if result:
return(result)
elif item.isipv4 or item.isipv4cidr:
if isinstance(source, ipaddress.IPv4Address): # Then IPv4/6 addresses
return(self.match4(source)) # or networks depending
elif item.isipv6 or item.isipv6cidr: # on the item type and
if isinstance(source, ipaddress.IPv6Address): # connection type
elif item.isipv4 or item.isipv4cidr: # Then IPv4/6 addresses or
if isinstance(source, ipaddress.IPv4Address): # networks
return(self.match4(source)) # depending on the item type
elif item.isipv6 or item.isipv6cidr: # and connect type
if isinstance(source, ipaddress.IPv6Address):
return(self.match6(source))
def matchname(self, source):
@@ -127,7 +133,7 @@ class HostsDataset(object):
for item in self.dataset:
if item.isdomain:
for ptr in ptrlist:
# Strip the leading '.' off the domain name so exact match works.
# Strip the leading '.' off the domain name for exact match
if item.item[1:] == ptr[-len(item.item)+1:]:
matchdomain = True
negativedomain = item.negative
@@ -212,21 +218,16 @@ class HostsDataset(object):
match = False
return(match)
def dump(self):
for item in self.dataset:
print 'name: {0} ip4: {1} cidr4: {2} ip6: {3} cidr6: {4} host: {5} domain: {6} negat: {7} type: {8}'.format(item.item,
item.isipv4, item.isipv4cidr, item.isipv6, item.isipv6cidr, item.ishostname, item.isdomain,
item.negative, type(item.item))
####################################################################
def _processConfigFile(filename=None, configdata=None, useSyslog=1,
useStderr=0):
'''Load the specified config file, exit and log errors if it fails,
otherwise return a config dictionary.'''
import config
if configdata == None: configdata = config.defaultConfigData
if filename != None:
if configdata is None:
configdata = config.defaultConfigData
if filename is not None:
try:
_readConfigFile(filename, configdata)
except Exception, e:
@@ -238,7 +239,7 @@ def _processConfigFile(filename = None, configdata = None, useSyslog = 1,
sys.exit(1)
return(configdata)
####################
def _find_boolean(item):
if type(item) == int:
item = str(item)
@@ -249,14 +250,15 @@ def _find_boolean(item):
else:
raise dkim.ParameterError()
return item
####################
def _calculate_authserv_id(as_id):
"""Determine AuthservID if needed"""
if as_id == 'HOSTNAME':
as_id = socket.gethostname()
return as_id
####################
def _dataset_to_list(dataset):
"""Convert a dataset (as defined in dkimpymilter.8) and return a python
list of values."""
@@ -299,16 +301,19 @@ def _dataset_to_list(dataset):
elif dataset[:3] == 'db:':
dbname = dataset[3:]
else:
raise dkim.ParameterError('Unimplmented dataset type: {0}'.format(type(dataset)))
raise dkim.ParameterError('Unimplmented dataset type: {0}'
.format(type(dataset)))
if whichdb.whichdb(dbname) != 'dbhash':
raise dkim.ParameterError('Unimplmented dataset type: {0}'.format(type(dataset)))
raise dkim.ParameterError('Unimplmented dataset type: {0}'
.format(type(dataset)))
#TODO replace this with code to use db maps
raise dkim.ParameterError('Unsupported dataset db dataset not yet used: {0}'.format(type(dataset)))
raise dkim.ParameterError('Unsupported dataset db datase: {0}'
.format(type(dataset)))
raise dkim.ParameterError('Unimplmented dataset type: {0}'
.format(type(dataset)))
raise dkim.ParameterError('Unimplmented dataset type: {0}'.format(type(dataset)))
###############################################################
commentRx = re.compile(r'^(.*)#.*$')
def _readConfigFile(path, configData=None, configGlobal={}):
'''Reads a configuration file from the specified path, merging it
with the configuration data specified in configData. Returns a
@@ -316,8 +321,10 @@ def _readConfigFile(path, configData = None, configGlobal = {}):
read from path.'''
debugLevel = configGlobal.get('debugLevel', 0)
if debugLevel >= 5: syslog.syslog('readConfigFile: Loading "%s"' % path)
if configData == None: configData = {}
if debugLevel >= 5:
syslog.syslog('readConfigFile: Loading "%s"' % path)
if configData is None:
configData = {}
nameConversion = {
'AuthservID': 'str',
'Syslog': 'bool',
@@ -345,26 +352,30 @@ def _readConfigFile(path, configData = None, configGlobal = {}):
try:
mode = os.stat(path)[0]
except OSError, e:
syslog.syslog(syslog.LOG_ERR,'ERROR stating "%s": %s' % ( path, e.strerror ))
syslog.syslog(syslog.LOG_ERR, 'ERROR stating "%s": %s'
% (path, e.strerror))
return(configData)
if not stat.S_ISREG(mode):
syslog.syslog(syslog.LOG_ERR,'ERROR: is not a file: "%s", mode=%s' % ( path, oct(mode) ))
syslog.syslog(syslog.LOG_ERR, 'ERROR: is not a file: "%s", mode=%s'
% (path, oct(mode)))
return(configData)
# load file
fp = open(path, 'r')
while 1:
line = fp.readline()
if not line: break
if not line:
break
# parse line
line = line.split('#', 1)[0].strip()
if not line: continue
if not line:
continue
data = line.split()
if len(data) != 2:
if len(data) == 1:
if debugLevel >= 1:
syslog.syslog('Configuration item "%s" not defined in file "%s"'
syslog.syslog('Config item "%s" not defined in file "%s"'
% (line, path))
if len(data) == 1:
name = data
@@ -377,11 +388,13 @@ def _readConfigFile(path, configData = None, configGlobal = {}):
# check validity of name
conversion = nameConversion.get(name)
if conversion == None:
syslog.syslog('ERROR: Unknown name "%s" in file "%s"' % ( name, path ))
if conversion is None:
syslog.syslog('ERROR: Unknown name "%s" in file "%s"'
% (name, path))
continue
if debugLevel >= 5: syslog.syslog('readConfigFile: Found entry "%s=%s"'
if debugLevel >= 5:
syslog.syslog('readConfigFile: Found entry "%s=%s"'
% (name, value))
if conversion == 'bool':
configData[name] = _find_boolean(value)
@@ -392,12 +405,15 @@ def _readConfigFile(path, configData = None, configGlobal = {}):
elif conversion == 'dataset':
configData[name] = _dataset_to_list(value)
else:
syslog.syslog(str('name: ' + name + ' value: ' + value + ' conversion: ' + conversion))
syslog.syslog(str('name: ' + name + ' value: ' + value +
' conversion: ' + conversion))
configData[name] = conversion(value)
fp.close()
try:
configData['AuthservID'] = _calculate_authserv_id(configData['AuthservID'])
configData['InternalHostsObj'] = HostsDataset(configData['InternalHosts'])
configData['AuthservID'] = _calculate_authserv_id(configData
['AuthservID'])
configData['InternalHostsObj'] = HostsDataset(configData
['InternalHosts'])
except:
pass
+17 -10
View File
@@ -16,6 +16,7 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
def fold(header):
"""Fold a header line into multiple crlf-separated lines at column 72.
Borrowed from dkimpy and updated to only add \n instead of \r\n because
@@ -48,6 +49,7 @@ def fold(header):
header = header[j:]
return pre + header
def user_group(userid):
"""Return user and group from UserID"""
import grp
@@ -63,13 +65,14 @@ def user_group(userid):
running_gid = grp.getgrnam(gidname).gr_gid
return running_uid, running_gid
def drop_privileges(milterconfig):
import os
import syslog
if os.getuid() != 0:
if milterconfig.get('Syslog'):
syslog.syslog('drop_privileges: Not running as root. Cannot drop permissions.')
syslog.syslog('drop_privileges: Not root. No action taken.')
return
# Get user and group
@@ -85,7 +88,7 @@ def drop_privileges(milterconfig):
# Set umask
old_umask = os.umask(milterconfig.get('UMask'))
#################
class ExceptHook:
def __init__(self, useSyslog=1, useStderr=0):
self.useSyslog = useSyslog
@@ -103,12 +106,11 @@ class ExceptHook:
sys.stderr.write(line)
####################
def setExceptHook():
import sys
sys.excepthook = ExceptHook(useSyslog=1, useStderr=1)
####################
def write_pid(milterconfig):
"""Write PID in pidfile. Will not overwrite an existing file."""
import os
@@ -125,10 +127,11 @@ def write_pid(milterconfig):
os.chown(piddir, user, group)
f = open(milterconfig.get('PidFile'), 'w')
if milterconfig.get('Syslog'):
syslog.syslog('Missing pid dir created: {0}'.format(piddir))
syslog.syslog('PID dir created: {0}'.format(piddir))
else:
if milterconfig.get('Syslog'):
syslog.syslog('Unable to write pidfle {0}. IOError: {1}'.format(milterconfig.get('PidFile'), e))
syslog.syslog('Unable to write pidfle {0}. IOError: {1}'
.format(milterconfig.get('PidFile'), e))
raise
f.write(pid)
f.close()
@@ -136,10 +139,13 @@ def write_pid(milterconfig):
os.chown(milterconfig.get('PidFile'), user, group)
else:
if milterconfig.get('Syslog'):
syslog.syslog('Unable to write pidfle {0}. File exists.'.format(milterconfig.get('PidFile')))
raise RuntimeError('Unable to write pidfle {0}. File exists.'.format(milterconfig.get('PidFile')))
syslog.syslog('Unable to write pidfle {0}. File exists.'
.format(milterconfig.get('PidFile')))
raise RuntimeError('Unable to write pidfle {0}. File exists.'
.format(milterconfig.get('PidFile')))
return pid
def own_socketfile(milterconfig):
"""If socket is Unix socket, chown to UserID before dropping privileges"""
import os
@@ -149,7 +155,7 @@ def own_socketfile(milterconfig):
if milterconfig.get('Socket')[:6] == "local:":
os.chown(milterconfig.get('Socket')[6:], user, group)
####################
def read_keyfile(milterconfig, keytype):
"""Read private key from file."""
import syslog
@@ -162,7 +168,8 @@ def read_keyfile(milterconfig, keytype):
keylist = f.readlines()
except IOError as e:
if milterconfig.get('Syslog'):
syslog.syslog('Unable to read keyfile {0}. IOError: {1}'.format(keyfile, e))
syslog.syslog('Unable to read keyfile {0}. IOError: {1}'
.format(keyfile, e))
raise
f.close()
key = ''