Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5675adeb3c | |||
| 35416dfc46 | |||
| c33de064ee | |||
| 1c05080768 | |||
| dce7c0080a | |||
| 7deec90a59 | |||
| c73b533acb |
+24
-5
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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'])
|
||||
|
||||
Reference in New Issue
Block a user