Python Milter FAQ
- Q. I have tried to download the current milter code and my virus scan
traps several viruses in the download.
A. The milter source includes a number of deactivated viruses in
the test directory. All but the first and last lines of the base64
encoded virus data has been removed. I suppose I should randomize
the first and last lines as well, since pymilter just deletes executables,
and doesn't look for signatures.
- Q. I have installed sendmail from source, but Python milter won't
A. Even though libmilter is officially supported in sendmail-8.12,
you need to build and install it in separate steps. Take a look
at the RPM spec file for sendmail-8.12.
The %prep section shows you how to create
a site.config.m4 that enables MILTER. The %build section shows you how
to build libmilter in a separate invocation of make. The %install section
shows you how to install libmilter with a separate invocation of make.
- Q. Why is mfapi.h not found when I try to compile Python milter on
A. RedHat forgot to include the header in the RPM. See the
RedHat 7.2 requirements.
- Q. Python milter compiles ok, but I get an error like this when
I try to import the milter module:
ImportError: /usr/lib/python2.4/site-packages/milter.so: undefined symbol: smfi_setmlreply
A. Your libmilter.a is from sendmail-8.12 or earlier. You need
sendmail-8.13 or later to support setmlreply. You can disable
setmlreply by changing setup.py. Change:
define_macros = [ ('MAX_ML_REPLY',32) ]
in setup.py to
define_macros = [ ('MAX_ML_REPLY',1) ]
- Q. The sample.py milter prints a message, then just sits there.
To use this with sendmail, add the following to sendmail.cf:
See the sendmail README for libmilter.
sample milter startup
A. You need to tell sendmail to connect to your milter. The
sample milter tells you what to add to your sendmail.cf to tell
sendmail to use the milter. You can also add an INPUT_MAIL_FILTER
macro to your sendmail.mc file and rebuild sendmail.cf - see the sendmail
README for milters.
- Q. I've configured sendmail properly, but still nothing happens
when I send myself mail!
A. Sendmail only milters SMTP mail. Local mail is not miltered.
You can pipe a raw message through sendmail to test your milter:
$ cat rawtextmsg | sendmail firstname.lastname@example.org
Now check your milter log.
- Q. Why do I get this ImportError exception?
File "mime.py", line 370, in ?
from sgmllib import declstringlit, declname
ImportError: cannot import name declstringlit
declstringlit is not provided by sgmllib in all versions
of python. For instance, python-2.2 does not have it. Upgrade to
milter-0.4.5 or later to remove this dependency.
- Q. Why do I get
milter.error: cannot add recipient?
A. You must tell libmilter how you might mutate the message with
set_flags() before calling
Milter.set_flags(Milter.ADDRCPT). You must add together
ADDHDRS, CHGBODY, ADDRCPT, DELRCPT, CHGHDRS that apply.
NOTE - recent versions default flags to enabling all features. You
must now call
set_flags() if you wish to disable features for
- Q. Why does sendmail sometimes print something like:
"...write(D) returned -1, expected 5: Broken pipe"
in the sendmail log?
A. Libmilter expects "rcpt to" shortly after getting "mail from".
"Shortly" is defined by the timeout parameter you passed to
milter.settimeout(). If the timeout is 10 seconds,
and looking up the first recipient in DNS takes more than
10 seconds, libmilter will give up and break the connection.
Milter.runmilter() defaulted to 10 seconds in 0.3.4. In 0.3.5
it will keep the libmilter default of 2 hours.
- Q. Why does milter block messages with big5 encoding? What if I
want to receive them?
A. sample.py is a sample. It is supposed to be easily modified
for your specific needs. We will of course continue to move generic
code out of the sample as the project evolves. Think of sample.py as
an active config file.
If you are running bms.py, then the block_chinese option in
/etc/mail/pymilter.cfg controls this feature.
- Q. How do I use a network socket instead of a unix socket?
inet: instead of
unix: in the
URL for your socket in sendmail.mc/sendmail.cf and in *milter.cfg. The default
protocol is unix. So the default config would be:
NOTE: for spfmilter, the config is "socketname" instead of "socket".
To have your python milter listen on port 1234 of the network interface with
- Q. Why does sendmail coredump with milters on OpenBSD?
A. Sendmail has a problem with unix sockets on old versions of OpenBSD.
OpenBSD users report that this problem has been fixed, so upgrading
OpenBSD will fix this. Otherwise, you can
use an internet domain socket instead. For example, in
and change sample.py accordingly.
- Q. How can I change the bounce message for an invalid recipient?
I can only change the recipient in the eom callback, but the eom callback
is never called when the recipient is invalid!
A. For sendmail-8.13 and later, use pymilter-0.9.3 and clear
Milter.P_RCPT_REJ in the _protocol_mask class var:
myMilter._protocol_mask = myMilter.protocol_mask() & ~Milter.P_RCPT_REJ
For sendmail-8.12 and earlier, configure sendmail to use virtusertable,
and send all unknown addresses to /dev/null. For example,
Now your milter will get to the eom callback, and can change the
envelope recipient at will. Thanks to Dredd at
milter.org for this solution.
- Q. I am having trouble with the setreply method. It always outputs
"milter.error: cannot set reply".
A. Check the sendmail log for errors. If sendmail is getting
milter timeouts, then your milter is taking too long and sendmail gave
up waiting. You can adjust the timeouts in your sendmail config. Here
is a milter declaration for sendmail.cf with all timeouts specified:
Xpythonfilter, S=local:/var/log/milter/pythonsock, F=T, T=C:5m;S:20s;R:60s;E:5m
- Q. Once I feed my milter a valid address (which returns Milter.ACCEPT
from the envrcpt() method) the envrcpt() method is no longer called.
Is there something I can do to change this behavior?
A. Return Milter.CONTINUE instead of Milter.ACCEPT from envrcpt().
- Q. There is a Python traceback in the log file! What happened to
A. By default, when the milter fails with an untrapped exception, a
TEMPFAIL result (451) is returned to the sender. The sender will then retry
every hour or so for several days. Hopefully, someone will notice the
traceback, and workaround or fix the problem. Beginning with milter-0.8.2,
you can call
to cause an untrapped exception to continue processing with the
next callback or milter instead. For
completeness, you can also set the exception policy to
- Q. I read some notes such as "Check valid domains allowed by internal
senders to detect PCs infected with spam trojans." but could not
understand the idea. Could you clarify the content ?
internal_domains configuration specifies which
MAIL FROM domains are used by internal connections. If an internal
PC tries to use some other domain, it is assumed to be a "Zombie".
Here is a sample log line:
2005Jun22 12:01:04  REJECT: zombie PC at 192.168.100.171 sending MAIL FROM email@example.com
No, fedex.com does not use pymilter, and there is no one named debby at my
client. But the idiot using the PC at 192.168.100.171 has downloaded and
installed some stupid weatherbar/hotbar/aquariumscreensaver that is actually a
internal_domains option is simplistic, it assumes all
valid senders of the domains are internal. SPF provides a much more general
check of IP and MAIL FROM for external email. Pymilter should soon
have a local policy feature for more general checking of internal mail.
mail_archive isn't working. Or I don't understand how
it's suppose to work. I have
mail_archive = /var/mail/mail_archive
pymilter.cfg but nothing ever gets dumped into
A. The 'mail' user needs to have write access. Permission failures
should be logged as a traceback in milter.log if it doesn't.
- Q. So how do I use the SPF support? The sample.py milter doesn't seem
to use it.
A. The milter package contains several more useful milters. The spfmilter.py milter checks SPF. The bms.py milter supports spf and too many other
things. The RedHat RPMs will set almost everything up for you.
For other systems:
- Arrange to run spfmilter.py or bms.py in the background (as a service
perhaps) and redirect output and errors to a logfile. For instance, on
AIX you'll want to use SRC (System Resource Controller).
- Copy spfmilter.cfg or pymilter.cfg to /etc/mail or the directory you run
bms.py in, and edit it. The comments should explain the options.
- Start spfmilter.py or bms.py in the background as arranged.
- Add Xpythonfilter (or whatever you configured as miltername) to
sendmail.cf or add an INPUT_MAIL_FILTER to sendmail.mc. Regen sendmail.cf
if you use sendmail.mc and restart sendmail.
- Arrange to rotate log files and remove old defang files in
tempdir. The RedHat RPM uses
logfiles and a simple cron script using
find to clean
spfmilter.py runs as a service,
and does just SPF. It uses the sendmail
file to configure SPF responses just like
supports only REJECT and OK.
- Q. Can I somehow disable SPF checking for outgoing mail? The spf milter
is always writing a useless (and wrong) Received-SPF header when I
A. Geeky answer: Please define what you mean by "outgoing". An MTA
receives incoming mail transactions via SMTP, and either delivers them
via a local delivery agent or relays them to another MTA. You might
call it "outgoing" when it relays to another MTA, but spf milter
is never invoked for "outgoing" connections, and can never
check SPF. (Sometimes you do want to check SPF on outgoing connections
as a last minute check that your own SPF record is correct,
and you have to use an SMTP proxy or another MTA instance to
do that.) Note that a given incoming connection often results
in both local deliveries and relays to other MTAs.
Useful answer: spfmilter.py does not check SPF for incoming
connections that are "internal". You define IPs that are
considered "internal" in the configuration (e.g. 192.168.*), and
Transactions using SMTP AUTH are also considered internal.
You probably need to configure your internal email sources
to avoid spurious SPF checks.
- Q. bms.py sends the SPF DSN at least once for domains that
don't publish a
SPF. How do I stop this behavior?
A. The SPF response is controlled by
(actually the file you specify with
[spf] section of
Responses are OK, CBV, DSN, and REJECT. DSN sends the DSN.
You can change the defaults. For instance, I have:
I have best_guess = 1, so SPF none is converted to PASS/NEUTRAL for policy
lookup, and 3 strikes (no PTR, no HELO, no SPF) becomes "SPF NONE" for local
policy purposes (the Received-SPF header always shows the official SPF
You can change the default for specific domains:
# these guys aren't going to pay attention to CBVs anyway...
- Q. The SRS part doesn't seem to work as whenever I try to start
/etc/init.d/pysrs, I get this in
ConfigParser.NoOptionError: No option 'fwdomain' in section: 'srs'
A. You need to specify the forward domain - i.e. the domain you want
SRS to rewrite stuff to.
For instance, I have:
# sample SRS configuration
secret = don't you wish
maxage = 8
hashlength = 5
fwdomain = bmsi.com
sign is for local domains which are signed.
srs list is for other domains which you are relaying,
and which need to have SRS checked/undone for bounces.