Compare commits

..

14 Commits

Author SHA1 Message Date
Sandro 20751ea706 Set C standard to C17 explicitely (#70)
GCC 15 uses C23 by default. But `libmilter` is not compatible, yet.
This breaks the build as `bool` is a keyword in C23 (issue #68).
2025-03-12 20:05:10 -04:00
dotlambda 7197b82ed6 thread module has been renamed to _thread in Python 3 (#64) 2025-03-12 20:00:12 -04:00
Stuart D. Gathman 39a1fc78d8 Merge branch 'master' of github.com:sdgathman/pymilter 2024-10-15 19:43:54 -04:00
Stuart D. Gathman 5ad23e468d bsddb changed nulls in access file policy 2024-10-15 19:42:05 -04:00
Sandro 6eedaf7717 Python 3.13: Replace deprecated makeSuite() (#65)
The function has been deprecated in Python 3.11 and is no longer
available in Python 3.13.
2024-10-14 14:36:04 -04:00
Jean-Yves 4a8018c2de Welcome __NetBSD__ to the required header include. (#60)
Same rule applies for NetBSD as FreeBSD, <arpa/inet.h> include is
needed to provide inet_nto*() prototypes.
2024-06-05 08:18:15 -04:00
Stuart D. Gathman 1212a0ef59 Forgot to bump internal tags 2024-05-29 18:45:09 -04:00
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
15 changed files with 92 additions and 59 deletions
+3
View File
@@ -1,8 +1,11 @@
*.pyc
*.tar.gz
build/
test/*.out
test/*.tstout
test/*.log
test/*.db
test.db
dist
log
MANIFEST
+1 -1
View File
@@ -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
+4 -1
View File
@@ -1,7 +1,10 @@
from __future__ import print_function
import time
import shelve
import thread
try:
import thread
except:
import _thread as thread
import logging
import urllib
+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 -1
View File
@@ -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
View File
@@ -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
+5 -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",
@@ -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'],
+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
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
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 = 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
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")
@@ -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
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'])
@@ -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))