Home

TclMilter

TclMilter(n) 1.0 "Tcl interface to SendMail's Milter API"

NAME

TclMilter is a package for Tcl (written in C) that implements an interface to SendMail's Milter (Mail Filter) API for developing custom scripted message rewriting and spam filtering processes. A thread-enabled Tcl build is required due to Milter's threading requirements. However, you can only run, configure, or register callbacks for a milter from the first thread/interpreter the package is loaded into. If you need to be able to stop the milter from within Tcl, you can load the package in another thread to do so. Only the "milter stop" command will function in secondary threads.

TABLE OF CONTENTS

SYNOPSIS

package require Tcl 8.1
package require TclMilter ?1.0?

DESCRIPTION

TclMilter is a package for Tcl (written in C) that implements an interface to SendMail's Milter (Mail Filter) API for developing custom scripted message rewriting and spam filtering processes. A thread-enabled Tcl build is required due to Milter's threading requirements. However, you can only run, configure, or register callbacks for a milter from the first thread/interpreter the package is loaded into. If you need to be able to stop the milter from within Tcl, you can load the package in another thread to do so. Only the "milter stop" command will function in secondary threads.

TclMilter follows libmilter's APIs very closely, with the following exceptions for readability and flexibility:

  • The smfi_opensocket command is not provided, only milter main as an interface to smfi_main.
  • The smfi_setconn, smfi_settimeout, smfi_setbacklog and smfi_setdbg commands, as well as settings normally provided through smfi_main are instead provided as milter configure options.
  • The smfi_register command is provided as milter register; however, unlike libmilter it's possible to register and unregister callbacks at any time, rather than just once prior to calling main.
  • There isn't a single data storage value for smfi_setpriv; rather, TclMilter's milter setpriv command uses a hash table so you can set individual, custom variables with data to associate with a context.
  • The context provided to callbacks is not simply a pointer, but is an actual Tcl command that can be used to interact with the context.

RETURN CODES

Callbacks are invoked by TclMilter at various stages of message processing in order to provide a filter with all of the information about the MTA connection and message being sent. These callbacks may return one of five values to indicate to SendMail whether to continue processing the message or whether to reject it for some reason.

SMFIS_CONTINUE
Indicates that message processing should continue, and that other callbacks should continue to be called (i.e., the message is neither accepted or rejected yet). Callbacks should always return this value by default.
SMFIS_REJECT
Indicates that the message should be rejected immediately. The milter may use context setreply prior to returning this value to indicate the status code and reason for the rejection.
SMFIS_DISCARD
Indicates that the message should be discared immediately.
SMFIS_ACCEPT
Indicates that the message should be accepted as far as this milter is concerned. No other callbacks will be invoked. However, the message may still be rejected for other reasons - either by other milters or by SendMail itself.
SMFIS_TEMPFAIL
Indicates that the message should be rejected immediately with a temporary failure. The milter may use context setreply prior to returning this value to indicate the status code and reason for the rejection.

MILTER CONTROL FUNCTIONS

milter configure ?option ?value?? ...

Configure Milter session parameters. Given no options, returns a list of option/value pairs indicating current milter configuration. Given an option and no values, returns the current value of the specified option. Otherwise, each option is set to the given value.

-name name
Specifies the name for the milter. This should be a unique name among all the milters being used by the MTA, and it must be set before calling milter main if at all. The default is "TclMilter".
-connection foo

Specifies the type of connection to use for communicating with SendMail, as well as the appropriate parameters for the connection type. It must be set before calling milter main if at all. Per libmilter's smfi_setconn documentation, the format is "proto:address":

  • {unix|local}:/path/to/file -- A named pipe.
  • inet:port@{hostname|ip-address} -- An IPV4 socket.
  • inet6:port@{hostname|ip-address} -- An IPV6 socket.

The default is "unix:/var/run/tclmilter.sock" - a pipe socket using the file "/var/run/tclmilter.sock".

