Compare commits

..

7 Commits

Author SHA1 Message Date
Stuart D. Gathman 5675adeb3c Work with berkeleydb and try importing it first. 2024-05-29 18:08:30 -04:00
Stuart D. Gathman 35416dfc46 Support MTAs with colon separator 2024-05-29 11:15:26 -04:00
Stuart D. Gathman c33de064ee Merge branch 'master' of github.com:sdgathman/pymilter 2024-05-29 11:14:19 -04:00
Jaime Marquínez Ferrándiz 1c05080768 Remove calls to the deprecated method "assertEquals" (#57)
It has been removed in python 3.12
2024-03-11 22:27:18 -04:00
Stuart D. Gathman dce7c0080a Adapt to MTAs that use ':' as key terminator and/or add null char to
key/value.
2022-07-15 19:00:41 -04:00
Stuart D. Gathman 7deec90a59 Drop paragraph about python 2.0 compatibility 2021-12-31 00:56:54 -05:00
Stuart D. Gathman c73b533acb Update README.md to satisfy PiPy 2021-12-31 00:49:54 -05:00
7 changed files with 68 additions and 44 deletions
+24 -5
View File
@@ -1,4 +1,7 @@
try:
try:
from berkeleydb import db
except:
from bsddb3 import db
class DB(object):
def open(self,fname,mode):
@@ -6,6 +9,8 @@ try:
else: raise RuntimeException('unsupported mode')
self.f = db.DB()
self.f.open(fname,flags=flags)
def __contains__(self,key):
return not not self.f.get(key)
def __getitem__(self,key):
v = self.f.get(key)
if not v: raise KeyError(key)
@@ -27,6 +32,10 @@ class MTAPolicy(object):
if not access_file:
access_file = conf.access_file
self.use_nulls = conf.access_file_nulls
try:
self.use_colon = conf.access_file_colon
except:
self.use_colon = True
self.sender = sender
self.domain = sender.split('@')[-1].lower()
self.acf = None
@@ -52,14 +61,24 @@ class MTAPolicy(object):
if not acf: return None
if self.use_nulls: sfx = b'\x00'
else: sfx = b''
pfx = pfx.encode() + b'!'
try:
if self.use_colon:
sep = b':'
else:
sep = b'!'
pfx = pfx.encode() + sep
try: # try with localpart@domain
return acf[pfx + self.sender.encode() + sfx].rstrip(b'\x00').decode()
except KeyError:
try:
return acf[pfx + self.domain.encode() + sfx].rstrip(b'\x00').decode()
try: # try with domain
d = self.domain.encode()
k = pfx + d + sfx
while not k in acf and b'.' in d:
# check partial domains
d = b'.'.join(d.split(b'.')[1:])
k = pfx + b'.' + d + sfx
return acf[k].rstrip(b'\x00').decode()
except KeyError:
try:
try: # try bare prefix
return acf[pfx + sfx].rstrip(b'\x00').decode()
except KeyError:
try:
+24 -27
View File
@@ -11,9 +11,11 @@ sending DSNs or doing CBVs.
# Requirements
Python milter extension: https://pypi.python.org/pypi/pymilter/
Python milter extension: https://pypi.org/project/pymilter/
Python: http://www.python.org
Sendmail: http://www.sendmail.org
or
Postfix: http://www.postfix.org/MILTER_README.html
# Quick Installation
@@ -21,10 +23,10 @@ Sendmail: http://www.sendmail.org
2. Build and install Python, enabling threading.
3. Install this module: python setup.py --help
4. Add these two lines to sendmail.cf[a]:
```
O InputMailFilters=pythonfilter
Xpythonfilter, S=local:/home/username/pythonsock
```
5. Run the sample.py example milter with: python sample.py
Note that milters should almost certainly not run as root.
@@ -36,9 +38,9 @@ milter used in production.
[a] This is for a quick test. Your sendmail.cf in most distros will get
overwritten whenever sendmail.mc is updated. To make a milter permanent,
add something like:
```
INPUT_MAIL_FILTER(`pythonfilter', `S=local:/home/username/pythonsock, F=T, T=C:5m;S:20s;R:5m;E:5m')
```
to sendmail.mc instead.
# Not-so-quick Installation
@@ -54,18 +56,13 @@ Install this miltermodule package; DistUtils Automatic Installation:
$ python setup.py --help
For versions of python prior to 2.0, you will need to download distutils
separately or build manually. You will need to download unittest
separately to run the test programs. The bdist_rpm distutils option seems
not to work for python 2.0; upgrade to at least 2.1.1.
Now that everything is installed, we need to tell sendmail that we're going
to filter incoming email. Add lines similar to the following to
sendmail.cf:
```
O InputMailFilters=pythonfilter
Xpythonfilter, S=local:/home/username/pythonsock
```
The "O" line tells sendmail which filters to use in what order; here we're
telling sendmail to use the filter named "pythonfilter".
@@ -79,14 +76,14 @@ NB: The name is specified in two places: here, in sendmail's cf file, and
in the milter itself. Make sure the two match.
NB: The above lines can be added in your .mc file with this line:
```
INPUT_MAIL_FILTER(`pythonfilter', `S=local:/home/username/pythonsock')
```
For versions of sendmail prior to 8.12, you will need to enable
_FFR_MILTER for the cf macros. For example,
`_FFR_MILTER` for the cf macros. For example,
```
m4 -D_FFR_MILTER ../m4/cf.m4 myconfig.mc > myconfig.cf
```
# IPv6 Notes
The IPv6 protocol is supported if your operation system supports it
@@ -94,9 +91,9 @@ and if sendmail was compiled with IPv6 support. To determine if your
sendmail supports IPv6, run "sendmail -d0" and check for the NETINET6
compilation option. To compile sendmail with IPv6 support, add this
declaration to your site.config.m4 before building it:
```
APPENDDEF(`confENVDEF', `-DNETINET6=1')
```
IPv6 support can show up in two places; the communications socket
between the milter and sendmail processes and in the host address
argument to the connect() callback method.
@@ -107,26 +104,26 @@ want to allow both IPv4 and IPv6 connections, some operating systems
will require that each listens to different port numbers. For an
IPv6-only setup, your sendmail configuration should contain a line
similar to (first line is for sendmail.mc, second is sendmail.cf):
```
DAEMON_OPTIONS(`Name=MTA-v6, Family=inet6, Modify=C, Port=25')
O DaemonPortOptions=Name=MTA-v6, Family=inet6, Modify=C, Port=25
```
To allow sendmail and the milter process to communicate with each
other over IPv6, you may use the "inet6" socket name prefix, as in:
```
Xpythonfilter, S=inet6:1234@fec0:0:0:7::5c
```
The connect() callback method in the milter class will pass the
IPv6-specific information in the 'hostaddr' argument as a tuple. Note
that the type of this value is dependent upon the protocol family, and
is not compatible with IPv4 connections. Therefore you should always
check the family argument before attempting to use the hostaddr
argument. A quick example showing this follows:
```
import socket
...
class ipv6awareMilter(Milter.Milter):
...
def connect(self,hostname,family,hostaddr):
if family==socket.AF_INET:
ipaddress, port = hostaddr
@@ -134,7 +131,7 @@ argument. A quick example showing this follows:
ip6address, port, flowinfo, scopeid = hostaddr
elif family==socket.AF_UNIX:
socketpath = hostaddr
```
The hostname argument is always safe to use without interpreting the
protocol family. For IPv6 connections for which the hostname can not
be determined the hostname will appear similar to the string
+1
View File
@@ -20,6 +20,7 @@ modules = ["mime"]
setup(name = "pymilter", version = '1.0.5',
description="Python interface to sendmail milter API",
long_description=long_description,
long_description_content_type='text/markdown',
author="Jim Niemira",
author_email="urmane@urmane.org",
maintainer="Stuart D. Gathman",
+1
View File
@@ -7,3 +7,4 @@ SMTP-Auth:good@example.com OK
SMTP-Auth:example.com REJECT
SMTP-Auth:bad@localhost.localdomain REJECT
SMTP-Test: REJECT
SMTP-Test:.baz.com WILDCARD
+6
View File
@@ -8,6 +8,7 @@ class Config(object):
def __init__(self):
self.access_file='test/access.db'
self.access_file_nulls=True
self.access_file_colon = False
class PolicyTestCase(unittest.TestCase):
@@ -23,6 +24,8 @@ class PolicyTestCase(unittest.TestCase):
print("Missing test/access")
def testPolicy(self):
self.config.access_file_colon = False
self.config.access_file_nulls = True
with MTAPolicy('good@example.com',conf=self.config) as p:
pol = p.getPolicy('smtp-auth')
self.assertEqual(pol,'OK')
@@ -35,6 +38,9 @@ class PolicyTestCase(unittest.TestCase):
with MTAPolicy('any@random.com',conf=self.config) as p:
pol = p.getPolicy('smtp-test')
self.assertEqual(pol,'REJECT')
with MTAPolicy('foo@bar.baz.com',conf=self.config) as p:
pol = p.getPolicy('smtp-test')
self.assertEqual(pol,'WILDCARD')
def suite(): return unittest.makeSuite(PolicyTestCase,'test')
+3 -3
View File
@@ -31,7 +31,7 @@ class BMSMilterTestCase(unittest.TestCase):
count = 10
while count > 0:
rc = ctx._connect(helo='milter-template.example.org')
self.assertEquals(rc,Milter.CONTINUE)
self.assertEqual(rc,Milter.CONTINUE)
with open('test/'+fname,'rb') as fp:
rc = ctx._feedFile(fp)
milter = ctx.getpriv()
@@ -46,7 +46,7 @@ class BMSMilterTestCase(unittest.TestCase):
ctx._setsymval('{auth_type}','batcomputer')
ctx._setsymval('j','mailhost')
rc = ctx._connect()
self.assertEquals(rc,Milter.CONTINUE)
self.assertEqual(rc,Milter.CONTINUE)
with open('test/'+fname,'rb') as fp:
rc = ctx._feedFile(fp)
milter = ctx.getpriv()
@@ -69,7 +69,7 @@ class BMSMilterTestCase(unittest.TestCase):
milter = ctx.getpriv()
# self.assertTrue(milter.user == 'batman',"getsymval failed: "+
# "%s != %s"%(milter.user,'batman'))
self.assertEquals(milter.user,'batman')
self.assertEqual(milter.user,'batman')
self.assertTrue(milter.auth_type != 'batcomputer',"setsymlist failed")
self.assertTrue(rc == Milter.ACCEPT)
self.assertTrue(ctx._bodyreplaced,"Message body not replaced")
+2 -2
View File
@@ -24,11 +24,11 @@ class AddrCacheTestCase(unittest.TestCase):
self.assertTrue(cache.has_key('foo@bar.com'))
self.assertTrue(not cache.has_key('hello@bar.com'))
self.assertTrue('baz@bar.com' in cache)
self.assertEquals(cache['temp@bar.com'],'testing')
self.assertEqual(cache['temp@bar.com'],'testing')
s = open(self.fname).readlines()
self.assertTrue(len(s) == 2)
self.assertTrue(s[0].startswith('foo@bar.com '))
self.assertEquals(s[1].strip(),'baz@bar.com')
self.assertEqual(s[1].strip(),'baz@bar.com')
# check that new result overrides old
cache['temp@bar.com'] = None
self.assertTrue(not cache['temp@bar.com'])