- Added support for InternalHosts option (ipaddress and either dns (dnspython)
or pydns (DNS) modules are now required)
This commit is contained in:
@@ -11,6 +11,8 @@
|
||||
added after an existing DKIM signing application to add an Ed25519
|
||||
signature (Thanks to A. Schulze for the patch)
|
||||
- Added support for AuthservID option
|
||||
- Added support for InternalHosts option (ipaddress and either dns (dnspython)
|
||||
or pydns (DNS) modules are now required)
|
||||
|
||||
0.9.3 2018-03-02
|
||||
- Fixup csl dataset processing for single item lists
|
||||
|
||||
@@ -30,11 +30,11 @@ File dataset implemented verified
|
||||
|
||||
0.9.4 (Alpha)
|
||||
AuthservID implemented verified
|
||||
InternalHosts implemented verified
|
||||
|
||||
0.9.5 (Beta)
|
||||
Diagnostics
|
||||
DiagnosticDirectory
|
||||
InternalHosts
|
||||
SyslogSuccess
|
||||
|
||||
1.0.0
|
||||
@@ -42,7 +42,6 @@ Convert dkim-milter-python config
|
||||
No additional features planned
|
||||
|
||||
Plannedataset type support:
|
||||
refile:
|
||||
db:/.db
|
||||
mdb:
|
||||
|
||||
|
||||
@@ -69,8 +69,9 @@ class dkimMilter(Milter.Base):
|
||||
self.AuthservID = self.receiver
|
||||
if hostaddr and len(hostaddr) > 0:
|
||||
ipaddr = hostaddr[0]
|
||||
"""if iniplist(ipaddr,self.conf.internal_connect): FIXME
|
||||
self.internal_connection = True"""
|
||||
if milterconfig['InternalHostsObj']:
|
||||
if milterconfig['InternalHostsObj'].match(ipaddr):
|
||||
self.internal_connection = True
|
||||
else: ipaddr = ''
|
||||
self.connectip = ipaddr
|
||||
if self.internal_connection:
|
||||
|
||||
+169
-2
@@ -32,7 +32,8 @@ import urllib
|
||||
import stat
|
||||
import dkim
|
||||
import socket
|
||||
|
||||
import ipaddress
|
||||
from dnsplug import Session
|
||||
|
||||
# default values
|
||||
defaultConfigData = {
|
||||
@@ -43,7 +44,9 @@ defaultConfigData = {
|
||||
'Socket' : 'local:/var/run/dkimpy-milter/dkimpy-milter.sock',
|
||||
'PidFile' : '/var/run/dkimpy-milter/dkimpy-milter.pid',
|
||||
'UserID' : 'dkimpy-milter',
|
||||
'Canonicalization' : 'relaxed/simple'
|
||||
'Canonicalization' : 'relaxed/simple',
|
||||
'InternalHosts' : '127.0.0.1',
|
||||
'InternalHostsObj' : False
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +55,167 @@ class ConfigException(Exception):
|
||||
'''Exception raised when there's a configuration file error.'''
|
||||
pass
|
||||
|
||||
#################################
|
||||
class HostsDataset(object):
|
||||
'''Hold a group of host related dataset objects'''
|
||||
|
||||
def __init__(self, dataset):
|
||||
self.dataset = []
|
||||
# Self.dataset will end up being a list of DataSetItem(s).
|
||||
for item in dataset:
|
||||
item = item.rstrip(']')
|
||||
item = item.lstrip('[')
|
||||
self.dataset.append(self.DatasetItem(item))
|
||||
|
||||
class DatasetItem(object):
|
||||
'''Individual dataset item'''
|
||||
|
||||
def __init__(self, item):
|
||||
self.item = item
|
||||
self.isipv4 = False
|
||||
self.isipv4cidr = False
|
||||
self.isipv6 = False
|
||||
self.isipv6cidr = False
|
||||
self.ishostname = False
|
||||
self.isdomain = False
|
||||
self.negative = False
|
||||
if self.item[0] == '!':
|
||||
self.item = item[1:]
|
||||
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
|
||||
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
|
||||
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))
|
||||
|
||||
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
|
||||
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
|
||||
return(self.match6(source))
|
||||
|
||||
def matchname(self, source):
|
||||
'''Does source IP address relate to a domain/hostname in the dataset'''
|
||||
match = False
|
||||
matchone = False
|
||||
negativeone = False
|
||||
matchdomain = False
|
||||
negativedomain = False
|
||||
ptrlist = self.getptr(source)
|
||||
for item in self.dataset:
|
||||
if item.isdomain:
|
||||
for ptr in ptrlist:
|
||||
# Strip the leading '.' off the domain name so exact match works.
|
||||
if item.item[1:] == ptr[-len(item.item)+1:]:
|
||||
matchdomain = True
|
||||
negativedomain = item.negative
|
||||
elif item.ishostname:
|
||||
for ptr in ptrlist:
|
||||
if item.item == ptr:
|
||||
matchone = True
|
||||
negativeone = item.negative
|
||||
if matchdomain and not negativedomain:
|
||||
match = True
|
||||
if matchone and not negativeone:
|
||||
return True
|
||||
if matchone and negativeone:
|
||||
match = False
|
||||
return(match)
|
||||
|
||||
def getptr(self, source):
|
||||
'''Get validated PTR name of IP address'''
|
||||
results = []
|
||||
s = Session()
|
||||
ptrnames = s.dns(source.reverse_pointer, 'PTR')
|
||||
for name in ptrnames:
|
||||
if isinstance(source, ipaddress.IPv4Address):
|
||||
ips = s.dns(name, 'A')
|
||||
for ip in ips:
|
||||
ip = ipaddress.IPv4Address(unicode(ip, 'UTF-8'))
|
||||
if ip == source:
|
||||
results.append(name)
|
||||
if isinstance(source, ipaddress.IPv6Address):
|
||||
ips = s.dns(name, 'AAAA')
|
||||
for ip in ips:
|
||||
ip = ipaddress.IPv6Address(unicode(ip, 'UTF-8'))
|
||||
if ip == source:
|
||||
results.append(name)
|
||||
return results
|
||||
|
||||
def match4(self, source):
|
||||
'''Is the source IP related to a IPv4 address/network in the dataset'''
|
||||
match = False
|
||||
matchone = False
|
||||
negativeone = False
|
||||
matchcidr = False
|
||||
negativecidr = False
|
||||
for item in self.dataset:
|
||||
if item.isipv4:
|
||||
if source == item.item:
|
||||
matchone = True
|
||||
negativeone = item.negative
|
||||
elif item.isipv4cidr:
|
||||
if source in item.item:
|
||||
matchcidr = True
|
||||
negativecidr = item.negative
|
||||
if matchcidr and not negativecidr:
|
||||
match = True
|
||||
if matchone and not negativeone:
|
||||
return True
|
||||
if matchone and negativeone:
|
||||
match = False
|
||||
return(match)
|
||||
|
||||
def match6(self, source):
|
||||
'''Is the source IP realted to a IPv6 address/network in the dataset'''
|
||||
match = False
|
||||
matchone = False
|
||||
negativeone = False
|
||||
matchcidr = False
|
||||
negativecidr = False
|
||||
for item in self.dataset:
|
||||
if item.isipv6:
|
||||
if source == item.item:
|
||||
matchone = True
|
||||
negativeone = item.negative
|
||||
elif item.isipv6cidr:
|
||||
if source in item.item:
|
||||
matchcidr = True
|
||||
negativecidr = item.negative
|
||||
if matchcidr and not negativecidr:
|
||||
match = True
|
||||
if matchone and not negativeone:
|
||||
return True
|
||||
if matchone and negativeone:
|
||||
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):
|
||||
@@ -168,6 +332,8 @@ def _readConfigFile(path, configData = None, configGlobal = {}):
|
||||
'Selector' : 'str',
|
||||
'SelectorEd25519': 'str',
|
||||
'Canonicalization' : 'str',
|
||||
'InternalHosts' : 'dataset',
|
||||
'InternalHostsObj': 'bool',
|
||||
}
|
||||
|
||||
# check to see if it's a file
|
||||
@@ -226,6 +392,7 @@ def _readConfigFile(path, configData = None, configGlobal = {}):
|
||||
fp.close()
|
||||
try:
|
||||
configData['AuthservID'] = _calculate_authserv_id(configData['AuthservID'])
|
||||
configData['InternalHostsObj'] = HostsDataset(configData['InternalHosts'])
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
+1
-11
@@ -156,7 +156,7 @@ values, or to a file that contains them, or a database containing the data.
|
||||
|
||||
Some data sets require that the value contain more than one entry. How this
|
||||
is done depends on which data set type is used. Not all these datasets are
|
||||
currently used by dkimp-milter. See
|
||||
currently used by dkimpy-milter. See
|
||||
.B dkimpy-milter.conf(5)
|
||||
for details about specific options and which dataset types they use.
|
||||
|
||||
@@ -171,16 +171,6 @@ are ignored, and the hash ("#") character denotes the start of a comment.
|
||||
If a value contains multiple entries, the entries should be separated by
|
||||
colons.
|
||||
.TP
|
||||
.I b)
|
||||
If the string begins with "refile:", then the remainder of the string is
|
||||
presumed to specify a file that contains a set of patterns, one per line,
|
||||
and their associated values. The pattern is taken as the start of the line
|
||||
to the first whitespace, and the portion after that whitespace is taken as
|
||||
the value to be used when that pattern is matched. Patterns are simple
|
||||
wildcard patterns, matching all text except that the asterisk ("*") character
|
||||
is considered a wildcard. If a value contains multiple entries, the entries
|
||||
should be separated by colons. [Not implemented yet]
|
||||
.TP
|
||||
.I c)
|
||||
If the string begins with "db:" and the program was compiled with
|
||||
Sleepycat DB support, then the remainder of the string is presumed to
|
||||
|
||||
@@ -55,6 +55,6 @@ setup(
|
||||
(os.path.join('/lib', 'systemd', 'system'),
|
||||
['system/dkimpy-milter.service']),(os.path.join('/etc', 'init.d'),
|
||||
['system/dkimpy-milter'])],
|
||||
install_requires = ['dkimpy>=0.7', 'pymilter', 'authres>=1.1.0', 'PyNaCl'],
|
||||
install_requires = ['dkimpy>=0.7', 'pymilter', 'authres>=1.1.0', 'PyNaCl', 'ipaddress', 'dns'],
|
||||
zip_safe = False,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user