Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5cb2fbb1a5 | |||
| c74d030c06 | |||
| c2409105dc | |||
| 16ab67db0f | |||
| 6d1c796a5e | |||
| 84803d3779 | |||
| 815e1a612c | |||
| c3d6bce238 | |||
| ea2ef10438 | |||
| 039fcc54fd | |||
| e378fb0266 | |||
| 53368939fa | |||
| 1a0abcddc7 | |||
| 08a13fea9e | |||
| b4da312ea7 | |||
| 52c7ee02af | |||
| 44d8924060 | |||
| 88c17516d9 | |||
| 791f8d80de | |||
| 7b37e2cb8d | |||
| 7be865d7d7 | |||
| e67a1b3745 | |||
| bf578e7b86 | |||
| 04ef3629d7 | |||
| 489238dff0 | |||
| a8bf7104bc |
@@ -1,3 +1,34 @@
|
|||||||
|
1.2.3 2023-02-26
|
||||||
|
- Improve support for non-ASCII email messages. Anything UTF-8 should work
|
||||||
|
(including correct signing/verification). For messages that contain header
|
||||||
|
fields with non-ASCII or UTF-8 content, signatures are likely fail
|
||||||
|
verification, but the milter should continue to run. (Thanks to Casper
|
||||||
|
Bruun for help with this)
|
||||||
|
- Set minimum pymilter and dkimpy versions in setup.py to those that will
|
||||||
|
work reliably with non-ASCII content.
|
||||||
|
- Fixed support for percent in KeyTable - Thanks to Mika Tiainen
|
||||||
|
- Fix formatting for MinimumKeyBits in dkimpy-milter.conf(5)
|
||||||
|
(Closes: #995335)
|
||||||
|
- Reset the i= signature identity in get_identities_sign() (Closes: #981157)
|
||||||
|
- Improve documentation of inter-relationship between Mode, InternalHosts,
|
||||||
|
MacroList, and MacroListVerify options in dkimpy-milter.conf.5 (Closes:
|
||||||
|
#969215)
|
||||||
|
- Fix subdomain signing with top-level organizational domain (LP: #1999434)
|
||||||
|
- Thanks to Matthias Hunstock for the report and the fix
|
||||||
|
- Fix comma separated list processing in dkimpy_milter/config.py
|
||||||
|
(LP: #1901445)
|
||||||
|
|
||||||
|
1.2.2 2020-08-09
|
||||||
|
- Improve README.md formating for markdown display on pypi
|
||||||
|
- Improve documentation in dkimpy-milter.conf (5) and README.md for signing
|
||||||
|
for multiple domains (Thanks to Stefano Rivera)
|
||||||
|
- Minimal fix for dnspython 2.0.0 compatibility (still works with 1.16.0)
|
||||||
|
|
||||||
|
1.2.1 2020-01-04
|
||||||
|
- Fix expand option not to fail if files are missing since socket activation
|
||||||
|
service files are not shipped in the sdist
|
||||||
|
- Correct dkimpy-milter.conf file install location to match expand locations
|
||||||
|
|
||||||
1.2.0 2020-01-03
|
1.2.0 2020-01-03
|
||||||
- Add support for SigningTable, KeyTable, and KeyTableEd25519 (LP: #1797397)
|
- Add support for SigningTable, KeyTable, and KeyTableEd25519 (LP: #1797397)
|
||||||
- Add support for specifying MinimumKeyBits for RSA signatures
|
- Add support for specifying MinimumKeyBits for RSA signatures
|
||||||
|
|||||||
@@ -52,8 +52,9 @@ The package includes a custom setup command called expand. It allows various
|
|||||||
file locations in init scripts, man pages, and config files to be over-ridden
|
file locations in init scripts, man pages, and config files to be over-ridden
|
||||||
at install time.
|
at install time.
|
||||||
|
|
||||||
expand: Expand @@ variables in input files, simlar to make macros.
|
|
||||||
user_options:
|
expand: Expand @@ variables in input files, simlar to make macros.
|
||||||
|
user_options:
|
||||||
--sysconfigdir=, e: Specify system configuration directory.
|
--sysconfigdir=, e: Specify system configuration directory.
|
||||||
--sbindir=, s: Specify system binary directory [not used].
|
--sbindir=, s: Specify system binary directory [not used].
|
||||||
--bindir=, b: Specify binary directory.
|
--bindir=, b: Specify binary directory.
|
||||||
@@ -61,13 +62,13 @@ user_options:
|
|||||||
|
|
||||||
As an example, to change the run directory to /var/run, one would do:
|
As an example, to change the run directory to /var/run, one would do:
|
||||||
|
|
||||||
python3 setup.py expand --rundir=/var/run
|
python3 setup.py expand --rundir=/var/run
|
||||||
[sudo] python3 setup.py install --single-version-externally-managed \
|
[sudo] python3 setup.py install --single-version-externally-managed \
|
||||||
--record=/dev/null
|
--record=/dev/null
|
||||||
|
|
||||||
or in a single step (the order matters):
|
or in a single step (the order matters):
|
||||||
|
|
||||||
[sudo] python3 setup.py expand --rundir=/var/run install \
|
[sudo] python3 setup.py expand --rundir=/var/run install \
|
||||||
--single-version-externally-managed \
|
--single-version-externally-managed \
|
||||||
--record=/dev/null
|
--record=/dev/null
|
||||||
|
|
||||||
@@ -87,7 +88,7 @@ tools available to create them. Keys must (RFC 8302) have a minimum size of
|
|||||||
1024 bits and should have a size of at least 2048 bits. The dknewkey script
|
1024 bits and should have a size of at least 2048 bits. The dknewkey script
|
||||||
that is provided with dkimpy is one such tool:
|
that is provided with dkimpy is one such tool:
|
||||||
|
|
||||||
dknewkey exampleprivkey
|
dknewkey exampleprivkey
|
||||||
|
|
||||||
will produce both the private key file (.key suffix) and a file with the DKIM
|
will produce both the private key file (.key suffix) and a file with the DKIM
|
||||||
public key record to be published DNS (.dns suffix). RSA is the default key
|
public key record to be published DNS (.dns suffix). RSA is the default key
|
||||||
@@ -99,7 +100,7 @@ There is no standardized non-binary representation for Ed25519 private keys,
|
|||||||
so in order to generate Ed25519 keys for dkimpy-milter, dkimpy specific tools
|
so in order to generate Ed25519 keys for dkimpy-milter, dkimpy specific tools
|
||||||
must be used to be compatible. The same dknewkey script support Ed25519:
|
must be used to be compatible. The same dknewkey script support Ed25519:
|
||||||
|
|
||||||
dknewkey --ktype ed25519 anothernewkey
|
dknewkey --ktype ed25519 anothernewkey
|
||||||
|
|
||||||
will provide both the private key file (.key suffix) and a file with the DKIM
|
will provide both the private key file (.key suffix) and a file with the DKIM
|
||||||
public key record to be published DNS (.dns suffix). Ed25519 keys do not have
|
public key record to be published DNS (.dns suffix). Ed25519 keys do not have
|
||||||
@@ -135,9 +136,9 @@ for the above might look like this:
|
|||||||
comkey example.com:bar:/usr/local/etc/dkim/keys/excom
|
comkey example.com:bar:/usr/local/etc/dkim/keys/excom
|
||||||
netkey example.net:baz:/usr/local/etc/dkim/keys/exnet
|
netkey example.net:baz:/usr/local/etc/dkim/keys/exnet
|
||||||
|
|
||||||
If also signing with ed25519, specify a KeyTableEd25519 pointing to the keys
|
If also signing with ed25519, specify a KeyTableEd25519, with the same
|
||||||
needed for ed25519. Both KeyTable and KeyTableEd25519 are evaluated if there
|
names, pointing to the keys needed for ed25519. Both KeyTable and
|
||||||
is a SigningTable (see below).
|
KeyTableEd25519 are evaluated if there is a SigningTable (see below).
|
||||||
|
|
||||||
Per the documentation, multi-field data sets that are made of flat files have
|
Per the documentation, multi-field data sets that are made of flat files have
|
||||||
the fields separated by colons, but the key and value(s) are separated by
|
the fields separated by colons, but the key and value(s) are separated by
|
||||||
@@ -185,7 +186,7 @@ The dkimpy-milter drops priviledges after setup to the user/group specified in
|
|||||||
UserID. During initial setup, this system user needs to be manually created.
|
UserID. During initial setup, this system user needs to be manually created.
|
||||||
As an example, using the default dkimpy-user on Debian, the command would be:
|
As an example, using the default dkimpy-user on Debian, the command would be:
|
||||||
|
|
||||||
[sudo] adduser --system --no-create-home --quiet --disabled-password \
|
[sudo] adduser --system --no-create-home --quiet --disabled-password \
|
||||||
--disabled-login --shell /bin/false --group \
|
--disabled-login --shell /bin/false --group \
|
||||||
--home /run/dkimpy-milter dkimpy-milter
|
--home /run/dkimpy-milter dkimpy-milter
|
||||||
|
|
||||||
@@ -195,10 +196,10 @@ missing, the milter will create it on startup.
|
|||||||
To start dkimpy-milter with systemd for the first time, you will need to take
|
To start dkimpy-milter with systemd for the first time, you will need to take
|
||||||
the following steps:
|
the following steps:
|
||||||
|
|
||||||
[sudo] systemctl daemon-reload
|
[sudo] systemctl daemon-reload
|
||||||
[sudo] systemctl enable dkimpy-milter
|
[sudo] systemctl enable dkimpy-milter
|
||||||
[sudo] systemctl start dkimpy-milter
|
[sudo] systemctl start dkimpy-milter
|
||||||
[sudo] systemctl status dkimpy-milter (to verify it started correctly)
|
[sudo] systemctl status dkimpy-milter (to verify it started correctly)
|
||||||
|
|
||||||
As with all milters, dkimpy-milter needs to be integrated with your MTA of
|
As with all milters, dkimpy-milter needs to be integrated with your MTA of
|
||||||
choice (Sendmail or Postfix). When integrating with your MTA, the risk of
|
choice (Sendmail or Postfix). When integrating with your MTA, the risk of
|
||||||
@@ -214,7 +215,7 @@ Configuration is very similar to opendkim, but needs some adjustment for
|
|||||||
dkimpy-milter. Here's an example configuration line to include in your
|
dkimpy-milter. Here's an example configuration line to include in your
|
||||||
sendmail.mc:
|
sendmail.mc:
|
||||||
|
|
||||||
INPUT_MAIL_FILTER(`dkimpy-milter', `S=local:/run/dkimpy-milter/dkimpy-milter.sock')dnl
|
INPUT_MAIL_FILTER(`dkimpy-milter', `S=local:/run/dkimpy-milter/dkimpy-milter.sock')dnl
|
||||||
|
|
||||||
Changing the sendmail.mc file requires a Make (to compile it into sendmail.cf)
|
Changing the sendmail.mc file requires a Make (to compile it into sendmail.cf)
|
||||||
and a restart of sendmail. Note that S= needs to match the value of Socket in
|
and a restart of sendmail. Note that S= needs to match the value of Socket in
|
||||||
@@ -237,7 +238,7 @@ and deserve consideration.
|
|||||||
|
|
||||||
By default, sendmail quotes to address header fields when there are no
|
By default, sendmail quotes to address header fields when there are no
|
||||||
quotes and the display part of the address contains a period or an
|
quotes and the display part of the address contains a period or an
|
||||||
apostrophe. However, opendkim only sees the raw, unmodified form of
|
apostrophe. However, dkimpy-milter only sees the raw, unmodified form of
|
||||||
the header field, and so the content that gets verified and what gets
|
the header field, and so the content that gets verified and what gets
|
||||||
signed will not be the same, guaranteeing the attached signature is not
|
signed will not be the same, guaranteeing the attached signature is not
|
||||||
valid.
|
valid.
|
||||||
@@ -263,16 +264,16 @@ and deserve consideration.
|
|||||||
To: very long name <a@example.org>,
|
To: very long name <a@example.org>,
|
||||||
anotherloo...ong name b <b@example.org>
|
anotherloo...ong name b <b@example.org>
|
||||||
|
|
||||||
This rewrite is also done after opendkim has seen the message, meaning
|
This rewrite is also done after dkimpy-milter has seen the message,
|
||||||
the signature opendkim attaches to the message does not match the
|
meaning the signature dkimpy-milter attaches to the message does not match
|
||||||
content it signed. There is not a known configuration change to
|
the content it signed. There is not a known configuration change to
|
||||||
mitigate this mutation.
|
mitigate this mutation.
|
||||||
|
|
||||||
The only known mechanism for dealing with this is to have distinct
|
The only known mechanism for dealing with this is to have distinct
|
||||||
instances of opendkim do the verifying (inbound) and signing (outbound)
|
instances of dkimpy-milter do the verifying (inbound) and signing
|
||||||
so that the version that arrives at the signing instance is already
|
(outbound) so that the version that arrives at the signing instance is
|
||||||
in the rewritten form, guaranteeing the input and output are the same
|
already in the rewritten form, guaranteeing the input and output are the
|
||||||
and thus the signature matches the payload.
|
same and thus the signature matches the payload.
|
||||||
|
|
||||||
### POSTFIX
|
### POSTFIX
|
||||||
|
|
||||||
@@ -281,12 +282,12 @@ README_FILES/MILTER_README). Here's an example master.cf excerpt that talks
|
|||||||
to two dkimpy-milter instances, one configured for signing and one configured
|
to two dkimpy-milter instances, one configured for signing and one configured
|
||||||
for verification:
|
for verification:
|
||||||
|
|
||||||
smtp inet n - - - - smtpd
|
smtp inet n - - - - smtpd
|
||||||
...
|
...
|
||||||
-o smtpd_milters=inet:localhost:8892
|
-o smtpd_milters=inet:localhost:8892
|
||||||
...
|
...
|
||||||
|
|
||||||
submission inet n - - - - smtpd
|
submission inet n - - - - smtpd
|
||||||
...
|
...
|
||||||
-o smtpd_milters=inet:localhost:8891
|
-o smtpd_milters=inet:localhost:8891
|
||||||
...
|
...
|
||||||
@@ -300,13 +301,13 @@ macros to keep the mail streams segregated:
|
|||||||
|
|
||||||
Postfix master.cf:
|
Postfix master.cf:
|
||||||
|
|
||||||
smtp inet n - - - - smtpd
|
smtp inet n - - - - smtpd
|
||||||
...
|
...
|
||||||
-o smtpd_milters=inet:localhost:8891
|
-o smtpd_milters=inet:localhost:8891
|
||||||
-o milter_macro_daemon_name=VERIFYING
|
-o milter_macro_daemon_name=VERIFYING
|
||||||
...
|
...
|
||||||
|
|
||||||
submission inet n - - - - smtpd
|
submission inet n - - - - smtpd
|
||||||
-o syslog_name=postfix/submission
|
-o syslog_name=postfix/submission
|
||||||
-o smtpd_tls_security_level=encrypt
|
-o smtpd_tls_security_level=encrypt
|
||||||
-o smtpd_sasl_auth_enable=yes
|
-o smtpd_sasl_auth_enable=yes
|
||||||
@@ -317,11 +318,11 @@ submission inet n - - - - smtpd
|
|||||||
|
|
||||||
Dkimpy-milter.conf:
|
Dkimpy-milter.conf:
|
||||||
|
|
||||||
...
|
...
|
||||||
Mode sv
|
Mode sv
|
||||||
MacroList dameon_name|ORIGINATING
|
MacroList daemon_name|ORIGINATING
|
||||||
MacroListVerify daemon_name|VERIFYING
|
MacroListVerify daemon_name|VERIFYING
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
# NOTES
|
# NOTES
|
||||||
@@ -335,3 +336,8 @@ later support Ed25519 signing and verification. RFC 8301 removed rsa-sha1
|
|||||||
from DKIM. dkimpy-milter does not sign with rsa-sha1, but still considers
|
from DKIM. dkimpy-milter does not sign with rsa-sha1, but still considers
|
||||||
rsa-sha1 signatures as valid for verification because they are still in
|
rsa-sha1 signatures as valid for verification because they are still in
|
||||||
common use and are not known to be cryptographically broken.
|
common use and are not known to be cryptographically broken.
|
||||||
|
|
||||||
|
Support for non-ASCII email messages: Anything UTF-8 should work (including
|
||||||
|
correct signing/verification). For messages that contain header fields with
|
||||||
|
non-ASCII or UTF-8 content, signatures are likely fail verification, but the
|
||||||
|
milter should continue to run. RFC 8616 is not supported.
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ from dkimpy_milter.util import write_pid
|
|||||||
from dkimpy_milter.util import get_keys
|
from dkimpy_milter.util import get_keys
|
||||||
from dkimpy_milter.util import fold
|
from dkimpy_milter.util import fold
|
||||||
|
|
||||||
__version__ = "1.2.0"
|
__version__ = "1.2.3"
|
||||||
FWS = re.compile(r'\r?\n[ \t]+')
|
FWS = re.compile(r'\r?\n[ \t]+')
|
||||||
|
|
||||||
|
|
||||||
@@ -101,15 +101,28 @@ class dkimMilter(Milter.Base):
|
|||||||
if self.conf.get('Syslog') and self.conf.get('debugLevel') >= 1:
|
if self.conf.get('Syslog') and self.conf.get('debugLevel') >= 1:
|
||||||
syslog.syslog("connect from {0} at {1} {2}"
|
syslog.syslog("connect from {0} at {1} {2}"
|
||||||
.format(hostname, hostaddr, connecttype))
|
.format(hostname, hostaddr, connecttype))
|
||||||
|
if self.conf.get('Syslog') and self.conf.get('debugLevel') >= 3:
|
||||||
|
syslog.syslog("internal_conn: {0}, external_conn: {1}"
|
||||||
|
.format(self.internal_connection, self.external_connection))
|
||||||
|
|
||||||
return Milter.CONTINUE
|
return Milter.CONTINUE
|
||||||
|
|
||||||
# multiple messages can be received on a single connection
|
# multiple messages can be received on a single connection
|
||||||
# envfrom (MAIL FROM in the SMTP protocol) seems to mark the start
|
# envfrom (MAIL FROM in the SMTP protocol) seems to mark the start
|
||||||
# of each message.
|
# of each message.
|
||||||
@Milter.noreply
|
@Milter.noreply
|
||||||
def envfrom(self, f, *str):
|
def envfrom(self, f, *moredata):
|
||||||
|
try:
|
||||||
|
f = str(codecs.encode(f, 'UTF-8', 'replace'), 'UTF-8', 'ignore')
|
||||||
|
except TypeError:
|
||||||
|
f = codecs.encode(f, 'UTF-8', 'replace').decode()
|
||||||
|
try:
|
||||||
|
moredata = str(codecs.encode(str(moredata), 'UTF-8', 'replace'), 'UTF-8', 'ignore')
|
||||||
|
except TypeError:
|
||||||
|
moredata = codecs.encode(str(moredata), 'UTF-8', 'replace').decode()
|
||||||
|
|
||||||
if self.conf.get('Syslog') and self.conf.get('debugLevel') >= 2:
|
if self.conf.get('Syslog') and self.conf.get('debugLevel') >= 2:
|
||||||
syslog.syslog("mail from: {0} {1}".format(f, str))
|
syslog.syslog("mail from: {0} {1}".format(f, moredata))
|
||||||
self.fp = io.BytesIO()
|
self.fp = io.BytesIO()
|
||||||
self.mailfrom = f
|
self.mailfrom = f
|
||||||
t = parse_addr(f)
|
t = parse_addr(f)
|
||||||
@@ -141,6 +154,11 @@ class dkimMilter(Milter.Base):
|
|||||||
self.fdomain = self.author.split('@')[1].lower()
|
self.fdomain = self.author.split('@')[1].lower()
|
||||||
except IndexError as er:
|
except IndexError as er:
|
||||||
pass # self.author was not a proper email address
|
pass # self.author was not a proper email address
|
||||||
|
# This keeps non-ascii characters out of the From domain
|
||||||
|
try:
|
||||||
|
self.fdomain = str(codecs.encode(self.fdomain, 'ascii', 'replace'), 'ascii', 'ignore')
|
||||||
|
except TypeError:
|
||||||
|
self.fdomain = codecs.encode(self.fdomain, 'ascii', 'replace').decode('ascii','ignore')
|
||||||
if (self.conf.get('Syslog') and
|
if (self.conf.get('Syslog') and
|
||||||
self.conf.get('debugLevel') >= 1):
|
self.conf.get('debugLevel') >= 1):
|
||||||
syslog.syslog("{0}: {1}".format(name, val))
|
syslog.syslog("{0}: {1}".format(name, val))
|
||||||
@@ -148,6 +166,10 @@ class dkimMilter(Milter.Base):
|
|||||||
self.arheaders.append(val)
|
self.arheaders.append(val)
|
||||||
if self.fp:
|
if self.fp:
|
||||||
try:
|
try:
|
||||||
|
if lname == 'from':
|
||||||
|
# Non-ascii in email address localpart is legal, so this is a special case
|
||||||
|
self.fp.write(b"%s: %s\n" % (codecs.encode(name, 'ascii'), codecs.encode(val, 'UTF-8', 'replace')))
|
||||||
|
else:
|
||||||
self.fp.write(b"%s: %s\n" % (codecs.encode(name, 'ascii'), codecs.encode(val, 'ascii')))
|
self.fp.write(b"%s: %s\n" % (codecs.encode(name, 'ascii'), codecs.encode(val, 'ascii')))
|
||||||
except:
|
except:
|
||||||
# Don't choke on header fields with non-ascii garbage in them.
|
# Don't choke on header fields with non-ascii garbage in them.
|
||||||
@@ -195,6 +217,8 @@ class dkimMilter(Milter.Base):
|
|||||||
syslog.syslog('self.domain: {0}, self.fdomain: {1}, self.iequals: {2}'.format(self.domain, self.fdomain, self.iequals))
|
syslog.syslog('self.domain: {0}, self.fdomain: {1}, self.iequals: {2}'.format(self.domain, self.fdomain, self.iequals))
|
||||||
if ((self.fdomain in self.domain) and not self.conf.get('Mode') == 'v'
|
if ((self.fdomain in self.domain) and not self.conf.get('Mode') == 'v'
|
||||||
and not self.external_connection):
|
and not self.external_connection):
|
||||||
|
if (self.conf.get('Syslog') and self.conf.get('debugLevel') >= 3):
|
||||||
|
syslog.syslog("Signing DKIM")
|
||||||
self.sign_dkim(txt)
|
self.sign_dkim(txt)
|
||||||
if ((self.has_dkim) and (not self.internal_connection) and
|
if ((self.has_dkim) and (not self.internal_connection) and
|
||||||
(self.conf.get('Mode') == 'v' or
|
(self.conf.get('Mode') == 'v' or
|
||||||
@@ -227,7 +251,7 @@ class dkimMilter(Milter.Base):
|
|||||||
def get_identities_sign(self):
|
def get_identities_sign(self):
|
||||||
"""Determine d= and i= identiies for signature"""
|
"""Determine d= and i= identiies for signature"""
|
||||||
self.domain = []
|
self.domain = []
|
||||||
iequals = None
|
self.iequals = None
|
||||||
try:
|
try:
|
||||||
self.privkeyRSA = self.conf.get('privateRSA')
|
self.privkeyRSA = self.conf.get('privateRSA')
|
||||||
except:
|
except:
|
||||||
@@ -280,6 +304,7 @@ class dkimMilter(Milter.Base):
|
|||||||
keytabledata = self.conf.get('privateRSATable')[keytablekey]
|
keytabledata = self.conf.get('privateRSATable')[keytablekey]
|
||||||
try:
|
try:
|
||||||
self.fdomain = keytabledata[0]
|
self.fdomain = keytabledata[0]
|
||||||
|
self.domain.append(self.fdomain)
|
||||||
self.selectorRSA = keytabledata[1]
|
self.selectorRSA = keytabledata[1]
|
||||||
self.privkeyRSA = keytabledata[2]
|
self.privkeyRSA = keytabledata[2]
|
||||||
except:
|
except:
|
||||||
@@ -290,11 +315,14 @@ class dkimMilter(Milter.Base):
|
|||||||
keytabledata = self.conf.get('privateEd25519Table')[keytablekey]
|
keytabledata = self.conf.get('privateEd25519Table')[keytablekey]
|
||||||
try:
|
try:
|
||||||
self.fdomain = keytabledata[0]
|
self.fdomain = keytabledata[0]
|
||||||
|
self.domain.append(self.fdomain)
|
||||||
self.selectorEd25519 = keytabledata[1]
|
self.selectorEd25519 = keytabledata[1]
|
||||||
self.privkeyEd25519 = keytabledata[2]
|
self.privkeyEd25519 = keytabledata[2]
|
||||||
except:
|
except:
|
||||||
if (self.conf.get('Syslog')):
|
if (self.conf.get('Syslog')):
|
||||||
syslog.syslog('Error: Invalid KeyTable data {0}'.format(keytabledata))
|
syslog.syslog('Error: Invalid KeyTable data {0}'.format(keytabledata))
|
||||||
|
if (self.fdomain == '%'):
|
||||||
|
self.fdomain = self.author.split('@')[1].lower()
|
||||||
break
|
break
|
||||||
|
|
||||||
def sign_dkim(self, txt):
|
def sign_dkim(self, txt):
|
||||||
@@ -363,8 +391,12 @@ class dkimMilter(Milter.Base):
|
|||||||
try:
|
try:
|
||||||
dnsoverride = self.conf.get('DNSOverride')
|
dnsoverride = self.conf.get('DNSOverride')
|
||||||
if isinstance(dnsoverride, str):
|
if isinstance(dnsoverride, str):
|
||||||
|
timeout = 5
|
||||||
|
domain = self.fdomain
|
||||||
|
def dnsfunc(domain, timeout=timeout, dnsoverride=dnsoverride):
|
||||||
|
return dnsoverride
|
||||||
syslog.syslog("DNSOverride: {0}".format(dnsoverride))
|
syslog.syslog("DNSOverride: {0}".format(dnsoverride))
|
||||||
res = d.verify(idx=y, dnsfunc=lambda _x: dnsoverride)
|
res = d.verify(idx=y, dnsfunc=dnsfunc)
|
||||||
else:
|
else:
|
||||||
res = d.verify(idx=y)
|
res = d.verify(idx=y)
|
||||||
algo = codecs.decode(d.signature_fields.get(b'a'), 'ascii')
|
algo = codecs.decode(d.signature_fields.get(b'a'), 'ascii')
|
||||||
|
|||||||
+31
-3
@@ -88,17 +88,24 @@ class HostsDataset(object):
|
|||||||
if self.item[0] == '!':
|
if self.item[0] == '!':
|
||||||
self.item = item[1:]
|
self.item = item[1:]
|
||||||
self.negative = True
|
self.negative = True
|
||||||
|
try:
|
||||||
try:
|
try:
|
||||||
self.item = ipaddress.ip_address(str(self.item, "utf-8"))
|
self.item = ipaddress.ip_address(str(self.item, "utf-8"))
|
||||||
|
except TypeError:
|
||||||
|
self.item = ipaddress.ip_address(self.item)
|
||||||
if isinstance(self.item, ipaddress.IPv4Address):
|
if isinstance(self.item, ipaddress.IPv4Address):
|
||||||
self.isipv4 = True
|
self.isipv4 = True
|
||||||
elif isinstance(self.item, ipaddress.IPv6Address):
|
elif isinstance(self.item, ipaddress.IPv6Address):
|
||||||
self.isipv6 = True
|
self.isipv6 = True
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
|
try:
|
||||||
try:
|
try:
|
||||||
self.item = ipaddress.ip_network(str
|
self.item = ipaddress.ip_network(str
|
||||||
(self.item, "utf-8"),
|
(self.item, "utf-8"),
|
||||||
strict=False)
|
strict=False)
|
||||||
|
except TypeError:
|
||||||
|
self.item = ipaddress.ip_network(self.item,
|
||||||
|
strict=False)
|
||||||
if isinstance(self.item, ipaddress.IPv4Network):
|
if isinstance(self.item, ipaddress.IPv4Network):
|
||||||
self.isipv4cidr = True
|
self.isipv4cidr = True
|
||||||
elif isinstance(self.item, ipaddress.IPv6Network):
|
elif isinstance(self.item, ipaddress.IPv6Network):
|
||||||
@@ -114,7 +121,10 @@ class HostsDataset(object):
|
|||||||
|
|
||||||
def match(self, connectip):
|
def match(self, connectip):
|
||||||
'''Check if the connect IP is part of the dataset'''
|
'''Check if the connect IP is part of the dataset'''
|
||||||
|
try:
|
||||||
source = ipaddress.ip_address(str(connectip, "utf-8"))
|
source = ipaddress.ip_address(str(connectip, "utf-8"))
|
||||||
|
except TypeError:
|
||||||
|
source = ipaddress.ip_address(connectip)
|
||||||
for item in self.dataset:
|
for item in self.dataset:
|
||||||
if item.isdomain or item.ishostname:
|
if item.isdomain or item.ishostname:
|
||||||
result = self.matchname(source) # Match host/domains first
|
result = self.matchname(source) # Match host/domains first
|
||||||
@@ -164,13 +174,19 @@ class HostsDataset(object):
|
|||||||
if isinstance(source, ipaddress.IPv4Address):
|
if isinstance(source, ipaddress.IPv4Address):
|
||||||
ips = s.dns(name, 'A')
|
ips = s.dns(name, 'A')
|
||||||
for ip in ips:
|
for ip in ips:
|
||||||
|
try:
|
||||||
ip = ipaddress.IPv4Address(str(ip, 'UTF-8'))
|
ip = ipaddress.IPv4Address(str(ip, 'UTF-8'))
|
||||||
|
except TypeError:
|
||||||
|
ip = ipaddress.IPv4Address(ip)
|
||||||
if ip == source:
|
if ip == source:
|
||||||
results.append(name)
|
results.append(name)
|
||||||
if isinstance(source, ipaddress.IPv6Address):
|
if isinstance(source, ipaddress.IPv6Address):
|
||||||
ips = s.dns(name, 'AAAA')
|
ips = s.dns(name, 'AAAA')
|
||||||
for ip in ips:
|
for ip in ips:
|
||||||
|
try:
|
||||||
ip = ipaddress.IPv6Address(str(ip, 'UTF-8'))
|
ip = ipaddress.IPv6Address(str(ip, 'UTF-8'))
|
||||||
|
except TypeError:
|
||||||
|
ip = ipaddress.IPv6Address(ip)
|
||||||
if ip == source:
|
if ip == source:
|
||||||
results.append(name)
|
results.append(name)
|
||||||
return results
|
return results
|
||||||
@@ -306,9 +322,15 @@ def _dataset_to_list(dataset):
|
|||||||
return dsd
|
return dsd
|
||||||
# If it's a str and csl, it has one value and we return a list
|
# If it's a str and csl, it has one value and we return a list
|
||||||
if dataset[:4] == 'csl:':
|
if dataset[:4] == 'csl:':
|
||||||
return [dataset[4:].strip().strip(',')]
|
datalist = dataset[4:].split(',')
|
||||||
|
for item in datalist:
|
||||||
|
datalist[datalist.index(item)] = item.strip().strip(',')
|
||||||
|
return datalist
|
||||||
else:
|
else:
|
||||||
return [dataset.strip().strip(',')]
|
datalist = dataset.split(',')
|
||||||
|
for item in datalist:
|
||||||
|
datalist[datalist.index(item)] = item.strip().strip(',')
|
||||||
|
return datalist
|
||||||
if dataset[-3:] == '.db' or dataset[:3] == 'db:':
|
if dataset[-3:] == '.db' or dataset[:3] == 'db:':
|
||||||
# This is a Sleepycat (Oracle) DB dataset, which we dont support
|
# This is a Sleepycat (Oracle) DB dataset, which we dont support
|
||||||
raise dkim.ParameterError('Unsupported dataset db datase: {0}'
|
raise dkim.ParameterError('Unsupported dataset db datase: {0}'
|
||||||
@@ -439,8 +461,14 @@ def _readConfigFile(path, configData=None, configGlobal={}):
|
|||||||
fp.close()
|
fp.close()
|
||||||
try:
|
try:
|
||||||
configData['AuthservID'] = _make_authserv_id(configData.get('AuthservID', 'HOSTNAME'))
|
configData['AuthservID'] = _make_authserv_id(configData.get('AuthservID', 'HOSTNAME'))
|
||||||
|
except Exception as e:
|
||||||
|
syslog.syslog("Could not make AuthservID: {}".format(e))
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
configData['IntHosts'] = HostsDataset(configData['InternalHosts'])
|
configData['IntHosts'] = HostsDataset(configData['InternalHosts'])
|
||||||
except:
|
except Exception as e:
|
||||||
|
syslog.syslog("Could not make HostDataset from InternalHosts: {}".format(e))
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return(configData)
|
return(configData)
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ def DNSLookup_dnspython(name,qtype,tcpfallback=True,timeout=5):
|
|||||||
elif qtype == 'PTR':
|
elif qtype == 'PTR':
|
||||||
retVal.append(((name, qtype), rdata.target.to_text(True)))
|
retVal.append(((name, qtype), rdata.target.to_text(True)))
|
||||||
elif qtype == 'TXT' or qtype == 'SPF':
|
elif qtype == 'TXT' or qtype == 'SPF':
|
||||||
retVal.append(((name, qtype), rdata.strings))
|
retVal.append(((name, qtype), list(rdata.strings)))
|
||||||
except dns.resolver.NoAnswer:
|
except dns.resolver.NoAnswer:
|
||||||
pass
|
pass
|
||||||
except dns.resolver.NXDOMAIN:
|
except dns.resolver.NXDOMAIN:
|
||||||
|
|||||||
@@ -244,6 +244,10 @@ Naturally, providing a value here overrides the default, so if mail from
|
|||||||
127.0.0.1 should be signed, the list provided here should include that
|
127.0.0.1 should be signed, the list provided here should include that
|
||||||
address explicitly. [PeerList NOT IMPLEMENTED]
|
address explicitly. [PeerList NOT IMPLEMENTED]
|
||||||
|
|
||||||
|
Mail sent via connections from InternalHosts will not have any existing DKIM
|
||||||
|
signatures verified. This is not overridden by MacroList or Mode. If the
|
||||||
|
Mode is 'v', then no actions will be performed.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.I KeyFile (string)
|
.I KeyFile (string)
|
||||||
Gives the location of a PEM-formatted private key to be used for RSA signing
|
Gives the location of a PEM-formatted private key to be used for RSA signing
|
||||||
@@ -298,6 +302,10 @@ at the time the filter receives a connection from the MTA and its availability
|
|||||||
depends upon the version of milter used to compile the filter and the version
|
depends upon the version of milter used to compile the filter and the version
|
||||||
of the MTA making the connection.
|
of the MTA making the connection.
|
||||||
|
|
||||||
|
Mail sent via connections where macros that are in MacroList are provided
|
||||||
|
will not have any existing DKIM signatures verified. If the Mode is 'v', then
|
||||||
|
no actions will be performed.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.I MacroListVerify (dataset)
|
.I MacroListVerify (dataset)
|
||||||
Defines a set of MTA-provided
|
Defines a set of MTA-provided
|
||||||
@@ -308,6 +316,10 @@ Entries in this data set follow the same form as those of the
|
|||||||
.I MacroList
|
.I MacroList
|
||||||
option above. [this option is not inhereted from OpenDKIM]
|
option above. [this option is not inhereted from OpenDKIM]
|
||||||
|
|
||||||
|
Mail sent via connections where macros that are in MacroListVerify are
|
||||||
|
provided will be not DKIM signed. If the Mode is 's', then no actions will
|
||||||
|
be performed.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.I Mode (string)
|
.I Mode (string)
|
||||||
Selects operating modes. The string is a concatenation of characters that
|
Selects operating modes. The string is a concatenation of characters that
|
||||||
@@ -327,7 +339,12 @@ be set:
|
|||||||
(a) Domain, KeyFile, Selector, no KeyTable, no SigningTable;
|
(a) Domain, KeyFile, Selector, no KeyTable, no SigningTable;
|
||||||
(b) KeyTable, SigningTable, no Domain, no KeyFile, no Selector;
|
(b) KeyTable, SigningTable, no Domain, no KeyFile, no Selector;
|
||||||
|
|
||||||
TP
|
The action to sign or verify is also affected by the InternalHosts, MacroList,
|
||||||
|
and MacroListVerify options. Those options may preclude signing or
|
||||||
|
verification in some cases, but will not enable signing or verifying if not
|
||||||
|
allowed by Mode.
|
||||||
|
|
||||||
|
.TP
|
||||||
.I MinimumKeyBits (integer)
|
.I MinimumKeyBits (integer)
|
||||||
Establishes a minimum key size for acceptable RSA signatures. Signatures with
|
Establishes a minimum key size for acceptable RSA signatures. Signatures with
|
||||||
smaller key sizes, even if they otherwise pass DKIM validation, will me marked
|
smaller key sizes, even if they otherwise pass DKIM validation, will me marked
|
||||||
|
|||||||
@@ -244,6 +244,10 @@ Naturally, providing a value here overrides the default, so if mail from
|
|||||||
127.0.0.1 should be signed, the list provided here should include that
|
127.0.0.1 should be signed, the list provided here should include that
|
||||||
address explicitly. [PeerList NOT IMPLEMENTED]
|
address explicitly. [PeerList NOT IMPLEMENTED]
|
||||||
|
|
||||||
|
Mail sent via connections from InternalHosts will not have any existing DKIM
|
||||||
|
signatures verified. This is not overridden by MacroList or Mode. If the
|
||||||
|
Mode is 'v', then no actions will be performed.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.I KeyFile (string)
|
.I KeyFile (string)
|
||||||
Gives the location of a PEM-formatted private key to be used for RSA signing
|
Gives the location of a PEM-formatted private key to be used for RSA signing
|
||||||
@@ -298,6 +302,10 @@ at the time the filter receives a connection from the MTA and its availability
|
|||||||
depends upon the version of milter used to compile the filter and the version
|
depends upon the version of milter used to compile the filter and the version
|
||||||
of the MTA making the connection.
|
of the MTA making the connection.
|
||||||
|
|
||||||
|
Mail sent via connections where macros that are in MacroList are provided
|
||||||
|
will not have any existing DKIM signatures verified. If the Mode is 'v', then
|
||||||
|
no actions will be performed.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.I MacroListVerify (dataset)
|
.I MacroListVerify (dataset)
|
||||||
Defines a set of MTA-provided
|
Defines a set of MTA-provided
|
||||||
@@ -308,6 +316,10 @@ Entries in this data set follow the same form as those of the
|
|||||||
.I MacroList
|
.I MacroList
|
||||||
option above. [this option is not inhereted from OpenDKIM]
|
option above. [this option is not inhereted from OpenDKIM]
|
||||||
|
|
||||||
|
Mail sent via connections where macros that are in MacroListVerify are
|
||||||
|
provided will be not DKIM signed. If the Mode is 's', then no actions will
|
||||||
|
be performed.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.I Mode (string)
|
.I Mode (string)
|
||||||
Selects operating modes. The string is a concatenation of characters that
|
Selects operating modes. The string is a concatenation of characters that
|
||||||
@@ -327,7 +339,12 @@ be set:
|
|||||||
(a) Domain, KeyFile, Selector, no KeyTable, no SigningTable;
|
(a) Domain, KeyFile, Selector, no KeyTable, no SigningTable;
|
||||||
(b) KeyTable, SigningTable, no Domain, no KeyFile, no Selector;
|
(b) KeyTable, SigningTable, no Domain, no KeyFile, no Selector;
|
||||||
|
|
||||||
TP
|
The action to sign or verify is also affected by the InternalHosts, MacroList,
|
||||||
|
and MacroListVerify options. Those options may preclude signing or
|
||||||
|
verification in some cases, but will not enable signing or verifying if not
|
||||||
|
allowed by Mode.
|
||||||
|
|
||||||
|
.TP
|
||||||
.I MinimumKeyBits (integer)
|
.I MinimumKeyBits (integer)
|
||||||
Establishes a minimum key size for acceptable RSA signatures. Signatures with
|
Establishes a minimum key size for acceptable RSA signatures. Signatures with
|
||||||
smaller key sizes, even if they otherwise pass DKIM validation, will me marked
|
smaller key sizes, even if they otherwise pass DKIM validation, will me marked
|
||||||
@@ -428,7 +445,7 @@ of this field.
|
|||||||
.TP
|
.TP
|
||||||
.I SigningTable (dataset)
|
.I SigningTable (dataset)
|
||||||
|
|
||||||
Defines a table used to select one or more signing identities to apply to a message based on the address found in the From: header field. Keys in this table vary depending on the type of table used; values in this data set should include one field that contains a name found in the KeyTable (see above) that identifies which key should be used in generating the signature, and an optional second field naming the signer of the message that will be included in the "i=" tag in the generated signature. Note that the "i=" value will not be included in the signature if it conflicts with the signing domain (the "d=" value).
|
Defines a table used to select a signing identity to apply to a message based on the address found in the From: header field. Keys in this table vary depending on the type of table used; values in this data set should include one field that contains a name found in the KeyTable (see above) that identifies which key should be used in generating the signature, and an optional second field naming the signer of the message that will be included in the "i=" tag in the generated signature. Note that the "i=" value will not be included in the signature if it conflicts with the signing domain (the "d=" value).
|
||||||
|
|
||||||
If the first field contains only a "%" character, it will be replaced by the domain found in the From: header field. Similarly, within the optional second field, any "%" character will be replaced by the domain found in the From: header field.
|
If the first field contains only a "%" character, it will be replaced by the domain found in the From: header field. Similarly, within the optional second field, any "%" character will be replaced by the domain found in the From: header field.
|
||||||
|
|
||||||
|
|||||||
@@ -53,9 +53,10 @@ class FileMacroExpand(distutils.cmd.Command):
|
|||||||
'system/dkimpy-milter.service', 'system/dkimpy-milter', \
|
'system/dkimpy-milter.service', 'system/dkimpy-milter', \
|
||||||
'system/dkimpy-milter.openrc', \
|
'system/dkimpy-milter.openrc', \
|
||||||
'system/socket-activation/dkimpy-milter.service', \
|
'system/socket-activation/dkimpy-milter.service', \
|
||||||
'system/socket-activation/dkimpy-milter.socket',]
|
'system/socket-activation/dkimpy-milter.socket', ]
|
||||||
for infile in files:
|
for infile in files:
|
||||||
outfile = ''
|
outfile = ''
|
||||||
|
try:
|
||||||
filein = open(infile + '.in')
|
filein = open(infile + '.in')
|
||||||
for line in filein:
|
for line in filein:
|
||||||
for function in ["@SYSCONFDIR@", "@CONFDIR@", "@SBINDIR@", "@BINDIR@", "@RUNSTATEDIR@"]:
|
for function in ["@SYSCONFDIR@", "@CONFDIR@", "@SBINDIR@", "@BINDIR@", "@RUNSTATEDIR@"]:
|
||||||
@@ -76,17 +77,19 @@ class FileMacroExpand(distutils.cmd.Command):
|
|||||||
for line in outfile:
|
for line in outfile:
|
||||||
out.write(line)
|
out.write(line)
|
||||||
out.close()
|
out.close()
|
||||||
|
except FileNotFoundError as x:
|
||||||
|
pass
|
||||||
|
|
||||||
kw = {} # Work-around for lack of 'or' requires in setuptools.
|
kw = {} # Work-around for lack of 'or' requires in setuptools.
|
||||||
try:
|
try:
|
||||||
import dns
|
import dns
|
||||||
kw['install_requires'] = ['dkimpy>=1.0', 'pymilter', 'authres>=1.1.0', 'PyNaCl', 'dnspython>=1.16.0']
|
kw['install_requires'] = ['dkimpy>=1.1.0', 'pymilter>=1.0.5', 'authres>=1.1.0', 'PyNaCl', 'dnspython>=1.16.0']
|
||||||
except ImportError: # If PyDNS is not installed, prefer dnspython
|
except ImportError: # If PyDNS is not installed, prefer dnspython
|
||||||
kw['install_requires'] = ['dkimpy>=1.0', 'pymilter', 'authres>=1.1.0', 'PyNaCl', 'Py3DNS']
|
kw['install_requires'] = ['dkimpy>=1.1.0', 'pymilter>=1.0.5', 'authres>=1.1.0', 'PyNaCl', 'Py3DNS']
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='dkimpy-milter',
|
name='dkimpy-milter',
|
||||||
version='1.2.0',
|
version='1.2.3',
|
||||||
author='Scott Kitterman',
|
author='Scott Kitterman',
|
||||||
author_email='scott@kitterman.com',
|
author_email='scott@kitterman.com',
|
||||||
url='https://launchpad.net/dkimpy-milter',
|
url='https://launchpad.net/dkimpy-milter',
|
||||||
@@ -115,8 +118,8 @@ setup(
|
|||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
data_files=[(os.path.join('share', 'man', 'man5'),
|
data_files=[(os.path.join('share', 'man', 'man5'),
|
||||||
['man/dkimpy-milter.conf.5']), (os.path.join('share', 'man', 'man8'),
|
['man/dkimpy-milter.conf.5']), (os.path.join('share', 'man', 'man8'),
|
||||||
['man/dkimpy-milter.8']), ('etc', ['etc/dkimpy-milter.conf']),
|
['man/dkimpy-milter.8']), (os.path.join('etc', 'dkimpy-milter'),
|
||||||
(os.path.join('lib', 'systemd', 'system'),
|
['etc/dkimpy-milter.conf']), (os.path.join('lib', 'systemd', 'system'),
|
||||||
['system/dkimpy-milter.service']),(os.path.join('etc', 'init.d'),
|
['system/dkimpy-milter.service']),(os.path.join('etc', 'init.d'),
|
||||||
['system/dkimpy-milter']), (os.path.join('etc', 'init.d'),
|
['system/dkimpy-milter']), (os.path.join('etc', 'init.d'),
|
||||||
['system/dkimpy-milter.openrc'])],
|
['system/dkimpy-milter.openrc'])],
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ function connect_and_send (sockname, headers, body)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- mt.macro(conn, SMFIC_MAIL, "i", "simple-message")
|
-- mt.macro(conn, SMFIC_MAIL, "i", "simple-message")
|
||||||
if mt.mailfrom(conn, "<alice@example.net>") ~= nil then
|
if mt.mailfrom(conn, "<alicüe@example.net> (Alicþþÿÿe)") ~= nil then
|
||||||
error "mt.mailfrom() failed"
|
error "mt.mailfrom() failed"
|
||||||
end
|
end
|
||||||
if mt.getreply(conn) ~= SMFIR_CONTINUE then
|
if mt.getreply(conn) ~= SMFIR_CONTINUE then
|
||||||
|
|||||||
Reference in New Issue
Block a user