-timeout seconds
Specifies the number of seconds that the Milter should wait for an MTA connection before timing out. The timer can be reset using the context progress command. This setting may only be changed prior to calling milter main. Default: 7210 seconds.
-backlog count
Configures the number of incoming connections that the milter will allow in the listen queue. This setting may only be changed prior to milter main. The default for the milter is the system default.
-debuglevel level
Configures the level of debug information that will be logged by libmilter. This setting may be changed at any time. Default: 0.
milter main
Opens the socket to allow connection to SendMail and to begin filtering mail. This command will not return unless milter stop is called or the milter receives a signal requiring a shutdown, such as SIGHUP.
milter register event ?callback?

Register a callback for TclMilter to invoke at the specified stage on mail processing. Callbacks are supported corresponding to each of those provided by libmilter its self. Unlike libmilter, which must have callbacks specified by the call to smfi_main(), callbacks for TclMilter may be registered or unregistered at any time. The callback argument specifies the name of the procedure for TclMilter to invoke for the specified event. If this argument is not provided, the command will return the name of the currently registered callback procedure for the specified event.

The type and arguments that the callback receives depend on the event, although every callback is provided with a unique context ID that can be used to manipulate the message being processed. Supported event types are as follows:

milter register connect connectProc

Arranges for proc connectProc to be called when a connection is established to sendmail, before the exchange of HELO greetings. The callback is appended with three arguments: the context command, the hostname and a list containing a dictionary of information from the sockaddr stucture (e.g., address and port).

proc connectProc { context hostname addrInfo } { ... }

milter register helo heloProc

Arranges for proc heloProc to be called when the client sends its HELO greeting command. The command is appended with two arguments: the context and the hostname as provided in the HELO command.

proc heloProc { context helohost } { ... }

milter register envfrom envfromProc

Arranges for proc envfromProc to be called when the client sends the MAIL FROM command. The command is appended with two elements: the context command and a list of all of the addresses specified in the MAIL FROM envelope.

proc envfromProc { context fromList } { ... }

milter register envrcpt envrcptProc

Arranges for proc envrcptProc to be called when the client sends the RCPT TO command. The command is appended with two elements: teh context command and a list of all of the addresses specified in the RCPT TO envelope.

proc envrcptProc { context rcptList } { ... }

milter register header headerProc

Arranges for proc headerProc to be called for each header sent by the client. The command is appended with three elements: the context command, the header name and the contents of the header.

proc headerProc { context name content } { ... }

milter register eoh eohProc

Arranges for proc eohProc to be called once all headers have been sent and handled by the milter's "header" callback, before the message body is sent. The command is appended with one argument: the context command.

proc eohProc { context } { ... }

milter register body bodyProc

Arranges for proc bodyProc to be called for each block of data in the message body. The command is appended with two elements: the context command and the body text. Each message has only one body, but this body may be sent in multiple blocks. If the milter intends to do content filtering on the message body, it is recommended to use context setpriv to build and store the complete message body and perform content filtering in the eom callback.

proc bodyProc { context block } { ... }

milter register eom eomProc

Arranges for proc eomProc to be called once once all message information has been received, including the entire message body, and processed by the milter. All message rewriting, such as adding and removing headers or changing the body, must be performed within this callback. The command is appended with one element: the context command.

proc eomProc { context } { ... }

milter register abort abortProc

Arranges for proc abortProc to be called if message transmission is aborted, such as due to a dropped connection. This callback is not invoked if message transmission is completed. The command is appended with one element: the context command.

proc abortProc { context } { ... }

milter register close closeProc

Arranges for proc closeProc to be called when the client connection has been closed. This callback is not invoked if message transmission is aborted. The command is appended with one element: the context command.

proc closeProc { context } { ... }

milter stop
Stop the milter, causing milter main to return. Note that, due to libmilter limitations, it is currently not possible to restart the milter once stopped. You will have to exit Tcl and then restart, reload the package, etc. in order to start the milter up again.

DATA ACCESS FUNCTIONS

The following context functions may be invoked from any callback to get or store information about the message context or sendmail configuration. Each context has a hash table associated with it, and a set of accessor functions that work similar to Tcl's own set command, allowing the milter to associate data with each message that can be passed around between callbacks and automatically garbage collected when the context is deleted.

