Quick Navigator

Search Site

Unix VPS
A - Starter
B - Basic
C - Preferred
D - Commercial
MPS - Dedicated
Previous VPSs
* Sign Up! *

Contact Us
Online Help
Domain Status
Man Pages

Virtual Servers

Topology Map

Server Agreement
Year 2038

USA Flag



Man Pages

Manual Reference Pages  -  SKEM (8)


skem - State KEeping Milter


Security Considerations
Future enhancements being considered
See Also


skem -h skem -v skem [-d] [-C directory] [-u uid | user] -b -| -w ip.add.res.s1 [ip.add.res.s2 ... ip.add.res.sN] skem [-d] [-C directory] [-u uid | user] [-g seconds] [-B seconds] [-l seconds] [-p pid-filename] [-L] skem [-d] [-C directory] [-u uid | user] [-g seconds] [-B seconds] [-l seconds] [-p pid-filename] [-2] [-4] [-S spamword] [-s pattern] [-r reject-string] socket


The skem utility is a sendmail milter, that checks and maintains a list of whitelisted, temporary banned, and permanently blacklisted IP-addresses. How you obtain the entries is up to you, but the included logwatcher module provides one possibility.

The list is stored in a directory (see the -C option), each entry being a file (usually -- zero sized) or a symlink (usually -- a "broken" one). Such entries are stored efficiently (within the directory itself) and the directories are searched using the hash tables on modern file systems. At the same time, they can be listed, added, and removed with the simple ls(1), touch 1 and rm(1).

The current release considers the following attributes of each entry:
mtime The time of creation
mode Setuid means the entry is "white-" and setgid -- "blacklisted".
ctime The time, this blacklisted entry last tried to contact us. Keeping this record allows for periodic purging of blacklist of the IPs not seen in a while, to keep the list from growing indefinitely.

This milter does not itself filter spam, instead it memorizes the verdicts issued by your other anti-spam defenses to reduce the system load and resource consumption by temporarily rejecting the relays suspected of spamming (banned) and, optionally, by permanently rejecting the relays "convicted" of spamming (blacklisted).

The idea is to stem the spam from real spam sources, while reducing the ill effects of false-positives to merely delaying, rather than rejecting future messages.

The following options are available:
-2 Do the check twice -- at the beginning (connection) and at the end of headers. This is to allow a ban, issued by other milters (and/or our own logwatcher, if enabled) based on the headers arriving from a previously not banned source, to have effect.

For example, if you have spamtrap-addresses, which trigger blacklisting, an e-mail coming from a previously unseen IP may contain them mixed with regular addresses. Without this option, skem will let the message through, even if subsequent transmissions from this address will be banned. With this option, even the first e-mail will be rejected. Only use if you employ such detectors and if they don’t have the capability to reject the whole message themselves, otherwise it is just a waste.

-4 Use only if sendmail supports closing connection at the conn-time. At the time of this writing (8.12.x being the latest version), sendmail must be rebuilt with the -D_FFR_MILTER_421 flag (a C define). This lets us issue temporary rejections right at the connection time and make sendmail close the connection on the spot, saving resources. If the sendmail does not support this, however (and by default it does not), any attempts by skem to do this will be interpreted as its own temporary failures by sendmail.

Without this option, skem will work with any sendmail issuing the temporary bans at the helo-time. The permanent bans are always acted upon at the conn-time.

-B seconds
  This specifies the amount of time a blacklist entry is allowed to exist in the list since it was last triggered. This is to avoid wasting storage on IPs, that have once offended us, but are not doing this any more. The default value is 3 weeks (1814400 seconds).
-C directory
  This is the database directory. If not specified, "." is assumed. skem will chdir(3) into this directory, and -- if started by root -- will also chroot(3) there.
-L This option will cause skem to run in the cleanup mode only. If the -l options was not specified prior, skem will make one cleanup run and exit. Otherwise, it will be doing the cleanups periodically, but without becoming a milter.
-S spamword
  Tells the logwatcher to treat the occurrence of this spamword in addition to the reject (see -r below) and pattern (see -s below) as a sign of definite spam. This will cause the logwatcher to issue a permanent (setgid), rather than a normal temporary ban. Use carefully. Without it logwatcher will only create temporary bans.
-b ip1 [ip2 ... ipN]
  Blacklist the specified IPs and exit. If an entry already exists, it is skipped without change of status.
-d Allow logging at the LOG_DEBUG level. Without this, skem only logs messages up to the LOG_INFO level.
-g seconds
  Expire the temporary bans after this many seconds. The default is an hour and a half (1800 seconds). Keep it below 4 hours, or some legitimate mail, that might be delayed on the temporarily banned server may generate a warning to the sender. Such warnings are remarkably confusing to the laymen and women, who can’t, for some reason, distinguish them from real bounces -- even though they begin with: "THIS IS A WARNING MESSAGE ONLY. YOU DO NOT NEED TO RESEND..."
-h Print out an the help screen and exit with code 0.
-l seconds
  Perform the cleanup this often. If not specified, no cleanup will be performed at all (in which case, you may wish to rebuild skem with the -DSKEM_NO_CLEANUP define). During cleanups, the expired temporary bans are removed, as well as the permanent bans, which were not triggered for the last 3 weeks (but see the -B option above).
-p filename
  Write our process ID into the file specified.
