Compare commits
14 Commits
pymilter-1.0.5
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 20751ea706 | |||
| 7197b82ed6 | |||
| 39a1fc78d8 | |||
| 5ad23e468d | |||
| 6eedaf7717 | |||
| 4a8018c2de | |||
| 1212a0ef59 | |||
| 5675adeb3c | |||
| 35416dfc46 | |||
| c33de064ee | |||
| 1c05080768 | |||
| dce7c0080a | |||
| 7deec90a59 | |||
| c73b533acb |
@@ -1,8 +1,11 @@
|
||||
*.pyc
|
||||
*.tar.gz
|
||||
build/
|
||||
test/*.out
|
||||
test/*.tstout
|
||||
test/*.log
|
||||
test/*.db
|
||||
test.db
|
||||
dist
|
||||
log
|
||||
MANIFEST
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
# This code is under the GNU General Public License. See COPYING for details.
|
||||
|
||||
from __future__ import print_function
|
||||
__version__ = '1.0.5'
|
||||
__version__ = '1.0.6'
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
from __future__ import print_function
|
||||
import time
|
||||
import shelve
|
||||
try:
|
||||
import thread
|
||||
except:
|
||||
import _thread as thread
|
||||
import logging
|
||||
import urllib
|
||||
|
||||
|
||||
+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
|
||||
|
||||
@@ -4,7 +4,7 @@ web:
|
||||
rsync -ravKk doc/html/ pymilter.org:/var/www/html/milter/pymilter
|
||||
cd doc/html; zip -r ../../doc .
|
||||
|
||||
VERSION=1.0.5
|
||||
VERSION=1.0.6
|
||||
PKG=pymilter-$(VERSION)
|
||||
SRCTAR=$(PKG).tar.gz
|
||||
|
||||
|
||||
+1
-1
@@ -72,7 +72,7 @@ $ python setup.py help
|
||||
* published. Unfortunately I know of no good way to do this
|
||||
* other than with OS-specific tests.
|
||||
*/
|
||||
#if defined(__FreeBSD__) || defined(__linux__) || defined(__sun__) || defined(__GLIBC__) || (defined(__APPLE__) && defined(__MACH__))
|
||||
#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__linux__) || defined(__sun__) || defined(__GLIBC__) || (defined(__APPLE__) && defined(__MACH__))
|
||||
#define HAVE_IPV6_RFC2553
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
|
||||
@@ -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",
|
||||
@@ -35,7 +36,10 @@ setup(name = "pymilter", version = '1.0.5',
|
||||
# set MAX_ML_REPLY to 1 for sendmail < 8.13
|
||||
define_macros = [ ('MAX_ML_REPLY',32) ],
|
||||
# save lots of debugging time testing rfc2553 compliance
|
||||
extra_compile_args = [ "-Werror=implicit-function-declaration" ]
|
||||
extra_compile_args = [
|
||||
"-Werror=implicit-function-declaration",
|
||||
"-std=gnu17",
|
||||
]
|
||||
),
|
||||
],
|
||||
keywords = ['sendmail','milter'],
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ class ConfigTestCase(unittest.TestCase):
|
||||
miltersrs = cp.getboolean('srsmilter','miltersrs')
|
||||
self.assertFalse(miltersrs)
|
||||
|
||||
def suite(): return unittest.makeSuite(ConfigTestCase,'test')
|
||||
def suite(): return unittest.TestLoader().loadTestsFromTestCase(ConfigTestCase)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ class GreylistTestCase(unittest.TestCase):
|
||||
grey.close()
|
||||
|
||||
def suite():
|
||||
s = unittest.makeSuite(GreylistTestCase,'test')
|
||||
s = unittest.TestLoader().loadTestsFromTestCase(GreylistTestCase)
|
||||
return s
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
+1
-1
@@ -234,7 +234,7 @@ class MimeTestCase(unittest.TestCase):
|
||||
#print(msg + filter.msg)
|
||||
self.assertTrue(result.getvalue() == msg + filter.msg)
|
||||
|
||||
def suite(): return unittest.makeSuite(MimeTestCase,'test')
|
||||
def suite(): return unittest.TestLoader().loadTestsFromTestCase(MimeTestCase)
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) < 2:
|
||||
|
||||
+7
-1
@@ -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 = False # FIXME: test old and new bsddb
|
||||
with MTAPolicy('good@example.com',conf=self.config) as p:
|
||||
pol = p.getPolicy('smtp-auth')
|
||||
self.assertEqual(pol,'OK')
|
||||
@@ -35,8 +38,11 @@ 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')
|
||||
def suite(): return unittest.TestLoader().loadTestsFromTestCase(PolicyTestCase)
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) < 2:
|
||||
|
||||
+4
-4
@@ -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")
|
||||
@@ -142,7 +142,7 @@ class BMSMilterTestCase(unittest.TestCase):
|
||||
f.write(fp.getvalue())
|
||||
milter.close()
|
||||
|
||||
def suite(): return unittest.makeSuite(BMSMilterTestCase,'test')
|
||||
def suite(): return unittest.TestLoader().loadTestsFromTestCase(BMSMilterTestCase)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
+3
-3
@@ -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'])
|
||||
@@ -54,7 +54,7 @@ class AddrCacheTestCase(unittest.TestCase):
|
||||
self.assertEqual(s,('WRONG', 'a@b'))
|
||||
|
||||
def suite():
|
||||
s = unittest.makeSuite(AddrCacheTestCase,'test')
|
||||
s = unittest.TestLoader().loadTestsFromTestCase(AddrCacheTestCase)
|
||||
s.addTest(doctest.DocTestSuite(Milter.utils))
|
||||
s.addTest(doctest.DocTestSuite(Milter.dynip))
|
||||
s.addTest(doctest.DocTestSuite(Milter.pyip6))
|
||||
|
||||
Reference in New Issue
Block a user