context getpriv ?name?
If name is provided, this command returns the value from the hash table with the given key name. If name is unspecified, a list of name value pairs is returned with all of the private data associated with the context, in dictionary format (i.e., suitable for use with array set).
context getsymval symbol
Returns the value for the specified symbol in the SendMail configuration. The symbol may be either a single letter or a keyword surrounded in {braces}. Consult the libmilter and/or SendMail documentation for more details.
context setpriv name ?value?
Sets the specified value within the context's private hash table using the given variable name/key name. If value is not specified, returns the current value for the specified key, as with getpriv.
context setreply rcode ??xcode? message?
Sets the specific reply/status code for the message to be returned to the client MTA and, optionally, the extended reply code and a message (such as a reason for rejecting the message).
context unsetpriv ?pattern?
This command could be considered a hybrid of Tcl's unset and array unset commands. It removes each variable whose name matches the specified glob-style pattern from the context's private data hash table. If no pattern is specified then all private data is deleted from the context.

MESSAGE MODIFICATION FUNCTIONS

The following context functions may be invoked ONLY from the eom callback to modify message contents. The result of invoking these commands in other callbacks is undefined.

context addheader name data
Adds a new header to the message with the given header name and contents data.
context addrcpt rcpt
Instructs sendmail to deliver the message to the specified recipient in addition to any others the message is already being sent to, provided the message is not rejected.
context chgheader name ?index? data
Changes an existing header in the message with the given header name to the contents data. The index value specifies which instance of the header is to be modified. If unspecified, the first instance of the header will be modified. If no such header exists, it will be added.
context delrcpt rcpt
Instructs sendmail NOT to deliver the message to specified recipient. The message may still be delivered to other recipients, provided the message is not rejected.
context insheader name ?index? data
Inserts a new header into the message with the given header name and contents data. By default, headers are inserted with index 0 - that is, they are inserted as the first header. Otherwise, they are inserted before the indexth header. If index is greater than the number of headers in the message, the new header is added at the end, as with addheader.
context replacebody data
Replaces the entire message body with data. This can be useful, for example, to remove questionable attachments or change message encoding.

OTHER MESSAGE HANDLING FUNCTIONS

The following context functions may be invoked ONLY from the eom callback. The result of invoking these commands in other callbacks is undefined.

context progress
This command may be called during long processes to inform SendMail/libmilter that the message is still being processed. When called, the timeout clock is reset.
context quarantine reason
This command may be called to quarantine the message. The message is neither rejected nor delivered, but is left in the mail queue, requiring administrator action to be delivered or unqueued.

EXAMPLE

#!/usr/local/bin/tclsh
#
# A simple email address-based blacklist milter
#

package require Tcl 8.4
package require TclMilter 1.0

# Configuration

set blacklist {
    friend@public.com
    spammer@spamdomain.com
}

milter configure -name Blacklist
milter configure -connection {unix:/var/run/blmilter.sock}

# Check to see if an address is on the blacklist

proc is_blacklisted { address } {
    set address [string trim $address <>]

    if {[lsearch -exact $::blacklist $address] != -1} {
        return 1
    }

    return 0
}

# Callbacks

proc bl_header { context name data } {
    if {($name == "From") && [is_blacklisted $data]} {
        return SMFIS_REJECT
    }

    return SMFIS_CONTINUE
}

proc bl_envfrom { context addressList } {
    foreach address $addressList {
        if {[is_blacklisted $address]} {
            return SMFIS_REJECT
        }
    }

    return SMFIS_CONTINUE
}

# Register the callbacks and start up the filter

milter register header bl_header
milter register envfrom bl_envfrom
milter main
file delete /var/run/blmilter.sock

LICENSE

TclMilter is provided under the GNU General Public License (GPL) (see the license.terms file for details).

ACKNOWLEDGEMENTS

Portions of this document, as well as the package its self, are based on the Milter API specification.

SEE ALSO

Milter API, RFC 1893, RFC 2034, RFC 2821, RFC 821

KEYWORDS

email, filter, milter, sendmail, spam, tcl

Copyright © 2006 Muonics, Inc.