-r reject-string
  Tells logwatcher what to look for in the log messages. The default is "reject=5".
-s pattern
  This turns on the logwatcher thread, which reads log entries from stdin and applies the pattern to those of them, that contain the reject-string. The pattern is expected to be either a "-" (dash) to use the default pattern, or an extended regular expression with exactly one parenthesized expression -- the IP address of the relay.
-u username | uid
  Switch identity to the specified username or (numeric) uid. skem will refuse to become a milter as root, so this argument is mandatory, unless we are starting as a non-root already.
-v Print the version string(s) and exit.
-w ip1 [ip2 ... ipN]
  Whitelist the specified IPs and exit. If an entry already exists, it is skipped without change of status. Whitelists are never removed by the cleanup. If you have lower priority Mail eXchangers for your domain, or other legitimate machines, which frequently forward mail (ham and spam) to you, you should whitelist them.


The cleanup thread cleans up the directory off the expired temporary bans and the long-unseen permanent bans. See the description of -l -L -B options above for details. You can have the skem-milter do that, or, if that’s how you like it, use the -L option launching skem from cron(8). (Not that I can see a single reason to do it that way in normal operation.)

Note, that the expired entries are always removed by the milter thread itself, when triggered -- whether or not the cleanup thread is activated.

To avoid building the cleanup functionality, be sure to NOT use the -DSKEM_CLEANUP define.


The current milter architecture does not provide a way for milters to learn the fate of each message. They can not request to be notified, when a message is rejected by one of the many different mechanisms (such as another milter, non-existent domain, spamtrap, etc.). The only way to get this information is by watching the log files. You can cook something up with awk 1 / perl 1 / python 1 / Tcl n / whatever your poison -- all, your logwatcher needs to do, is place entries in the directory, where skem is looking for them -- with correct mode.

skem by default includes its own logwatcher thread, which is reasonably useful (but see the Security Considerations section below). If used, it expects stdin to contain the arriving log messages. Those of the lines, that contain the reject string (see the -r options above), are examined closer with IP-address pattern as provided by the -s option. If the string yielded by the pattern’s parenthesized expression looks like an IP address, the address is banned. If the spamword is specified (by -S option) and the string contains it as well, the ban is permanent. Otherwise, it is temporary. No ban is created if the IP is already listed as anything.

To avoid building the logwatcher functionality, be sure to NOT use the -DSKEM_LOGWATCHER define.

Security Considerations

Although believed to be secure anyway, skem will refuse to become a milter as root insisting on the alternative username to be passed with the -u switch. If started as root, it will also chroot(3) itself into the database directory prior to dropping root privileges.

However, using the logwatcher may expose you to denial of service attacks if your installation logs network-obtained data. For example, MIMEDefang milter logs the subjects of the messages it analyzes. If a subject contains the reject string and matches the IP pattern, the IP may be banned -- any IP, that an attacker puts into subject.

The FreeBSD example below does not have this problem, but the syslog.conf(5) syntax on other systems may not be as powerful.

Even on FreeBSD, a local user can use logger(1) to pretend to be sendmail. This is, however, intentional. It gives the users the power to request bans through logwatcher directly by something like

logger -t sendmail -p mail.notice "Please, reject=55x relay=[]"

But if you can not trust local users, you, probably, can not rely on log-watching anyway.


Here is the "live" syslog.conf(5) entry on my server
mail.notice,   |exec /home/mi/skem/skem -C /var/db/skem -S spammer -2 -s - -g 12000 -u mi -l 6000 -4 -B 604800 -d /var/db/skem/skem.sock

This has a disadvantage of being shut down every time the syslogd(8) is HUP-ed, and not restarted until sendmail(8) has something to say (like complain about a missing skem-milter).

A different way would be to log into a named pipe (see mkfifo(1)) with skem reading from the pipe. However, this is likely to hang the syslogd should skem ever go down (which is very unlikely, but still).


No configuration files are considered by skem -- all settings are given on command line.

When started as root, skem will chroot to the directory specified with the -C option (or to "."), so be sure the socket and the optional pid-filename specify paths underneath it. For convenience, skem will try to automatically adjust the paths specified so that they are correct after the chroot-ing.


The skem executable and the manual page should be installed according to your OS standards and customs (probably, under /usr/local/sbin and /usr/local/man/man8). To tell sendmail about the milter, please, refer to the sendmail’s libmilter documentation (in libmilter/README).

Future enhancements being considered

Depending on user interest (if any), the following features might be implemented:
grey list delay IPs, not yet listed, or listed less than a given number of seconds ago
accept syslog messages enhance the logwatcher module to accept the syslog messages directly through an inet or a domain socket
interpret the values of the links Although reading a file is unpleasant, the readlink(2) is a fast way to obtain information. skem can use the targets of the symlink-entries for various "interesting" purposes. For example, logwatcher can record the perceived reasons for adding an entry, like:
ln -s "caught in spam-trap"

This is fun, but wasteful, because all the information is in the maillogs too, one just needs to fgrep(1) for a particular IP-address to find out the reasons, it was added to the list.


logger(1), rm(1), touch(1), sendmail(8), syslogd(8), umask(1)


.An Mikhail Teterin Aq
Search for    or go to Top of page |  Section 8 |  Main Index

Powered by GSP Visit the GSP FreeBSD Man Page Interface.
Output converted with manServer 1.07.