diff --git a/Doxyfile b/Doxyfile
index ad35549..fecd122 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -31,7 +31,7 @@ PROJECT_NAME = pymilter
# This could be handy for archiving the generated documentation or
# if some version control system is used.
-PROJECT_NUMBER = 0.9.2
+PROJECT_NUMBER = 0.9.3
# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
# base path where the generated documentation will be put.
diff --git a/Milter/__init__.py b/Milter/__init__.py
index 960cad1..f712fe0 100755
--- a/Milter/__init__.py
+++ b/Milter/__init__.py
@@ -146,6 +146,14 @@ class Base(object):
def close(self): return CONTINUE
## Return mask of SMFIP_N.. protocol option bits to clear for this class
+ # The @@nocallback and @@noreply decorators set the
+ # milter_protocol function attribute to the protocol mask bit to
+ # pass to libmilter, causing that callback or its reply to be skipped.
+ # Overriding a method creates a new function object, so that
+ # milter_protocol defaults to 0.
+ # Libmilter passes the protocol bits that the current MTA knows
+ # how to skip. We clear the ones we don't want to skip.
+ # The negation is somewhat mind bending, but it is simple.
@classmethod
def protocol_mask(klass):
try:
diff --git a/Milter/dns.py b/Milter/dns.py
index ff962c2..fe9b54a 100644
--- a/Milter/dns.py
+++ b/Milter/dns.py
@@ -1,12 +1,22 @@
-# provide a higher level interface to pydns
+## @package Milter.dns
+# Provide a higher level interface to pydns.
import DNS
from DNS import DNSError
MAX_CNAME = 10
+## Lookup DNS records by label and RR type.
+# The response can include records of other types that the DNS
+# server thinks we might need.
+# @param name the DNS label to lookup
+# @param qtype the name of the DNS RR type to lookup
+# @return a list of ((name,type),data) tuples
def DNSLookup(name, qtype):
try:
+ # To be thread safe, we create a fresh DnsRequest with
+ # each call. It would be more efficient to reuse
+ # a req object stored in a Session.
req = DNS.DnsRequest(name, qtype=qtype)
resp = req.req()
#resp.show()
@@ -24,25 +34,28 @@ class Session(object):
def __init__(self):
self.cache = {}
+ ## Additional DNS RRs we can safely cache.
# We have to be careful which additional DNS RRs we cache. For
# instance, PTR records are controlled by the connecting IP, and they
# could poison our local cache with bogus A and MX records.
+ # Each entry is a tuple of (query_type,rr_type). So for instance,
+ # the entry ('MX','A') says it is safe (for milter purposes) to cache
+ # any 'A' RRs found in an 'MX' query.
+ SAFE2CACHE = frozenset((
+ ('MX','MX'), ('MX','A'),
+ ('CNAME','CNAME'), ('CNAME','A'),
+ ('A','A'),
+ ('AAAA','AAAA'),
+ ('PTR','PTR'),
+ ('NS','NS'), ('NS','A'),
+ ('TXT','TXT'),
+ ('SPF','SPF')
+ ))
- SAFE2CACHE = {
- ('MX','A'): None,
- ('MX','MX'): None,
- ('CNAME','A'): None,
- ('CNAME','CNAME'): None,
- ('A','A'): None,
- ('AAAA','AAAA'): None,
- ('PTR','PTR'): None,
- ('NS','NS'): None,
- ('NS','A'): None,
- ('TXT','TXT'): None,
- ('SPF','SPF'): None
- }
-
-
+ ## Cached DNS lookup.
+ # @param name the DNS label to query
+ # @param qtype the query type, e.g. 'A'
+ # @param cnames tracks CNAMES already followed in recursive calls
def dns(self, name, qtype, cnames=None):
"""DNS query.
diff --git a/Milter/dsn.py b/Milter/dsn.py
index 75be7ff..290fdd0 100644
--- a/Milter/dsn.py
+++ b/Milter/dsn.py
@@ -5,6 +5,9 @@
# Send DSNs, do call back verification,
# and generate DSN messages from a template
# $Log$
+# Revision 1.17 2009/05/20 20:08:44 customdesigned
+# Support non-DSN CBV (non-empty MAIL FROM)
+#
# Revision 1.16 2007/09/25 01:24:59 customdesigned
# Allow arbitrary object, not just spf.query like, to provide data for create_msg
#
@@ -26,7 +29,31 @@
# Revision 1.10 2006/05/24 20:56:35 customdesigned
# Remove default templates. Scrub test.
#
-
+## @package Milter.dsn
+# Support DSNs and CallBackValidations (CBV).
+#
+# A Delivery Status Notification (bounce) is sent to the envelope
+# sender (original MAIL FROM) with a null MAIL FROM (<>) to notify the
+# original sender # of delays or problems with delivery. A Callback Validation
+# starts the DSN process, but stops before issuing the DATA command. The
+# purpose is to check whether the envelope recipient is accepted (and is
+# therefore a valid email). The null MAIL FROM tells the remote
+# MTA to never reply according to RFC2821 (but some braindead MTAs
+# reply anyway, of course).
+#
+# Milters should cache CBV results and should avoid sending DSNs
+# unless the sender is authenticated somehow (e.g. SPF Pass). However,
+# when email is quarantined, and is not known to be a forgery, sending a DSN
+# is better than silently disappearing, and a DSN is better than sending
+# a normal message as notification - because MAIL FROM signing schemes
+# can reject bounces of forged emails. Whatever you do, don't copy those
+# assinine commercial filters that send a normal message to notify you
+# that some virus is forging your email.
+#
+# DSNs should *only* be sent to MAIL FROM addresses. Never send
+# a DSN or use a null MAIL FROM with an email address obtained from
+# anywhere else.
+#
import smtplib
import socket
from email.Message import Message
@@ -34,6 +61,19 @@ import Milter
import time
import dns
+## Send DSN.
+# Try the published MX names in order, rejecting obviously bogus entries
+# (like localhost).
+# @param mailfrom the original sender we are notifying or validating
+# @param receiver the HELO name of the MTA we are sending the DSN on behalf of.
+# Be sure to send from an IP that matches the HELO.
+# @param msg the DSN message in RFC2822 format, or None for CBV.
+# @param timeout total seconds to wait for a response from an MX
+# @param session Milter.dns.Session object from current incoming mail
+# session to reuse its cache, or None to create a fresh one.
+# @param ourfrom set to a valid email to send a normal notification from, or
+# to validate emails not obtained from MAIL FROM.
+# @return None on success or (status_code,msg) on failure.
def send_dsn(mailfrom,receiver,msg=None,timeout=600,session=None,ourfrom=''):
"""Send DSN. If msg is None, do callback verification.
Mailfrom is original sender we are sending DSN or CBV to.