Chapter 26. The X (Milters) Configuration Command

Beginning with V8.12, sendmail offers hooks to access external programs via sockets, and a library to build external programs that listen on sockets. A Milter is an external program that can be used to screen inbound email (mail received by your server rather than mail sent by your client).[442] A Milter is composed of two kinds of functions:

  • Those that you write yourself are called xxfi_ functions.

  • Those that your xxfi_ functions call in the Milter library are called smfi_ Milter library routines.

V8.13 and V8.14 sendmail added several new functions to the Milter library. In this chapter, we first discuss the hooks inside the configuration file that support external programs, and after that briefly discuss building your own program.

While screening email messages, Milters can:

  • Add, modify, or remove headers.

  • Add, remove, or reject recipients.

  • Change or reject the sender.

  • Replace the body.

  • Accept, reject, defer, or quarantine individual messages.

  • Enforce policy, archive for conformance, or enable security rules.

  • Sign or verify using DKIM, DomainKeys, SPF, or other standards.

Creating your own Milters is possible only on operating systems that include POSIX threading (pthread) support. Table 26-1 lists the operating systems that do, and do not, include the required threading support.

Table 26-1. Operating system support for Milters

Operating system

Support

FreeBSD

3.x and later

SunOS (Solaris)

5.5 and later

AIX

4.3 and later

HP-UX

11 and later

Linux

Recent distributions

IRIX

No

Ultrix

No

Mac OS X

10.4 and later

If your operating system lacks support, consider upgrading or contact your vendor. Or if your operating system is not listed, try building a Milter and, if you succeed, let the folks at know by visiting this page:

http://www.sendmail.org/support/

Create Milter Support

Milters are external programs that run separately from sendmail, but communicate with sendmail using a special API called the Milter API. Thus, support must be included inside sendmail before you may use any Milter at all. In this section, we discuss the support you must set up:

  • Prior to V8.13 sendmail, use the -DMILTER Build switch to enable Milter support inside sendmail.

  • Build and install the libmilter library for use by Milters.

The libmilter library is needed only if you intend to write (or download and build) your own Milter. If you purchase a prebuilt Milter, you may not need to build the libmilter library.

Pre-V8.13 Enable with -DMILTER

Prior to V8.13 sendmail, you needed to build sendmail with the MILTER compile-time macro defined. With V8.13 and later, MILTER is always defined by default.

To build sendmail in this way, simply add a line such as the following to your m4 Build file:

APPENDDEF(`confENVDEF', `-DMILTER')

Then, build sendmail in the usual manner.

If you are using precompiled sendmail, you can detect whether it was built with the MILTER compile-time macro defined by running the following command:[443]

% /usr/sbin/sendmail -bt -d0.4 < /dev/null

If MILTER was defined, it will appear among a list of other defined macros in a line that will look something like this:

Compiled with: DNSMAP LOG MAP_REGEX MILTER MIME7TO8 MIME8TO7
                                      ↑
                                    note

If it doesn’t appear, you will need to either download the sendmail source and build it yourself, or contact your operating system vendor and request a properly compiled version in binary form.

Create libmilter

The libmilter Milter library is not automatically built when you build sendmail. If you wish to build and install it you must do so manually.[444] Note that you do not need to define the -DMILTER Build macro to build the library, but including it does not hurt.

First build sendmail in your usual manner. Then cd into the libmilter directory and build again there:

% ./Build -c -f ../../mybulid.m4
... lots of output here
% cd libmilter
% ./Build
... lots of output here

Here, a number of Build-time switches were specified to build sendmail. Recall (Build sendmail on page 53) that those switches create a Makefile, and thereafter are no longer needed. That is why a bare ./Build command can be used in the libmilter directory.

After the libmilter is built, you must install it. The place where it is installed, and the permissions given to it, are defined by the various confLIB... Build macros (confLIB... on page 81). By default, libmilter will be installed in /usr/lib, so the install command must be run by root:

# ./Build install
... lots of output here

The library file, libmilter.a, is installed by default in the /usr/lib directory. Two corresponding #include files, mfapi.h and mfdef.h, are installed by default in the /usr/include/libmilter directory. No Unix manual pages are installed. Instead, you must read HTML files located under the sendmail source tree, in libmilter/docs, to learn how to use this library.

Special Build-Time Support

You may either Build the libmilter library in its plain-vanilla form, or tune it to better support your environment. Table 26-2 lists the Build-time macros that tune the libmilter library. Note that some macros change the Milter library, whereas others change sendmail.

Table 26-2. Macros to tune how sendmail and libmilter are built

Macro

§

Means

SM_CONF_POLL

SM_CONF_POLL on page 1172

Use poll(2) instead of select(2) in the Milter library (V8.13 and later).

MILTER_NO_NAGLE

MILTER_NO_NAGLE on page 1172

Turn off Nagle algorithm with Milters inside sendmail (V8.14 and later).

SM_CONF_POLL

Use poll(2) instead of select(2) (V8.13 and later) Tune with confENVDEF

By default, the Milter library uses select(2) to determine whether I/O is present on any given Milter connection. This is sufficient at low-volume sites. But at sites that run many Milters or high numbers of parallel connections to them, poll(2) will prove more efficient. To switch your Milter library from use of select(2) to use of poll(2) define the following and then rebuild your Milter library:

APPENDDEF(`conf_libmilter_ENVDEF', `-DSM_CONF_POLL' )

MILTER_NO_NAGLE

Turn off Nagle algorithm with Milters Tune with confENVDEF

Named for its creator, John Nagle, the Nagle algorithm is used to automatically concatenate a number of small network messages together and then transmit them together. This process (called nagling) increases the efficiency of a network application system by decreasing the total number of packets that must be sent for the same data. The Nagle algorithm is sometimes considered undesirable for use in interactive environments, such as with some client/server situations like sendmail-to-Milter communications. In such cases, nagling may be turned off by defining the TCP_NODELAY sockets option.

If you wish to turn off nagling for sendmail’s communication with its Milters, you may do so by defining the following, and then rebuilding sendmail:

APPENDDEF(`conf_sendmail_ENVDEF', `-DMILTER_NO_NAGLE=1')

By default, nagling is turned on for communication with Milters because turning it off does not improve performance on all operating systems.

Add Configuration Support

The sendmail program won’t use Milters unless you configure it to do so. Each Milter you use must be declared using the sendmail X configuration command. After that, other configuration commands define the order in which Milters are called (The InputMailFilters Option on page 1177) or associate each Milter with a particular listening daemon (DaemonPortOptions=InputFilter= on page 1178).

The X Configuration Command

When the MILTER Build-time macro is enabled, sendmail offers a way to submit messages to external programs that can be used to screen messages for spam indicators, viruses, or other content that you might want to reject, defer, or quarantine. At the end of this chapter, we will show you the library routines to use for making these decisions. Here, we discuss the hooks inside the configuration file that allow sendmail to exchange information with external programs.

External programs are defined for use with the X configuration file command. The form for that command looks like this:

Xname, equates ...cf file
INPUT_MAIL_FILTER(`name', `equates ...')   ← mc file
MAIL_FILTER(`name', `equates ...')         ← mc file

The X in the first line, like all configuration commands, must begin a line. It is immediately followed by the name you will assign to the external Milter program, with no intervening spaces. That name is for sendmail’s use only, and does not need to be the actual name of the program. The name is followed by a comma. If you accidentally prefix the name with a space (in the cf or mc form), or omit the name, the following error will print and the sendmail program will exit:

cf file: line number name required for mail filter

The equates is a sequence of comma-separated expressions that are formed by a key letter, an equals sign, and a value:

key-letter=value

The recognized key letters and their meanings are shown in Table 26-3.

Table 26-3. X configuration command key letters

Key letter

Description

F

Controlling flags

S

Description of the socket to use

T

The timeouts to impose on the connection

For example, the following three mc file lines define three possible external Milter program hooks:

INPUT_MAIL_FILTER(`progA', ``S=local:/var/run/f1.sock, F=R'')
INPUT_MAIL_FILTER(`progB', ``S=inet6:999@localhost, F=T, T=S:1s;R:1s;E:5m'')
INPUT_MAIL_FILTER(`progC', `S=inet:3333@localhost')

The first example shows how to attach to a Unix-domain socket in the /var/run directory. The second example shows how to connect to an IPv6 socket on port 999 of the local host. The third example shows how to connect to an IPv4 socket on port 3333 of the local host. We will describe each equate in detail in the following three sections, but first, the following details should be noted.

If any argument contains commas, such as the first two in the preceding code, that argument must be surrounded by two single quotes.

If the = is missing from an equate, the following error is printed and sendmail exits:

cf file: line number Xname  `=' expected

If the key letter prefixing the = character is not one of the three shown in Table 26-3 on page 1173, the following error is printed and sendmail exits:

cf file: line number Xname  unknown filter equate badequate=

Note that the three external Milter programs will be used in the order declared. First, progA will be contacted on a Unix-domain socket. If it accepts the message, progB will be contacted on a network socket. If progB accepts the message, progC will be given the final crack at the message. When a socket allows it, some of these connections might be in parallel prior to header processing, but will always be in sequence when header processing begins. See Table 26-5 on page 1177 for a more detailed overview of this process.

If you want to declare external programs, but don’t want to set the order in which they are called, use the MAIL_FILTER mc macro instead:

MAIL_FILTER(`progA', ``S=local:/var/run/f1.sock, F=R'')
MAIL_FILTER(`progB', ``S=inet6:999@localhost, F=T, T=S:1s;R:1s;E:5m'')
MAIL_FILTER(`progC', `S=inet:3333@localhost')

This is the same declaration as before, except that it omits the declaration of the order in which the program sockets will be called. When using this form, you will have to separately declare the order with the InputMailFilters option (The InputMailFilters Option on page 1177):

define(`confINPUT_MAIL_FILTERS', ``progB, progA, progC'')

Note that if pre-V8.13 sendmail was not compiled with -DMILTER,[445] attempting to declare a socket with these commands will cause the following error to be printed, and sendmail will exit:

Warning: Filter usage ('X') requires Milter support (-DMILTER)

The X configuration command F= equate

The F= equate, which stands for “Flags,” can cause a message to be rejected or tempfailed if the connection to the socket fails or if the filter program gives a nonstandard response. If you want the message rejected on failure, specify the letter R in the equate. The R stands for “reject.” If you want the message to be temp-failed, use the letter T, which stands for “temporary failure”:

F=R        ← reject SMTP commands if the filter is unavailable or if it has an error
F=T        ← temp-fail SMTP commands if the filter is unavailable or if it has an
error
F=4         ← reject with 421 and close if the filter is unavailable or if it has an
error (V8.14 and later)

If any character other than R, T, or 4 is specified following the F=, or if the F= equate is missing, and if there was an error contacting, or a communication error while in contact with the Milter, the message is passed through sendmail as though the entire X configuration-file command were omitted, or as though the socket could not be contacted. When a Milter is successfully contacted, and when all communication with it works, the F= does not apply.

The X configuration command S= equate

The S= equate, which stands for “Socket,” is mandatory and can be used to specify the type of socket:

local        ← a Unix-domain socket
unix         ← synonym for local
inet         ← an IPv4 network socket
inet6        ← an IPv6 network socket

If you use a socket type other than one of those listed, the following error will print and sendmail will exit:

cf file: line number Xname  unknown socket type type: Protocol not supported

The format for the S= equate looks like this:

S=type:specification

The type is one of the three main types shown earlier. The colon is literal and must be present. The specification is particular to each type. For the local (or unix) type, the specification is the full pathname to a Unix-domain socket. For example:

S=local:/var/run/progA.soc

Note that the socket must not already exist for use by the Milter.[446] The Milter will automatically create a socket when one is needed. If the socket was created, it will be removed by the Milter on exit. Beginning with V8.13, the socket will not be removed if the Milter was run as, or by, root, even if the Milter created it on startup.

The inet and inet6-type sockets use a specification that is a port number, immediately followed by an @ character, which is again immediately followed by a host or address specification. For example:

S=inet:1099@localhost                    ← port 1099 on the local machine, using
IPv4
S=inet:1099@host.your.domain             ← port 1099 on another machine on your
network,
                                               using IPv4
S=inet6:1099@localhost                   ← port 1099 on the local machine, using
IPv6
S=inet:1099@123.45.67.89                 ← port 1099 at IPv4 number 123.45.67.89
S=inet6:1099@2002:c0a8:51d2::23f4        ← port 1099 at IPv6 number
2002:c0a8:51d2::23f4

As we have seen in the previous section, the F= equate determines what will happen to a message should the connection to a socket fail.

The X configuration command T= equate

There are four timeouts that can affect the use of an external program connected via a socket.[447] They are tunable in your configuration file. Table 26-4 shows all four timeouts, the key letter for each, and the default value for each.

Table 26-4. X configuration command T= letters

Key letter

Default

Description

E

5 minutes

Overall timeout from sending EOM to Milter to final EOM reply

R

10 seconds

Timeout for reading reply from the Milter

S

10 seconds

Timeout for sending information from the MTA to a Milter

C

5 minutes

Connection timeout

The form for each key letter looks like this:

letter:value

Space can surround the colon. If you specify more than one key letter with a value, you must separate each from the other with a semicolon. Again, space can surround each semicolon:

letter:value;letter :value

For example, the following code sets a timeout of 600 seconds for the connection to the socket, and 20 seconds for reads and writes:

T=C:600s; R:20s; S:20s

The letter s following each number stands for seconds. Instead, you can choose to use the letter m, which stands for minutes. The letters h for hours, d for days, and w for weeks are also available, but they don’t make sense for use with this equate.

Note that for the C: key letter, if you set the value to zero, the default timeout for the connect(2) system call will be used. See your system documentation to determine that default. Also note that any C: setting above the operating system’s default connection timeout will cause the C: setting to be ignored (the operating system’s limit, in such an instance, will always happen first).

The InputMailFilters Option

Filters to connect to for processing messages through external programs are declared with the X configuration command (The X Configuration Command on page 1173). One form of that command (for use in your mc file) not only declares the Milter, but also defines the order in which the Milters will be called:

INPUT_MAIL_FILTER(`progA', ``S=local:/var/run/f1.sock, F=R'')
INPUT_MAIL_FILTER(`progB', ``S=inet6:999@localhost, F=T, T=S:1s;R:1s;E:5m'')

Here, the Milters will be called in the order progA first and progB second, for each phase of the message. Table 26-5 shows which portion of the message is checked by each Milter in time order. Note the change in order when the DATA phase begins (header/body).

Table 26-5. Milters called in time order

Milter

Screens what

progA

Connection information, such as hostname and IP address

progB

Connection information, such as hostname and IP address

progA

HELO/EHLO greeting information

progB

HELO/EHLO greeting information

progA

MAIL From: address and ESMTP arguments

progB

MAIL From: address and ESMTP arguments

progA

RCPT To: address and ESMTP arguments

progB

RCPT To: address and ESMTP arguments

progA

DATA command (V8.14 and later)

progB

DATA command (V8.14 and later)

progA

The message headers

progA

The message body

progA

The end of a message (a semaphore)

progB

The message headers

progB

The message body

progB

The end of a message (a semaphore)

Each Milter is handed portions of a message envelope and body, in phases. For each phase, the Milter can advise sendmail of one decision among several possible decisions, to accept, reject, temp-fail, or quarantine.

Milters can also be declared with the MAIL_FILTER mc macro, but it does not set the order:

MAIL_FILTER(`progA', ``S=local:/var/run/f1.sock, F=R'')
MAIL_FILTER(`progB', ``S=inet6:999@localhost, F=T, T=S:1s;R:1s;E:5m'')

When the order is not set, or when it is set but you wish to change it, you can use the InputMailFilters option. It defines the order for calling Milters:

O InputMailFilters=progB, progA                        ← cf file
define(`confINPUT_MAIL_FILTERS', ``progB, progA'')     ← mc file

Here, the InputMailFilters option defines the order that the Milters will be called to be the reverse of what was defined with the MAIL_FILTER mc command.

If you fail to define an order for the Milters, no Milters will be called, and no message screening will happen.

If your version of sendmail is pre-V8.13 and was not compiled with -DMILTER defined and you declare this option, you will get the following error, and sendmail will exit:

Warning: Option: InputMailFilters requires Milter support (-DMILTER)

If you list more than the number of Milters permitted by MAXFILTERS (which defaults to 25), the following error will print and the extra Milters will be ignored:

Too many filters defined, 25 max

If you misspell one of the Milter names, the following error will print and that Milter will be ignored:

InputFilter probB not defined

Note that all external programs and Milters are connected to when the message is received via SMTP (either over the network or with the -bs command-line switch). None is called just before the message is transmitted. Such output filtering might appear in a future release of sendmail.

DaemonPortOptions=InputFilter=

The sendmail program can run in two connection modes: as a daemon, accepting connections; or as a client, making connections. Each mode connects to a port to do its work. The tuning for the client port is set by the ClientPortOptions option (ClientPortOptions on page 986). The tuning for the daemon is set by the DaemonPortOptions option (DaemonPortOptions on page 993). The format for declaring the DaemonPortOptions option in the mc configuration file looks like this:

DAEMON_OPTIONS(``pair,pair,pair'')

The list of pair items must be enclosed in two pairs of single quotes pairs because the list contains commas. Each pair is an equate of the form:

item=value

The new (as of V8.13) InputMailFilters= equate is used to list the Milters that should be called, and the order in which they must be called. This list overrides the setting of the InputMailFilters option and, indeed, may contain Milters not declared in that option. This InputMailFilters= equate lists one or more Milters, each separated from the next by a semicolon (not a comma):

DAEMON_OPTIONS(``N=inMTA, I=milterA;milterB'')

Note, as with all DaemonPortOptions option items, the first character of each is all that is needed. That is, both of the following produce the same effect:

I=milterA;milterB
InputMailFilters=milterA;milterB

This item can be useful when you have multiple network interfaces. One interface, for example, might be connected only to the internal network where a Milter records all outbound email. Another might be connected to the external network where a Milter can screen for viruses and spam email.

The SuperSafe Option with Milters

Beginning with V8.13, a forth option was introduced that is useful when Milters reject a great deal of mail. The SuperSafe option accepts a PostMilter setting (SuperSafe on page 1096) which delays the fsync()ing of the df file until after all Milters have reviewed the message. You use it like this:

define(`confSAFE_QUEUE',   `PostMilter')    ← V8.13 and later

In general, this setting should be reserved for sites that screen huge amounts of email. Any relaxation of the SuperSafe option creates the risk that mail can be lost should the machine fail or lose power.

Root Won’t Remove Socket File

When a Milter shuts down, it automatically removes any Unix domain socket that was used as the communication port. The communication port is set with the smfi_setconn() Milter library routine. If the argument to that routine begins with "unix:" or "local:" the path listed following that prefix defines the Unix domain socket to use.

Beginning with V8.13, if the Milter is being run by, or as, root, the Milter library will refuse to remove a Unix domain socket.

Milter Logging with syslog

The Milter library performs no logging. If you wish to have the activities of your Milter logged, you must include that support into the Milter you create.

The sendmail program, on the other hand, does have the ability to log its interaction with Milters. That logging is enabled and its volume tuned using the Milter.LogLevel option (Milter.LogLevel on page 1053). It is declared like this:

O Milter.loglevel=levelconfiguration file
-OMilter.loglevel=levelcommand line
define(`confMILTER_LOG_LEVEL',`level')    ← mc configuration

Here, level is an integer that determines what and how much will be logged. In general, levels less than 10 are logged at LOG_ERR, and those greater than 10 are logged at LOG_INFO. A level of 0 disables logging. Table 26-6 shows the currently defined levels and what will be logged at each level. Note that each level also logs the information that is logged at the levels below it.

Table 26-6. Milter.LogLevel option settings

Milter.LogLevel

Screens what

1

Bad reply codes from the external program, socket errors, timeouts, and errors generally.

9

Added or deleted a header or RCPT To: response, replaced message body, etc. This is the default level..

10

Connection information.

11

Reply rejects, temp-fails, and deferrals.

14

Reply codes.

15

Milter senders, and Milter recipients.

18

Headers sent, and body sent.

22

Time to complete a command.

Pass Macros with Milter.macros

Individual sendmail macros may be sent to your Milter during nearly any phase of the SMTP transaction. Table 26-7 shows the individual options available for sending macros.

Table 26-7. Options to have sendmail macro values sent to a Milter

mc option

Configuration option

§

confMILTER_MACROS_CONNECT

Milter.macros.connect

Milter.macros.connect on page 1054

confMILTER_MACROS_ENVFROM

Milter.macros.envfrom

Milter.macros.envfrom on page 1054

confMILTER_MACROS_ENVRCPT

Milter.macros.envrcpt

Milter.macros.envrcpt on page 1055

confMILTER_MACROS_DATA

Milter.macros.data

Milter.macros.data on page 1055

confMILTER_MACROS_EOH

Milter.macros.eoh

Milter.macros.eoh on page 1056

confMILTER_MACROS_EOM

Milter.macros.eom

Milter.macros.eom on page 1056

confMILTER_MACROS_HELO

Milter.macros.helo

Milter.macros.helo on page 1054

Two steps are required for you to set up a macro for use with your Milter. First you declare your intention inside your mc (or configuration) file with a line like the following:

define(`confMILTER_MACROS_HELO',``{client_addr}, {client_name}'')

This tells sendmail you want the value of the ${client_addr} macro (${client_addr} on page 810) and the value of the ${client_name} macro (${client_name} on page 812) sent to the xxfi_helo() handler function (Milter xxfi_helo() on page 1218) inside your Milter.

Second, you arrange inside your Milter for your handler function (here your xxfi_helo() handler) to receive (request) those macro values when you need them. Milters can use sendmail macros and access those macros using this smfi_getsymval() routine (Milter smfi_getsymval() on page 1190). It is used like this:

symval = smfi_getsymval(ctx, symname);

For example, inside your xxfi_helo() handler function you might use the following two lines of code:

addr_val = smfi_getsymval(ctx, "{client_addr}");
name_val = smfi_getsymval(ctx, "{client_name}");

Note, however, that you are not required to fetch those macros just because you stated you wanted them. You can fetch the value of one or the other or neither or all, as you wish.

Build a Milter

A Milter is a program that listens on a socket. It receives each email message interactively on that socket from sendmail and receives each message in pieces. The sendmail program first offers the connection information, and the Milter can take it for review or decline it. If it accepts, it will screen that information and either reject the message based on its review or allow the message. Then the next piece of the message is offered and reviewed in the same manner. The order of the review is:

Connect

Review based on the IP address and hostname of the connecting site

Greeting

Review based on the hostname given as part of the SMTP HELO or EHLO command

Sender

Review the envelope sender as supplied as part of the SMTP MAIL From: command

Recipient

Review the envelope recipient as supplied as part of the SMTP RCPT To: command

Headers

Review the header portion of the email message

Data

Review the SMTP DATA command

EOH

Signals the end of the header portion of the message

Body

Review the message body, which can include MIME-encoded portions

EOM

Signals the end of the body portion of the message

The program must quickly (within the timeouts defined by the X configuration command) parse the message pieces and decide whether the message should be accepted or rejected. The program then advises sendmail of its decision, using the libmilter API.

The sendmail source distribution includes a library and sample program that you should use to create your own Milter program. Look in the directory libmilter. It contains the source for the library, a README file with the latest information, and a libmilter/docs subdirectory that contains all the documentation you will need in HTML format.

We recommend you build your Milter program using the supplied library. Don’t dig through the source to divine the current protocol, because that protocol will evolve from version to version. Instead, use the API provided by the library.

If you wish to write your own Milter, we recommend:

http://spambook.bcx.org/

sendmail Milters: A Guide for Fighting Spam—The complete guide to writing and creating Milters for use in spam and phishing suppression (only covers V8.13 and earlier).

If you don’t wish to write your own Milter program, consider the following:

http://www.milter.org/

A guide to and discussions about MILTERs in general.

http://mailbox.univie.ac.at/~at/vilter/

The vilter program scans incoming email and rejects or flags the infected messages with a header line.

http://www.amavis.org/

The amavis program is a mail virus scanner.

http://aeschi.ch.eu.org/milter/

The vbsfilter program will rename a variety of executable attachments to .txt, thus rendering them harmless.

http://sendmail.com/

Sendmail, Inc. offers several commercial Milters.

Pitfalls

  • If any Milter in a list of Milters returns reject, none of the Milters that follow it will be given a chance to accept the message. This can make a multi-Milter design tricky.

  • The meaning of SMFI_VERSION changed with V8.14. Any Milter written before the change that gives the old value to the version part of the struct smfiDesc initialization structure (Milter smfi_register() on page 1194) may fail to run if that Milter links against a vendor’s older dynamic library. Note that this has been fixed as of V8.14.2 and a patch is available for V8.14.1:

    http://www.sendmail.org/patches/libmilter.8142.p0
  • The order in which Milters are called is defined by your sendmail configuration file. Be aware that changes in the configuration file can change the order in which Milters are called. This is important because, in the event that a Milter’s position changes, there is no way for that Milter to know it. Even if you set the order in the configuration file, neither assume it will remain in the correct order forever nor build that assumption into your Milter code.

  • If a Milter declares SMFIP_RCPT_REJ as part of xxfi_negotiate() (Milter xxfi_negotiate() on page 1220) with the intention of reviewing rejected recipients, it will not see recipients rejected by other Milters. This makes sense because otherwise, an already called Milter might have to be called again for a recipient if a later Milter rejected that recipient. Remember that SMFIP_RCPT_REJ only causes recipients rejected for non-syntactic reasons at the RCPT To: command to be sent to the Milter.

smfi_ Routine Reference

A Milter is composed of two kinds of function calls. Those that you write are called the xxfi_ routines and are described in the next section. Those that your written functions call in the Milter library are the smfi_ routines, described here. The complete list of smfi_ routines is shown in Table 26-8, which is followed by a brief reference section for each routine.

Table 26-8. Milter library smfi_routines

smfi routine

§

Description

smfi_addheader()

Milter smfi_addheader() on page 1184

Conditionally add a header.

smfi_addrcpt()

Milter smfi_addrcpt() on page 1185

Add envelope recipient.

smfi_addrcpt_par()

Milter smfi_addrcpt_par() on page 1186

Add envelope recipient with ESMTP arguments (V8.14 and later).

smfi_chgfrom()

Milter smfi_chgfrom() on page 1187

Change envelope sender with ESMTP arguments (V8.14 and later).

smfi_chgheader()

Milter smfi_chgheader() on page 1188

Change or delete a header.

smfi_delrcpt()

Milter smfi_delrcpt() on page 1189

Delete envelope recipient.

smfi_getpriv()

Milter smfi_getpriv() on page 1189

Fetch private context data pointer.

smfi_getsymval()

Milter smfi_getsymval() on page 1190

Fetch a sendmail macro’s value.

smfi_insheader()

Milter smfi_insheader() on page 1192

Unconditionally insert a header (V8.13 and later).

smfi_main()

Milter smfi_main() on page 1193

Run the Milter.

smfi_opensocket()

Milter smfi_opensocket() on page 1193

Create the interface socket (V8.13 and above).

smfi_progress()

Milter smfi_progress() on page 1193

Report operation in progress (V8.13 and above).

smfi_quarantine()

Milter smfi_quarantine() on page 1194

Quarantine a message (V8.13 and above).

smfi_register()

Milter smfi_register() on page 1194

Declare which xxfi function to call for which phase and set flags.

smfi_replacebody()

Milter smfi_replacebody() on page 1196

Replace message body.

smfi_setbacklog()

Milter smfi_setbacklog() on page 1197

Set the listen(2) queue size (V8.13 and above).

smfi_setconn()

Milter smfi_setconn() on page 1197

Specify the interface socket to use.

smfi_setdbg()

Milter smfi_setdbg() on page 1198

Set the debugging level (V8.13 and above).

smfi_setmlreply()

Milter smfi_setmlreply() on page 1198

Set a multiline SMTP reply code (V8.13 and above).

smfi_setpriv()

Milter smfi_setpriv() on page 1199

Initialize private context data pointer.

smfi_setreply()

Milter smfi_setreply() on page 1200

Set SMTP code and reply text.

smfi_setsymlist()

Milter smfi_setsymlist() on page 1201

Request macros to be sent (V8.14 and later).

smfi_settimeout()

Milter smfi_settimeout() on page 1202

Set sendmail to Milter time out.

smfi_stop()

Milter smfi_stop() on page 1202

Cause a controlled shutdown (V8.13 and above).

smfi_version()

Milter smfi_version() on page 1203

Fetch version of the runtime library (V8.14 and later).

Milter smfi_addheader()

Conditionally insert a header All sendmail versions

To add a header to the existing headers in a message you may use either this smfi_addheader() routine or the smfi_insheader() routine (Milter smfi_insheader() on page 1192). This routine is conditional in that it will replace some headers and insert others, whereas the smfi_insheader() routine is unconditional and always inserts headers. With this smfi_addheader() special logic inside sendmail scans headers to see whether the new header name already exists. If that header name exists, and if that header is not a trace header (such as Received:), and if that header is not an X- header nor one added by another Milter, sendmail will silently replace the existing named header’s value with the new value, rather than adding the new header.[448]

Before you may add headers, you must first declare your intention to do so by including the SMFIF_ADDHDRS flag to the flags portion of the smfiDesc structure:

struct smfiDesc smfilter =
{
    ...
    SMFIF_ADDHDRS,/* flags */
    ...

Failure to include this flag causes smfi_addheader() to return MI_FAILURE every time it is called.

You add headers to a message by calling this smfi_addheader() routine from inside your xxfi_eom() function (Milter xxfi_eom() on page 1215):

ret = smfi_addheader(ctx, name, value);

The first argument is the usual ctx connection-context pointer. It is the same ctx pointer that was passed to the enclosing xxfi_eom() function. It may not be NULL and must be a valid pointer.

The second argument (the name) is a string that contains the name for the header to insert. Header names must conform to RFC standards. The Milter library performs no standards checking, so you must ensure that no standards are violated. Note that whatever capitalization you choose is preserved. If the header name is NULL, or if it is an empty string, smfi_addheader() will return MI_FAILURE.

The third argument (the value) is the value for the header in the form of a string. The value must not be NULL but may be an empty string, in which instance the header will be inserted with no value.

The string containing the value should be fewer than 998 characters. If the value is too long, sendmail may silently truncate it. If you need to extend the value over multiple lines, you may do so by inserting newline characters, each followed by a space or tab. For example:

"Spamfilter status\n\tImages=0\n\tIsHTML=NO"

Do not use carriage-return/linefeed pairs here. When needed, those pairs will later be added by sendmail.

When later viewed by the message recipient, the preceding value might look like this:

X-Spamfilter: Spamfilter status
    Images=0
    IsHTML=NO

If the sendmail configuration file’s Milter.LogLevel option (Milter.LogLevel on page 1053) has a value of eight or less, nothing is logged. Otherwise, if an existing header had its value changed, the following will be logged:

Milter change: default header existing value with newvalue

Or, if a new header was added, the following message will be logged:

Milter add: header: name: value

Note that the current Milter may not have the opportunity to add a header if a prior Milter has rejected the message. Therefore, never use a custom-added header with the expectation that it could convey information to subsequent Milters.

Milter smfi_addrcpt()

Add an envelope recipient All sendmail versions

The smfi_addrcpt() Milter library routine is used to add an envelope recipient to the envelope. To remove an envelope recipient use smfi_delrcpt() (Milter smfi_delrcpt() on page 1189). To include ESMTP arguments along with the new recipient use smfi_addrcpt_par() (Milter smfi_addrcpt_par() on page 1186).

Before you can add recipients, you first need to declare your intention to do so by including the SMFIF_ADDRCPT flag in the flags portion of the smfiDesc structure:

struct smfiDesc smfilter =
{
    ...
    SMFIF_ADDRCPT,                  /* flags */
    ...

Failure to include this flag causes smfi_addrcpt() to return MI_FAILURE every time it is called.

The smfi_addrcpt() routine may be called only from within an xxfi_eom() function you write (Milter xxfi_eom() on page 1215). It is called like this:

ret = smfi_addrcpt(ctx, addr);

Here, ctx is the common context pointer that was passed to your xxfi_eom() function. The addr is the email address of the recipient you wish to add. On success, MI_SUCCESS will be returned (to ret). MI_FAILURE will be returned if anything went wrong.

The addr must be in the form of a string composed of a user part and a host part separated by an @ character:

"user@example.com"

Local addresses may omit the @ and the domain part:

"user"

The new address is added by sendmail. If there is a problem with the address, the problem will be completely handled by sendmail and your Milter will not be notified. You may enclose the address in angle braces with no change in effect.

"<user@example.com>"            ← okay too

Milter smfi_addrcpt_par()

Add envelope recipient with ESMTP arguments V8.14 and later

The smfi_addrcpt_par() Milter library routine is used just like the smfi_addrcpt() routine earlier, with two differences. First, instead of specifying SMFIF_ADDRCPT, you specify the SMFIF_ADDRCPT_PAR flag in the flags portion of the smfiDesc structure:

struct smfiDesc smfilter =
{
    ...
    SMFIF_ADDRCPT_PAR,                 /* flags */
    ...

Failure to include this flag causes smfi_addrcpt_par() to return MI_FAILURE every time it is called.

Like smfi_addrcpt(), this smfi_addrcpt_par() routine may be called only from within an xxfi_eom() function you write (Milter xxfi_eom() on page 1215). It is called with an additional argument:

ret = smfi_addrcpt_par(ctx, addr, args);

Here, ctx is the common context pointer that was passed to your xxfi_eom() function. The addr is the email address of the recipient you wish to add. The additional args specifies any ESMTP envelope recipient arguments you wish to add. For example, the following (see ${dsn_notify} on page 821 for an explanation) specifies that the envelope recipient should not be notified of delivery failure or delivery delay:

"NOTIFY=NEVER"

No check is made by sendmail to ensure that the ESMTP extension you add is legal. Be aware that if you make a mistake, delivery may fail:

RCPT To:<user@example.com> NOTIFY=NONE
501 5.5.4 Bad argument "NONE"  to NOTIFY

The sendmail program checks only to be sure the argument is properly formed.

Milter smfi_chgfrom()

Change envelope sender with ESMTP arguments V8.14 and later

The smfi_chgfrom() Milter library routine is used to change the envelope sender address (the address given with the MAIL From: SMTP command). Recall that the envelope sender is the address to which bounced email will be sent, and might also be the address used for TLS authentication.

Before you can use this smfi_chfrom() routine, you must notify the Milter library that you intend to do so by adding the SMFIF_CHGFROM flag to the flags portion of the smfiDesc structure:

struct smfiDesc smfilter =
{
    ...
    SMFIF_CHGFROM,                  /* flags */
    ...

Failure to include this flag causes smfi_chgfrom() to return MI_FAILURE every time it is called.

This smfi_chfrom() routine may only be called from inside an xxfi_eom() function (Milter xxfi_eom() on page 1215) you write yourself. It is called like this:

ret = smfi_chfrom(ctx, addr, args);

Here, ctx is the common context pointer that was passed to your xxfi_eom() function. The addr is the email address of the sender which will replace the original sender. The additional args specifies any ESMTP envelope sender arguments you wish to add.

If addr is NULL, smfi_chgfrom() will return MI_FAILURE. Otherwise, the address you specify must be a legal email address of the form user, a literal @ character, and then a canonical hostname:

user@example.com

The addr may optionally be surrounded in angle braces. If you omit them, sendmail will add them for you:

<user@example.com>

The args is optional. If it is NULL, no ESMTP arguments will be added. Otherwise, it must be a string containing the ESMTP arguments to add, for example:

ENVID=1234

Here we set the envelope ID (${dsn_envid} on page 820) to be 1234. Note, however, that the SIZE and BODY ESMTP arguments should not be used because they may confuse delivery. Also note that the ESMTP argument you add will be added to any that already exist.

The Milter library does not screen for legal values. They are passed as is to sendmail which only checks to see whether they are syntactically correct and rejects them if they are not.

Milter smfi_chgheader()

Change and remove headers All sendmail versions

The smfi_chgheader() Milter library routine is used to change the value of existing headers and to remove headers. To conditionally add headers use smfi_addheader() (Milter smfi_addheader() on page 1184). To unconditionally add headers use smfi_insheader() (Milter smfi_insheader() on page 1192).

Before you can modify header values, you first need to declare your intent to do so by including the SMFIF_CHGHDRS flag in the flags portion of the smfiDesc structure:

struct smfiDesc smfilter =
{
    ...
    SMFIF_CHGHDRS,                  /* flags */
    ...

Failure to include this flag causes smfi_chgheader() to return MI_FAILURE every time it is called.

The smfi_chgheader() routine may be called only from within the xxfi_eom() function you write (Milter xxfi_eom() on page 1215). It is called like this:

ret = smfi_chgheader(ctx, name, index, value);

Here, ctx is the common context pointer that was passed to your xxfi_eom() function. The name is the name of the header whose value you wish to change, and value is the new value you wish to assign to that header. On success, MI_SUCCESS will be returned (to ret) or MI_FAILURE will be returned if anything went wrong.

If value is set to NULL, the header will be removed.

The index is a count (not an offset) and must be greater than zero. The index should normally be set to one in order to change the value of the first occurrence of a header with the name. Some header names can appear multiple times, however (the Received: header is one), and when they do, you may set index to a count that changes the value of a particular occurrence of that header. But note that if the index is greater than the number of headers with that name, a new header will be silently created.

When you change a header’s value, the new value must be shorter than 2,048 characters and should be shorter than 998 characters. An overly long value will be silently truncated. A value may be made to span multiple lines in a message by inserting newline characters and spaces or tabs into the value, as for example:

"Results:\n\tWasHTML=TRUE\n\tNumAttachments=0"

But note that carriage-return/newline pairs must not be used (not \r\n) because illegal headers may result.

Milter smfi_delrcpt()

Remove an envelope recipient All sendmail versions

The smfi_delrcpt() Milter library routine is used to remove an envelope recipient from the envelope. To add an envelope recipient use smfi_addrcpt() (Milter smfi_addrcpt() on page 1185).

Before you can remove any recipients, you first need to declare your intention to do so by including the SMFIF_DELRCPT flag in the flags portion of the smfiDesc structure:

struct smfiDesc smfilter =
{
    ...
    SMFIF_DELRCPT,                  /* flags */
    ...

Failure to include this flag causes smfi_delrcpt() to return MI_FAILURE every time it is called.

The smfi_delrcpt() routine may be called only from within an xxfi_eom() function you write (Milter xxfi_eom() on page 1215). It is called like this:

ret = smfi_delrcpt(ctx, addr);

Here, ctx is the common context pointer that was passed to your xxfi_eom() function. The addr is the email address of the recipient you wish to delete. On success, MI_SUCCESS will be returned (to ret). MI_FAILURE will be returned if anything went wrong. But note that you are only suggesting to sendmail that it should remove the recipient. Note too that even if the recipient doesn’t exist in sendmail’s list of addresses, the call to smfi_delrcpt() will still succeed.

The addr must be in the form of a string composed of a user part and a host part separated by an @ character. The entire address must be surrounded in angle braces:

<user@example.com>

For an address to be removed, it must exactly match an address in sendmail’s recipient list. The best way to ensure that match is by saving each address passed to the xxfi_envrcpt() function (Milter xxfi_envrcpt() on page 1213) you write, and by passing one of those exact addresses to this smfi_delrcpt() routine. But note that despite the fact that rule sets and aliasing may have modified a recipient address between the time your xxfi_envrcpt() function first saw it and the time you later wish to delete it, sendmail ensures that your xxfi_eom() function will have access to the original addresses because they were saved during your Milter’s xxfi_envrcpt() processing. Thus, your Milter can safely ignore any risk from rule set and aliasing changes.

Also note that just because your Milter deleted a recipient, nothing prevents a later Milter from adding it back in.

Milter smfi_getpriv()

Fetch private data pointer All sendmail versions

The smfi_setpriv() routine (Milter smfi_setpriv() on page 1199) allows you to set aside and save private data on a per-context basis. From inside any of the xxfi_ routines you write, you may call smfi_getpriv() to fetch a pointer to the private data you earlier saved. You fetch private data like this:

dataptr = (type *)smfi_getpriv(ctx);

The smfi_getpriv() routine’s only argument is the common context pointer ctx passed to the xxfi_ function you write. The value returned (and here stored in dataptr) is a pointer. The smfi_getpriv() routine is of type void *, so you need to cast the return value to a type that matches your saved datatype. If you failed to first set aside a pointer with the smfi_setpriv() routine, smfi_getpriv() will return NULL.

Be very careful with your use of saved data. Milters are multi-threaded and any shared data should be protected with mutexes. If you allocate and free data, be careful to always test the retuned value for NULL to avoid dereferencing a zero address.

Note too that each private data is bound to a single context (the ctx pointer) that is instantiated when each connection starts. Thus, it is best to think of such private data as per-connection private data.

Milter smfi_getsymval()

Fetch a sendmail macro’s value All sendmail versions

The sendmail program defines macros for your use in rule sets (such as the $j and ${mail_addr} macros) and allows you to define new macros for your own use. Milters can access sendmail macros using this smfi_getsymval() routine. It is used like this:

symval = smfi_getsymval(ctx, symname);

Here, ctx is the common context pointer that was passed to your xxfi_eom() function. The symname is the name of the macro whose value you seek and symval is the value (a string) returned by the function call. The symname is a string that specifies the name of a single macro. The $ prefix must be omitted. Multicharacter macro names must be enclosed in curly braces. Single-character names may optionally be surrounded in curly braces:

"${j}"                        ← Won't work, has leading $ character
"{j}"                        ← Good
"j"                        ← Also good
"{mail_host}"                        ← Good multicharacter name
"{mail_host} {mail_addr}"                        ← Won't work, multiple macro names

The returned symval is a pointer to a string that will be NULL if the macro name is undefined, if one is not sent to the Milter, if there was a network error, or if symname is NULL. If the macro’s name is found, symval will point into the Milter library context’s memory. Note that this value is volatile, so you should copy it if you need to preserve it.

In general, an envelope-specific macro is valid only for the current envelope, and a connection-specific macro is valid only for the current connection. That is, sendmail macros you fetch from within xxfi_connect() normally persist from the time the connection is initially established until the connection is closed. On the other hand, sendmail macros you fetch from within xxfi_envrcpt() will only persist for the duration of that single recipient. You should generally only fetch values for macros that are initially made available to the appropriate xxfi_ function. The default macros are shown in Table 26-9.

Table 26-9. Default macros passed by default to xxfi_ functions

Macro

Macro described

Function

Function described

no defaults

 

xxfi_data()

Milter xxfi_data() on page 1210

no defaults

 

xxfi_eoh()

Milter xxfi_eoh() on page 1214

_

$_ on page 801

xxfi_connect()

Milter xxfi_connect() on page 1209

{auth_authen}

${auth_authen} on page 804

xxfi_envfrom()

Milter xxfi_envfrom() on page 1211

{auth_author}

${auth_author} on page 805

xxfi_envfrom()

Milter xxfi_envfrom() on page 1211

{auth_ssf}

${auth_ssf} on page 806

xxfi_envfrom()

Milter xxfi_envfrom() on page 1211

{auth_type}

${auth_type} on page 806

xxfi_envfrom()

Milter xxfi_envfrom() on page 1211

{cert_issuer}

${cert_issuer} on page 809

xxfi_helo()

Milter xxfi_helo() on page 1218

{certr_subject}

${cert_subject} on page 809

xxfi_helo()

Milter xxfi_helo() on page 1218

{cipher}

${cipher} on page 809

xxfi_helo()

Milter xxfi_helo() on page 1218

{cipher_bits}

${cipher_bits} on page 810

xxfi_helo()

Milter xxfi_helo() on page 1218

{daemon_name}

${daemon_name} on page 819

xxfi_connect()

Milter xxfi_connect() on page 1209

i

$i on page 826

xxfi_envfrom()

Milter xxfi_envfrom() on page 1211

{if_addr}

${if_addr} on page 827

xxfi_connect()

Milter xxfi_connect() on page 1209

{if_name}

${if_name} on page 828

xxfi_connect()

Milter xxfi_connect() on page 1209

j

$j on page 830

xxfi_connect()

Milter xxfi_connect() on page 1209

{mail_addr}

${mail_addr} on page 833

xxfi_envfrom()

Milter xxfi_envfrom() on page 1211

{mail_host}

${mail_host} on page 833

xxfi_envfrom()

Milter xxfi_envfrom() on page 1211

{mail_mailer}

${mail_mailer} on page 834

xxfi_envfrom()

Milter xxfi_envfrom() on page 1211

{msg_id}

${msg_id} on page 834

xxfi_eom()

Milter xxfi_eom() on page 1215

{rcpt_addr}

${rcpt_addr} on page 842

xxfi_envrcpt()

Milter xxfi_envrcpt() on page 1213

{rcpt_host}

${rcpt_host} on page 843

xxfi_envrcpt()

Milter xxfi_envrcpt() on page 1213

{rcpt_mailer}

${rcpt_mailer} on page 843

xxfi_envrcpt()

Milter xxfi_envrcpt() on page 1213

{tls_version}

${tls_version} on page 847

xxfi_helo()

Milter xxfi_helo() on page 1218

If you wish to have other macros (or your own macros) passed to an xxfi_ function, you may do so by defining the appropriate mc macro in your configuration mc file. Each mc macro in Table 26-10 adds sendmail macros to the list of values passed to the corresponding xxfi_ function.

Table 26-10. Configuration mc macros to define passed macros

Option

§

confMILTER_MACROS_CONNECT

Milter.macros.connect on page 1054

confMILTER_MACROS_DATA

Milter.macros.data on page 1055

confMILTER_MACROS_ENVFROM

Milter.macros.envfrom on page 1054

confMILTER_MACROS_ENVRCPT

Milter.macros.envrcpt on page 1055

confMILTER_MACROS_EOH

Milter.macros.eoh on page 1056

confMILTER_MACROS_EOM

Milter.macros.eom on page 1056

confMILTER_MACROS_HELO

Milter.macros.helo on page 1054

Each mc macro adds sendmail macros to the list of values passed to the corresponding xxfi_ function. For example, the following adds the ${nbadrcpts} macro’s value (${nbadrcpts} on page 837) to the default list passed to the xxfi_eom() function:

define(`confMILTER_MACROS_EOM', confMILTER_MACROS_EOM``,{nbadrcpts}'')

Because each macro name is separated from the next by a comma, the entire list must be surrounded in two single quotes. Note that the following declaration is the same as the preceding one, because the ${msg_id} macro is the default sent to the xxfi_eom() function:

define(`confMILTER_MACROS_EOM', ``{msg_id},{nbadrcpts}'')

Milter smfi_insheader()

Unconditionally insert a header V8.13 and later

Prior to V8.13, the only way to add a header to the message was by using either the smfi_addheader() (Milter smfi_addheader() on page 1184) or the smfi_chgheader() (Milter smfi_chgheader() on page 1188).

The smfi_addheader() routine, however, had its limitations. Using its special logic, it examined existing header names to determine whether the new name already existed, and, if it was neither a trace header (such as Received:) nor an X- header, nor one added by another Milter, sendmail would silently replace that existing header’s value with the new value, rather than adding the new header.

Beginning with V8.13, the new smfi_insheader() routine allows you to unconditionally insert a new header, even if that header already exists in the message. But before you can use this new smfi_insheader() routine, you must add the SMFIF_ADDHDRS flag to the flags part of the your smfiDesc declaration:

struct smfiDesc smfilter =
{
          ...
    SMFIF_ADDHDRS,    /* flags */   ← add here

Omitting this flag will cause smfi_insheader() to fail.

The smfi_insheader() routine is used like this.

ret = smfi_insheader(ctx, index, name, value);

The smfi_insheader() routine’s first argument is a common context pointer, ctx. The next argument is index, an index into the list of existing headers. If index is zero, the new header will be added at the beginning of the list, before the first existing header. If index is greater than the number of existing headers, the new header will be inserted after the last header in the list. Otherwise, the new header will be inserted into the list of existing headers after the header indicated by the value of index.

The name is the name of the new header (such as X-MyMilter) and excludes the colon. The value is the field value of the new header. If either of these two arguments is NULL, smfi_insheader() will fail. Failures can result from memory allocation or network errors.

Note that neither sendmail nor the Milter library will ensure that your new header is a valid one. It is up to you to make sure the header you insert does not violate any RFCs. You should also make sure that it does not cause headers to no longer parse correctly.

If a new header was added, the following message will be logged if the sendmail configuration file’s Milter.LogLevel option (Milter.LogLevel on page 1053) has a value of nine or more:

Milter add: header: name: value

Note that the current Milter may not have the opportunity to add a header if a prior Milter has rejected the message. Therefore, never use a custom-added header with the expectation that it could convey information to subsequent Milters.

Milter smfi_main()

Run the Milter All sendmail versions

The smfi_main() routine starts Milter running and, if you have not already called the smfi_opensocket() routine (Milter smfi_opensocket() on page 1193), establishes the listening socket. The smfi_main() routine takes no argument and is called like this:

ret = smfi_main();

Here, the returned integer value ret will contain either MI_FAILURE if the Milter failed to start, or MI_SUCCESS if the Milter ran and exited normally. A Milter can fail to start up because it could not establish a listening socket, or because of a system or memory error. Usually, failure to start is logged with syslog().

Note that smfi_main() does not put your program into the background to run as a daemon. You need to write that code yourself.

The clean way to shut down your Milter is by calling the smfi_stop() routine (Milter smfi_stop() on page 1202).

Milter smfi_opensocket()

Actually set up the listening connection V8.13 and later

After you call smfi_setconn() (Milter smfi_setconn() on page 1197) to declare the socket on which the Milter will listen, you may call the smfi_opensocket() library routine which actually causes the Milter to set up that socket for listening. The smfi_opensocket() library routine is called like this:

ret = smfi_opensocket(flag);

Here, the flag tells the Milter to remove an existing Unix domain socket before creating a new one. If the flag is true (nonzero), the socket is removed; otherwise, it is not. If the socket is not a Unix domain socket, this flag has no effect.

Any error in opening the socket will return a value other than MI_SUCCESS. If that occurs, you should print or log the error and close down the Milter. Note that if you don’t use this new routine, the socket will still be opened automatically by the smfi_main() routine (Milter smfi_main() on page 1193).

Milter smfi_progress()

Buy a little extra time V8.13 and later

The smfi_progress() routine causes sendmail to reset its timeouts so that your end-of-message routine has plenty of time to finish.

If your end-of-message routine requires far too much time to finish and sometimes times out, you may call smfi_progress() to gain any extra time needed to finish. The single argument to smfi_progress() is the ctx pointer:

... a great deal of processing
smfi_progress(ctx);
... more time-consuming processing

In general, it is best to write your end-of-message routines to be super-swift, rather than requesting extra time from sendmail. When you request extra time, you risk that the connecting host will time out, causing all your work to be wasted.

Milter smfi_quarantine()

Quarantine a message V8.13 and later

V8.13 sendmail added a routine called smfi_quarantine() to the Milter library. It is used to quarantine (rather than to simply accept or reject) a message. Quarantining is described in Queue Quarantining on page 438.

This new routine may only be called from the xxfi_eom() (Milter xxfi_eom() on page 1215) end-of-message handling routine. But before you can use this smfi_quarantine() routine, you must declare your intention to do so by first adding the SMFIF_QUARANTINE flag to the flags part of the smfiDesc declaration:

struct smfiDesc smfilter =
{
    ...
    SMFIF_ADDHDRS|SMFIF_QUARANTINE,    /* flags */   ← add here

Note that the flags are bitwise-ORed together (the “|” character). Once this is done, you can use this new smfi_quarantine() routine inside your xxfi_eom() routine, like this:

ret = smfi_quarantine(ctx, reason);

The smfi_quarantine() routine’s first argument is a common context pointer, ctx. The next argument is reason, a string that will be recorded in the queue as the reason this message was quarantined. The string must be non-NULL and not empty.

For example, suppose your Milter screens for viruses and one was found:

ret = smfi_quarantine(ctx, "Possible virus found in message body");

The return value (the ret) will be MI_SUCCESS on success; otherwise it will be MI_FAILURE. This smfi_quarantine() routine can fail if reason is NULL or empty, or if there was a network error, or if SMFIF_QUARANTINE was not set in your smfiDesc structure.

Milter smfi_register()

Declare xxfi_ functions to call and set flags All sendmail versions

The sendmail program calls your Milter by passing information to your Milter’s socket. The Milter library wraps your program, reads from that socket, and passes that information into xxfi_ functions that you write. But the Milter library won’t call any of those functions unless you let it know which ones to call. You do that by filling out an smfiDesc structure and passing that structure to the smfi_register() routine. The smfi_register() routine, which must be called before you call smfi_main() (Milter smfi_main() on page 1193), is called like this:

ret = smfi_register(descr);

The smfi_register() routine takes a single argument, descr, which is a copy of a structure (not a pointer) of the type smfiDesc. The integer value MI_FAILURE is returned (to ret) if smfi_register() cannot allocate memory, or if the version is wrong or if one of the flags is illegal (see Table 26-11). The smfiDesc structure looks like this:

struct smfiDesc
{
    char            *name;
    int             version;
    unsigned long             flags;
    sfsistat             funct;        /* for xxfi_connect */
    sfsistat             funct;        /* for xxfi_helo */
    sfsistat             funct;        /* for xxfi_envfrom */
    sfsistat             funct;        /* for xxfi_envrcpt */
    sfsistat             funct;        /* for xxfi_header */
    sfsistat             funct;        /* for xxfi_eoh */
    sfsistat             funct;        /* for xxfi_body */
    sfsistat             funct;        /* for xxfi_eom */
    sfsistat             funct;        /* for xxfi_abort */
    sfsistat             funct;        /* for xxfi_close */
    sfsistat             funct;        /* for xxfi_unknown */              ← V8.14
and later
    sfsistat             funct;        /* for xxfi_data */                 ← V8.14
and later
    sfsistat             funct;        /* for xxfi_negotiate */            ← V8.14
and later
}

Here, the name should be initialized with a string that is the name of your Milter. Note that this name does not have to match the name declared with the X configuration command, but should probably do so to avoid confusion.

The version is the literal constant SMFI_VERSION.

The 13 funct expressions are each either the address of an appropriate xxfi_ function you wrote, or the value NULL. If a funct points to an xxfi_ function, that function will be called by the Milter library. If the funct is NULL, the Milter library will act as though the xxfi_ function is called but always returns SMFIS_CONTINUE.

Table 26-11. Flags for the flags entry in the smfiDesc structure

Flag

Description

SMFIF_ADDHDRS

This Milter may call smfi_addheader() (Milter smfi_addheader() on page 1184) to add headers, and may call smfi_insheader() (Milter smfi_insheader() on page 1192) to insert headers.

SMFIF_ADDRCPT

This Milter may call smfi_addrcpt() (Milter smfi_addrcpt() on page 1185) to add an envelope recipient to the message.

SMFIF_ADDRCPT_PAR

This Milter may call smfi_addrcpt_par() (Milter smfi_addrcpt_par() on page 1186) to add an envelope recipient with ESMTP extensions to the message.

SMFIF_CHGBODY

This Milter may call smfi_replacebody() (Milter smfi_replacebody() on page 1196) to replace all or part of the message body.

SMFIF_CHGFROM

This Milter may call smfi_chgfrom() (Milter smfi_chgfrom() on page 1187) to add or change the envelope sender and its ESMTP extensions.

SMFIF_CHGHDRS

This Milter may call smfi_chgheader() (Milter smfi_chgheader() on page 1188) to change or delete a header.

SMFIF_DELRCPT

This Milter may call smfi_delrcpt() (Milter smfi_delrcpt() on page 1189) to remove an envelope recipient from a message.

SMFIF_QUARANTINE

This Milter may call smfi_quarantine() (Milter smfi_quarantine() on page 1194) to quarantine a message.

SMFIF_SETSYMLIST

This Milter may call use smfi_setsymlist() (Milter smfi_setsymlist() on page 1201) to specify the list of macros the Milter will need. (V8.14 and later)

When you specify multiple flags, you must separate each from the others with a vertical bar (the bitwise-OR operator):

SMFIF_ADDHDRS|SMFIF_CHGBODY|SMFIF_ADDRCPT; /* flags */

A full declaration of an smfiDesc structure followed by a call to smfi_negotiate() might look like this:

struct smfiDesc MyDesc = {
    "my_milter",                    /* name */
    SMFI_VERSION,                    /* version */
    SMFIF_ADDHDRS|SMFIF_CHGBODY|SMFIF_ADDRCPT; /* flags */
    xxfi_connect,
    xxfi_helo,
    xxfi_envfrom,
    xxfi_envrcpt,
    xxfi_header,
    xxfi_eoh,
    xxfi_body,
    xxfi_eom,
    xxfi_abort,
    xxfi_close,
    xxfi_unknown,
    xxfi_data,
    xxfi_negotiate,
};
ret = smfi_register(MyDesc);
if (ret == MI_FAILURE)
    /* handle error here */

The smfi_register() routine should be called only once at Milter startup, but beware that the Milter library will not warn if it is called multiple times, and undesirable behavior may result.

Milter smfi_replacebody()

Replace the message body All sendmail versions

The SMTP DATA portion of an envelope contains two parts: headers, then an empty line, followed by the body. A Milter receives a copy of the body in its xxfi_body() function (Milter xxfi_body() on page 1207). If a Milter intends to modify or replace the body, it must first either save and then modify a copy, or create a new body.

Once the new body is prepared, you call smfi_replacebody() and, using that, replace the old body with the new. Note that you can only call smfi_replacebody() from inside the xxfi_eom() function you write. The smfi_replacebody() routine is called like this:

ret = smfi_replacebody(ctx, buf, len);

Here, ctx is the common context pointer that was passed to your xxfi_eom() function. The buf is a pointer to the location in memory where your new message body is located, and len is the size in bytes of the new body.

The returned value (the ret) will be MI_FAILURE if buf is NULL and len is greater than zero, or if the SMFIF_CHGBODY flag was not set with smfi_register() (Milter smfi_register() on page 1194) or if there is a system error. If buf is NULL and len is zero, the body becomes empty.

The data in buf does not need to be zero-terminated (like a string) because the size is set with the len argument. Each line in the new body, however, must be terminated by a carriage-return/newline combination (\r\n).

Note that the first time you call smfi_replacebody() for an envelope, the body is truncated to zero length and the new body chunk replaces the old. Subsequent calls to smfi_replacebody() for that same envelope append text to the new body without first truncating.

Also note that the body can be changed by other Milters too. Don’t presume the current Milter will be the only one to call smfi_replacebody(). Also don’t presume the current Milter will necessarily be given the original body untouched by other Milters.

Milter smfi_setbacklog()

Tune the size of the listen() queue V8.13 and later

The Unix C-library listen(3) function takes two arguments: the socket on which to listen; and the backlog (maximum length) of the queue of pending connections:

listen(socket, backlog);

The smfi_setbacklog() routine is used to define a new value for backlog and is called like this:

ret = smfi_setbacklog(backlog);

This call will fail and return (in ret) MI_FAILURE only if backlog is less than or equal to zero. The default value for backlog, if you don’t change it, is 20. Note that smfi_setbacklog() should be called only once before the Milter library begins to listen. If called again thereafter, the request will be ignored and will not return an error.

Note that some kernels may have built-in defaults of their own for backlog, so calling smfi_setbacklog() may have no effect at all.

Milter smfi_setconn()

Set up for the listening connection All sendmail versions

Milters are capable of communicating with sendmail over a named pipe or over a TCP network socket. You specify which of these your Milter will use by calling smfi_setconn() before calling smfi_main(). The smfi_setconn() routine is called like this:

ret = smfi_setconn(how);

Here, how is a string of the form prefix:socket, where prefix is selected from those shown in Table 26-12, and socket is appropriate to the prefix.

Table 26-12. Prefixes used in the smfi_setconn() string

Prefix

Description

unix

The socket will be a named pipe, the path to which is specified, as for example, unix:/var/run/milter.soc

local

A synonym for unix

inet

The socket is for an IPv4 TCP/IP connection which is specified as either a hostname or an IP address, as, for example, inet:3030@127.0.0.1

inet6

The socket is for an IPv6 TCP/IP connection which is specified as either a hostname or an IP address, as, for example, inet6:3030@3ffe:8050:201:1860:42::1

Note that smfi_setconn() does not actually set up the socket. It only supplies your information to the Milter library. To actually create the socket you need to call either the smfi_main() routine (Milter smfi_main() on page 1193), which will create it automatically as part of startup, or the smfi_opensocket() routine (Milter smfi_opensocket() on page 1193), which will perform the actual socket creation. The latter is preferred if you wish to manage socket errors yourself.

For safety, don’t run your Milter as root if you will be using a unix: socket. If you run the Milter as an ordinary user, you should set the unix: socket’s permissions to 0600 or 0660.[449]

Milter smfi_setdbg()

Turn on/off library tracing V8.13 and later

You can trace selected actions by the Milter library routines from inside the Milter library. You turn tracing on and off with this smfi_setdbg() routine. It takes a single argument, which is a tracing level to use:

(void) smfi_setdbg(level);

The smfi_setdbg() routine sets an internal, global variable that causes selected events to be logged or printed. The default is zero, which turns off tracing. The maximum is six,[450] which prints the most tracing. To see what is traced and how to interpret that tracing output, search for "dbg" in the libmilter/*.c source files. Note that the smfi_setdbg() routine always returns MI_SUCCESS, no matter what,[451] so you may safely ignore its returned value.

Milter smfi_setmlreply()

Return multiline error messages V8.13 and later

The smfi_setmlreply() library routine allows your Milter to return errors that have multiple lines. It is used like this:

ret = smfi_setmlreply(ctx, smtpcode, dsncode, msg1, msg2, ..., NULL);

Here, ctx is the common context pointer, smtpcode is a string containing a three-digit SMTP reply code, and dsncode is a string containing three integers (separated by dots) that form a DSN reply code. The msg1, msg2, and so on are strings (or pointers to strings). Each string will occupy a separate line of the error message. Concluding the list of one or more strings is the literal NULL.

The following, for example, causes sendmail to issue additional information each time it rejects an offending SMTP command:

ret = smfi_setmlreply(ctx, "421", "4.7.1",
        "We do not accept spam from your site,",
        "Contact whitelist@our.domain to be whitelisted",
        "or telephone (555) 555-1234 for help.", NULL);

This setting will cause the message to be rejected like this:

421-4.7.1 We do not accept spam from your site,
421-4.7.1 Contact whiteliste@our.domain to be whitelisted
421 4.7.1 or telephone +1-555-555-1234 for help.

Note that beginning with V8.13.5, if the Milter returns SMFI_TEMPFAIL, the SMTP reply code 421 causes sendmail to drop the connection immediately after issuing this reply.

Milter smfi_setpriv()

Set aside private data for later use All sendmail versions

Often, a Milter will need private data to keep track of things such as individual headers viewed, or will need to buffer data, such as the parts of a message’s body. The Milter library provides a means to set aside and use private data. You declare the data using this smfi_setpriv() routine, then later fetch it using the smfi_getpriv() routine (Milter smfi_getpriv() on page 1189). The smfi_setpriv() routine is used like this:

ret = smfi_setpriv(ctx, datap);

Here, ctx is the common context pointer that was passed to your xxfi_eom() function. The datap is a pointer that contains the address of your data. The smfi_setpriv() routine expects a datap that is of type void *, so you may need to cast your call, depending on how picky your compiler is:

ret = smfi_setpriv(ctx, (void *)datap);

The data to which datap points must not be automatic or local because it must survive calls to multiple xxfi_ functions. Instead, you should allocate the space and free it when done. Consider, for example, the following:

typedef struct {
    char        **rheads;
    int           nheads;
} MY_DATUM;

MY_DATUM *mdp = calloc(1, sizeof(MY_DATUM));

ret = smfi_setpriv(ctx, mdp);

Each context (ctx) may have only one private data pointer. If you call smfi_setpriv() twice with the same ctx, the first pointer will be discarded and replaced with the second, possibly resulting in a memory leak.

The return value (ret) will be MI_FAILURE only if the context pointer ctx is invalid; otherwise, it is always MI_SUCCESS.

Be aware that when you allocate your private data it is up to you to free that memory before it is lost. Remember that a context comes into existence when the connection is made and is lost to you when the connection closes. The Milter library will not free memory for you, and it shouldn’t.[452] Plan you program logic such that memory will never leak.

Milter smfi_setreply()

Tune how messages are rejected All sendmail versions

The reply code and message that sendmail uses to reject or temp-fail the current message is set by calling the smfi_setreply() Milter library routine. That routine accepts four arguments:

ret  = smfi_setreply(ctx, rcode, dsncode, message);

Here, the rcode specifies the SMTP reply number that sendmail should return. The rcode is in the form of a three-digit string that must begin with a 4 or a 5.

The dsncode must either be NULL or a string containing three integers with a dot separating each integer from the next. For example:

"5.7.1"

If the first integer is not a 4 or 5, the smfi_setreply() routine will return MI_FAILURE. Similarly, if the three integers are not composed of all digits, or if the character positions that should be occupied by dots are not occupied by dots, the smfi_setreply() routine will also return MI_FAILURE. If dsncode is NULL, it is ignored and a default DSN return value will be generated by sendmail.

The last argument, the msg, is a string which specifies a new rejection or temp-fail message:

"Go away, evil spammer"

If the string is longer than 980 characters, or if it contains a carriage-return (\r) or linefeed character (\n), the smfi_setreply() routine will return MI_FAILURE. If msg is NULL, it is ignored and no message will be issued as part of the reply.

Each time smfi_setreply() is called, it frees any prior message and replaces it with the new one.

The Milter library, except for the single situation described in the next section, will silently enforce a failure to match the SMTP code to the type of rejection you specified. But note that if you specify a 5yz code and temporarily fail (temp-fail) the message, your smfi_setreply() setting will be ignored. Similarly, if you specify 4yz and reject the message, your custom reply will also be ignored.

V8.13 SMTP 421 and SMFIS_TEMPFAIL

The connection routine in a Milter can cause a connection to be rejected. Prior to V8.13 sendmail, connections were rejected in a gentle manner. The connecting site was given a 220 reply and all subsequent commands from that connecting site were each given a 550 reply—except for QUIT (a 221 reply) and NOOP (a 250 reply). This roundabout approach was needed to prevent harming some broken MTAs which could not handle a 550 rejection to the connection gracefully.

The reply code that sendmail uses to reject or temp-fail the current message is set by calling the smfi_setreply() Milter library routine. That routine accepts four arguments:

ret = smfi_setreply(ctx, rcode, dsncode, message);

Here, the rcode specifies the SMTP reply number that sendmail should return.

Beginning with V8.13, sendmail will reject the message with a 421 SMTP reply if you set rcode to 421 and if your Milter returns SMFIS_TEMPFAIL. When rejecting a connection, 421 allows sendmail to drop the connection immediately, instead of being forced to use the gentle approach described earlier. Prior to V8.14, this worked for all but xxfi_helo(). Beginning with V8.14, xxfi_helo() may now also take advantage of this property.

Milter smfi_setsymlist()

Set protocol macro list V8.13 and later

Normally the list of macros made available to a Milter is defined in the sendmail configuration file (see Milter smfi_getsymval() on page 1190). Prior to V8.14, it was not possible to change that list from within a running Milter. V8.14 added the xxfi_negotiate() function to the list of functions you write. From within your xxfi_negotiate() function you may call this smfi_setsymlist() routine to specify a list of macros you want to make available to your Milter.

The smfi_setsymlist() routine is called like this:

ret = smfi_setsymlist(ctx, stage, maclist);

Here, ctx is the common context pointer that was passed to your xxfi_negotiate() function. The stage is a symbolic constant that indicates the xxfi_ function that will want the macros, and maclist is that list of wanted macros as a string.

The stage is specified by one of the symbolic constants listed in Table 26-13.

Table 26-13. Constants to specify the stage for smfi_setsymlist()

Value for stage

Function called

SMFIM_CONNECT

xxfi_connect()

SMFIM_DATA

xxfi_data()

SMFIM_HELO

xxfi_helo()

SMFIM_ENVFROM

xxfi_envfrom()

SMFIM_ENVRCPT

xxfi_envrcpt()

SMFIM_EOH

xxfi_eoh

SMFIM_EOM

xxfi_eom

Only one stage may be specified with each call to smfi_setsymlist(). If stage is not one of the values listed, smfi_setsymlist() will return MI_FAILURE.

The returned value (the ret) will be MI_FAILURE if maclist is NULL or if smfi_setsymlist() has already been called before for a given stage, or if there was a memory allocation error.

The maclist is a string that lists the names of macros you want to use. The $ prefix must be omitted from each name. Multicharacter macro names must be enclosed in curly braces, and single-character names may optionally be surrounded in curly braces.

"${j}"                    ← Won't work, has leading $ character
"{j}"                        ← Good
"j"                        ← Also good

The list in maclist must be one macro name followed by a space character, then by the next name:

"{nbadrcpts} {MyMacro} j"

As of V8.14, you may specify no more than five macros in the list. That limit is enforced by sendmail, not by the Milter library, so smfi_setsymlist() will appear to succeed when there are more than five, but your xxfi_ function will fail to get back its full list of required macros.

Note that the list you specify with maclist is independent of the list specified in sendmail’s configuration file. The result received by any listed xxfi_ function’s stage is a union of the two.

Milter smfi_settimeout()

Change Milter to sendmail timeout All sendmail versions

Two different sets of timeouts are associated with your Milter. The time sendmail spends waiting for your Milter is hardcoded inside sendmail. The time your Milter spends waiting for sendmail is set with this smfi_settimeout() routine.

The smfi_settimeout() routine is called like this:

ret = smfi_settimeout(secs);

Here, secs is the number of seconds your Milter should ever wait for a reply from sendmail. The default is 7,210 seconds (roughly two hours). If you wish to change that timeout, you may do so from within any xxfi_ function at any time by specifying a new value with this smfi_settimeout() routine.

As a special case, a secs of zero or less cancels all timeouts and causes your Milter to wait forever. The returned value (the ret) is always MI_SUCCESS.

Milter smfi_stop()

Cause a controlled shutdown V8.13 and later

When an error occurs while running your Milter (such as a failure to write to a database, or the inability to allocate memory),[453] you would normally syslog(3) an error, and call exit(2) to quit. A more graceful way to quit your Milter is to use the smfi_stop() routine. It is called like this:

(void) smfi_stop();

The smfi_stop() routine always returns MI_SUCCESS, no matter what, so you may safely ignore its returned value. The smfi_stop() routine sets an internal, global flag that causes all threads to return (exit) when each has finished the current connection. The result is a return from your call to smfi_main() so that you can perform cleanup tasks before exiting, or warm-restart the Milter.

Note that smfi_stop() returns, whereas exit(3) does not. Be sure your code can handle that difference before replacing exit(3) with smfi_stop().

Milter smfi_version()

Fetch the runtime library version V8.14 and later

There are two versions associated with every Milter’s code. One is the compile-time version as hardcoded into the SMFI_VERSION macro. The other is the runtime version that can be fetched using this smfi_version() routine. The smfi_version() routine can be called from your main() routine, like this:

ret = smfi_version(pmajor, pminor, plevel);

Here, the three variables pmajor, pminor, and plevel are pointers to unsigned int types. The variables pointed to will, as a result of the call, be filled out with the corresponding values:

  • pmajor will contain the major version number for the Milter library.

  • pminor will contain the minor version number for the Milter library.

  • plevel will contain the current patch level for the Milter library.

The value returned from the call to smfi_version() is always MI_SUCCESS.

These three values are the values returned by the runtime library. If you wish to compare them to the version values that existed when you built your Milter, you may use the corresponding Build-time macros:

  • SM_LM_VRS_MAJOR(SMFI_VERSION) returns the major version number for the Milter library.

  • SM_LM_VRS_MINOR(SMFI_VERSION) returns the minor version number for the Milter library.

  • SM_LM_VRS_PLVL(SMFI_VERSION) returns the current patch level for the Milter library.

xxfi_ Routine Reference

A Milter is composed of two kinds of function calls. Those that you write are called the xxfi_ functions and are described here. Those that your written functions call in the Milter library are the smfi_ routines, described in the preceding section. The complete list of functions you write (the xxfi_ functions) is shown in Table 26-15 on page 1205, and that is followed by a reference section for each.

Note that these functions do not need to be prefixed with xxfi_ or given the names shown in the table or in the sections to follow. You may name these functions anything you want. Just to be sure to declare those names in the correct positions of the smfiDesc structure passed to the smfi_register() routine (Milter smfi_register() on page 1194):

struct smfiDesc MyFunctions  = {
    "MyMilter",                    /* Milter name    */
    SMFI_VERSION,                    /* Milter version */
    SMFIF_ADDHDRS,                    /* Milter flags     */
    xxfi_connect,                    /* Your connection handler      */
    myHeloFunction,                    /* Your HELO/EHLO handler */
... etc.

Here, for example, the xxfi_helo() function has been given the name myHeloFunction. We, however, will use the xxfi_ prefixed names used in the sendmail documentation for clarity.

Each function returns the type sfsistat and is passed as its first argument a context pointer called ctx:

sfsistat
xxfi_name(SMFICTX *ctx, args)

Your xxfi_ functions are called to handle each phase of an SMTP conversation. Some handle the connection setup and shutdown (are connection-oriented). Others handle the envelope startup and shutdown, where there may be multiple envelopes per connection. Yet others handle recipients, where there may be multiple recipients per envelope. The value that each function returns determines how the Milter will be called next and how other Milters will be called.

Note that the ssfistat value returned by one of your xxfi_ functions has a different effect depending on the phase of the SMTP conversation being handled at the time. These relationships and effects are described in Table 26-14 and in the sections to follow.

Table 26-14. Values of type sfsistat that xxfi_ functions may return

sfsistat return value

Description

SMFIS_CONTINUE

Continue processing the current connection, envelope, or recipient.

SMFIS_REJECT

For connection-oriented routines, reject this connection (your xxfi_close() will be called). For envelope-oriented routines other than xxfi_eom() and xxfi_abort(), reject this envelope. For recipient-oriented functions, reject the current recipient only and continue processing the current connection and envelope.

SMFIS_DISCARD

Envelope-oriented and recipient-oriented functions cause the enveloped to be accepted, but silently discarded. Note that this SMFIS_DISCARD value should never be returned by a connection-oriented function.

SMFIS_ACCEPT

Connection-oriented routines cause the current connection to be accepted (your Milter will only have xxfi_close() called thereafter for this connection). Envelope-oriented and recipient-oriented functions cause the current envelope to be accepted (your Milter will not be called again for this envelope).

SMFIS_TEMPFAIL

Causes the corresponding SMTP command to return a 4xx status code, which normally temp-fails the command. All envelope-oriented functions except xxfi_envfrom() cause the current envelope to fail. Connection-oriented functions cause the current connection to be rejected (your Milter will only have xxfi_close() called thereafter for this connection). Recipient-oriented functions only fail for the current recipient, but allow processing of the current envelope to continue.

SMFIS_SKIP

(V8.14 and later) Currently this return value is only allowed from within xxfi_body(). It causes the current xxfi_ function to cease being called when it would normally be called again for the same envelope. With the xxfi_body() function, for example, you can return SMFIS_SKIP after you have received enough body chunks to make a decision. For example, you may not need more data, but may still want to continue because you need to perform actions that can only later be performed from within your xxfi_eom() function (Milter xxfi_eom() on page 1215). Note that your Milter must negotiate this behavior with its xxfi_negotiate() function (Milter xxfi_negotiate() on page 1220) to check whether the protocol action SMFIP_SKIP is available and, if so, request it.

SMFIS_NOREPLY

(V8.14 and later) Tell the MTA to not block waiting for a reply from this Milter. If an xxfi_ function returns this value, it must always return this value (even in an error state). If you cannot guarantee that your xxif_ function will always return this value, return SMFIS_CONTINUE instead. Whether a given xxfi_ function will return SMFIS_NOREPLY or not is set in the xxfi_negotiate() function (Milter xxfi_negotiate() on page 1220). Note that your Milter must negotiate this behavior with its xxfi_negotiate() function (Milter xxfi_negotiate() on page 1220) to check whether the protocol action SMFIP_NOREPLY is available and, if so, request it.

The first argument passed to every xxfi_ function is the common ctx pointer. This pointer points to the context necessary to associate a given SMTP conversation with a given thread in a multithreaded environment:

sfsistat ret;
ret = xxfi_connect(SMFICTX    *ctx,  ... the rest of the arguments follow);

Although the Milter library will extend every effort to ensure that ctx is never NULL, you should use safe programming practices anyway and test all arguments before use. This applies to “... the rest of the arguments” that follow too. Recall that your Milter will receive its arguments from the SMTP transaction sent by clients on the Internet. It is up to you to ensure that your Milter does not break because of unexpected input.

Table 26-15 lists the all the xxfi_ functions currently available in logical order (the most likely order in which they will be called during a normal SMTP transaction). In the sections that follow the table, they are presented in alphabetical order to make them easier to locate.

Table 26-15. xxfi_ Milter Routines listed in logical order

Macro

§

Description

xxfi_negotiate

Milter xxfi_negotiate() on page 1220

Change your Milter’s relationship with sendmail dynamically at runtime (V8.14 and later)

xxfi_connect()

Milter xxfi_connect() on page 1209

Called once, upon initial connection by the sending site to the listening sendmail daemon

xxfi_helo()

Milter xxfi_helo() on page 1218

Normally called once, after sending site sends its HELO or EHLO; but it can be called anytime thereafter or may never be called

xxfi_envfrom()

Milter xxfi_envfrom() on page 1211

Called once per envelope, just after the sending site sends its MAIL From: envelope sender

xxfi_envrcpt()

Milter xxfi_envrcpt() on page 1213

Called multiple times, once each time just after the sending site sends one of its RCPT To: envelope recipients

xxfi_data()

Milter xxfi_data() on page 1210

Called once when the DATA command is received (V8.14 and later)

xxfi_unknown()

Milter xxfi_unknown() on page 1223

Called multiple times, once for each unknown SMTP command received (V8.14 and later)

xxfi_header()

Milter xxfi_header() on page 1217

Called multiple times, once for each header that is received

xxfi_eoh()

Milter xxfi_eoh() on page 1214

Called once per envelope, after all the headers have been received

xxfi_body()

Milter xxfi_body() on page 1207

Called multiple times, once for each piece of the message’s body

xxfi_eom()

Milter xxfi_eom() on page 1215

Called once per envelope, after the entire body has been received (either xxfi_eom or xxfi_abort will be called, but not both)

xxfi_abort()

Milter xxfi_abort() on page 1206

Called once, if the message was rejected outside the current Milter (either xxfi_eom or xxfi_abort will be called, but not both)

xxfi_close()

Milter xxfi_close() on page 1208

Called once when the connection is closed

Note that before you can build a Milter that contains xxfi_ functions, you need to include the correct header file:

#include <libmilter/mfapi.h>

Finally, note that the location of this #include file is determined when you build sendmail (Create libmilter on page 1171). Here we use the default location.

Milter xxfi_abort()

Handle envelope abort All Milter versions

As long as all your envelope-oriented xxfi_ functions return SMFIS_CONTINUE, the Milter library guarantees that either your xxfi_eom() or xxfi_abort() function will be called. The xxfi_eom() function (Milter xxfi_eom() on page 1215), if used, is called after all the chunks of the message body have been processed with xxfi_body() (Milter xxfi_body() on page 1207). This xxfi_abort() function is called if another Milter or sendmail rejected, temporarily failed, discarded, or final-accepted the current envelope, outside the control of your Milter.

Note that xxfi_eom() and xxfi_abort() are mutually exclusive, that is, if one is called the other will not be called.

The xxfi_abort() function is called like this:

sfsistat
xxfi_abort(SMFICTX *ctx)

Here, ctx is the context pointer passed to all xxfi_ functions to maintain state in a multithreaded environment. Nothing else is passed. Because there is nothing left of the envelope to process, the value returned by xxfi_abort() is ignored. If no abort function is listed in the smfiDesc structure (Milter smfi_register() on page 1194), SMFIS_CONTINUE is returned by default.

Note that xxfi_abort() marks the end of the current envelope. There may be multiple envelopes per connection. The xxfi_close() function (Milter xxfi_close() on page 1208), if used, ends processing of the connection. This xxfi_abort() mirrors xxfi_eom() and should be used to deallocate any envelope-specific private data and to clean up envelope-specific information in general.

Also note that xxfi_abort() is called only if the envelope is ended outside the control of your Milter (as by another Milter). If your Milter formally gives up control by returning SMFIS_ACCEPT, SMFIS_REJECT, or SMFIS_DISCARD from within one of your xxfi_ envelope-specific functions, your Milter will not have this xxfi_abort() called.

Milter xxfi_body()

Review a chunk of message body All Milter versions

The message body follows the headers. Thus, xxfi_eoh() (Milter xxfi_eoh() on page 1214), if used, will be called before the first call to xxfi_body(). Because the message body may be huge, xxfi_body() might reasonably be called multiple times for a given body and is passed a chunk of the body each time. After all the body chunks have been passed, xxfi_eom() (Milter xxfi_eom() on page 1215), if used, will be called to signal the end of body chunks.

The xxfi_body() function is called like this:

sfsistat
xxfi_body(SMFICTX *ctx,  unsigned char *bodyp,  size_t len)

Here, ctx is the context pointer passed to all xxfi_ functions to maintain state in a multithreaded environment. The bodyp is a pointer to a buffer that contains len bytes of body. Although bodyp is of type char *, it is not a string and must not be treated as a string (that is, you must not depend on it being zero-terminated).[454]

List xxfi_body() in smfiDesc only if you need to process the body. Message bodies can be large, and needlessly asking for body chunks can adversely impact a Milter’s performance.

The values your xxfi_body() function can return and their meanings are:

SMFIS_CONTINUE

Allow the current body chunk and expect more chunks if any. This is the default return value if you don’t declare a body chunk handler in smfiDesc (Milter smfi_register() on page 1194).

SMFIS_ACCEPT

Accept the current body chunk and thereafter the current envelope. Your Milter will not be called again for this envelope.

SMFIS_REJECT

Reject the current envelope (with a 5yz SMTP code). Your Milter will not be called again for this envelope. Note that this rejects only the current envelope. If there are more envelopes on the current connection, your Milter will still be called for each.

SMFIS_DISCARD

Accept but discard the current envelope. Your Milter will not be called again for this envelope. Note that this discards only the current envelope. If there are more envelopes on the current connection, your Milter will still be called for each.

SMFIS_TEMPFAIL

Temp-fail the current envelope (with a 4yz SMTP code). Your Milter will not be called again for this envelope. Note that this temp-fails only the current envelope. If there are more envelopes on the current connection, your Milter will still be called for each.

SMFIS_SKIP (V8.14 sendmail and later)

Tentatively continue, but not receive any more body chunks. This lets the Milter library know you will defer your decision until xxfi_eom() (Milter xxfi_eom() on page 1215), if used, is later called. But note that to return this value you must first use xxfi_negotiate() (Milter xxfi_negotiate() on page 1220) to let the library know your intention.

SMFIS_NOREPLY (V8.14 sendmail and later)

Do not communicate any decision back to sendmail. Note that if you elect to return SMFIS_NOREPLY, you must only return SMFIS_NOREPLY and must first use xxfi_negotiate() (Milter xxfi_negotiate() on page 1220) to let the library know your intention.

Note that each body chunk will be presented to your Milter as it comes over the SMTP connection (that is, carriage-return/linefeed combinations will terminate each line). Also note that your Milter may not see the original body. An earlier Milter may have changed the body and there is no way for your Milter to detect that change, nor should it try.

Milter xxfi_close()

Close a connection All Milter versions

A connecting client, when finished sending zero or more envelopes, will close down the connection to sendmail by sending the SMTP QUIT command. A connection can also be closed down if sendmail drops the connection itself. No matter how the connection shuts down, this xxfi_close() function, if used, will be called.

The xxfi_close() function is called like this:

sfsistat
xxfi_close(SMFICTX *ctx)

Here, ctx is the context pointer passed to all xxfi_ functions to maintain state in a multithreaded environment. Nothing else is passed. If you have earlier declared a private data pointer with smfi_setpriv() (Milter smfi_setpriv() on page 1199), this may be a good place to deallocate that data. But be aware that xxfi_close() can be the only xxfi_ function called for a connection. Consider the case of a connection rejected through the access database (The access Database on page 277). In that event, xxfi_connect() will not be called, but xxfi_close() will be, so always anticipate that your private data pointer might be NULL.

Note that xxfi_close() is called even if a prior Milter rejected the connection.

Also note that any value returned by xxfi_close() is ignored, so you may return any value with no change in effect. If you don’t declare a close handler in smfiDesc (Milter smfi_register() on page 1194), the default return value is SMFIS_CONTINUE.

Milter xxfi_connect()

Begin a connection All Milter versions

Before any messages (envelopes) can be processed, the sending client must connect to the listening sendmail server. After the connection is made, but before sendmail provides its normal 220 greeting, this xxfi_connect() function, if used, is called.

The xxfi_connect() function is called like this:

sfsistat
xxfi_connect(SMFICTX *ctx,  unsigned char *hostname,  SOCK_ADDR *hostaddr)

Here, ctx is the context pointer passed to all xxfi_ functions to maintain state in a multithreaded environment. The hostname is a pointer to a buffer that contains the hostname of the connecting client. The hostname is derived by a reverse-look-up of the connecting client’s IP address. After finding the hostname, sendmail looks it up to find its IP address. As a special case, if the found IP address does not match the original, the hostname will contain the found IP address in square braces.

"foo.example.com"            ← success
"[123.45.67.89]"             ← failure

Note that a host can have multiple IP addresses and, if so, each is compared to the original connecting IP address and at least one must match.

The hostname is guaranteed by the Milter library to never be NULL, but it can contain an empty string. If the connection is over the standard input, the hostname will contain a copy of the expression "localhost" as a string. The hostname may or may not be a canonical hostname, depending on the connecting client’s behavior.

The hostaddr is the result of a call to getpeername(2) for information about the connecting client’s socket. This hostaddr pointer will be NULL if the connection is over the standard input.

The values the xxfi_connect() function can return and their meanings are:

SMFIS_CONTINUE

Allow the current connection and continue handling it. This is the default return value if you don’t declare a connection handler in smfiDesc (Milter smfi_register() on page 1194).

SMFIS_ACCEPT

Accept the current connection but do not handle it. Your Milter will not be called again for this connection until the connection terminates and your xxfi_close() function (Milter xxfi_close() on page 1208) is called.

SMFIS_REJECT

Reject the current connection (with a 5yz SMTP code). Your Milter will not be called again for this connection until the connection terminates and your xxfi_close() function (Milter xxfi_close() on page 1208), if used, is called.

SMFIS_TEMPFAIL

Temp-fail the current connection (with a 4yz SMTP code). Your Milter will not be called again for this connection until the connection terminates and your xxfi_close() function (Milter xxfi_close() on page 1208), if used, is called.

SMFIS_NOREPLY (V8.14 sendmail and later)

Do not communicate any decision back to sendmail. Note that if you elect to return SMFIS_NOREPLY, you must only return SMFIS_NOREPLY and must first use xxfi_negotiate() (Milter xxfi_negotiate() on page 1220) to let the library know your intention.

Note that an xxfi_connect() function will not be called if an earlier Milter failed or rejected the connection, or if sendmail itself rejected the connection. Also note that your Milter may deny subsequent Milters a crack at the connection if you mistakenly return a wrong code.

Finally, note that it makes no sense to return SMFIS_DISCARD because at this point, no envelopes have been received yet, so there is nothing to discard.

Milter xxfi_data()

Process the DATA command V8.14 and later

After the connecting client has sent the last of its recipients (after all SMTP RCPT To: commands have been sent), the client normally begins to send the message itself by issuing the SMTP DATA command. After the DATA command has been received, but before sendmail responds to that SMTP DATA command, the xxfi_data() function, if used, is called.

The xxfi_data() function is called like this:

sfsistat
xxfi_data(SMFICTX *ctx)

Here, ctx is the context pointer passed to all xxfi_ functions to maintain state in a multithreaded environment. That is the only argument passed.

The xxfi_data() function is useful as a means to reject an envelope after all the envelope recipients have been specified. Such a rejection can occur, for example, because more than the number of envelopes allowed from a particular sender were received, or because the ratio of accepted versus rejected recipients by your Milter was too low. The value returned by xxfi_data() specifies how you wish the DATA command handled:

SMFIS_CONTINUE

Allow the DATA command and thus the current envelope, and continue handling the current envelope. This is the default return value if you don’t declare a data handler in smfiDesc (Milter smfi_register() on page 1194).

SMFIS_ACCEPT

Accept the DATA command and thus the current envelope. Your Milter will not be called again for this envelope.

SMFIS_REJECT

Reject the DATA command (with a 5yz SMTP code), and thus the current envelope. Your Milter will not be called again for this envelope. Note that this rejects only the current envelope. If there are more envelopes on the current connection, your Milter will still be called for each.

SMFIS_DISCARD

Accept the DATA command (with a 354 SMTP code) and discard it, and thus discard the current envelope. Your Milter will not be called again for this envelope. Note that this discards only the current envelope. If there are more envelopes on the current connection, your Milter will still be called for each.

SMFIS_TEMPFAIL

Temp-fail the DATA command (with a 4yz SMTP code), and thus the current envelope. Your Milter will not be called again for this envelope. Note that this temp-fails only the current envelope. If there are more envelopes on the current connection, your Milter will still be called for each.

SMFIS_NOREPLY (V8.14 sendmail and later)

Do not communicate any decision back to sendmail. Note that if you elect to return SMFIS_NOREPLY, you must only return SMFIS_NOREPLY and must first use xxfi_negotiate() (Milter xxfi_negotiate() on page 1220) to let the library know your intention.

Note that you must not depend on xxfi_data() to signal the end of recipients. This is because it is possible for the client to send a QUIT or RSET or to drop the connection or to supply unexpected, input thereby resulting in xxfi_data() not being called.

Also note that if an earlier Milter rejected the DATA command, this xxfi_data() function, if used, will not be called.

Milter xxfi_envfrom()

Process the MAIL From: values All Milter versions

After the connecting client has sent the HELO/EHLO command and performed any required AUTH or STARTTLS startup, the client normally issues the SMTP MAIL From: command to specify the envelope sender. After the MAIL From: has been received, but before sendmail responds to that command, the xxfi_envfrom() function, if used, is called, like this:

sfsistat
xxfi_envfrom(SMFICTX *ctx, char **argv)

Here, ctx is the context pointer passed to all xxfi_ functions to maintain state in a multithreaded environment. The argv is an array of pointers to strings. The zeroth string is always the envelope-sender address. Note that this is the address as it was received by sendmail and could easily be in an unexpected format:

argv[0] →  "<bob>"
argv[0] →  "<bob <bob@example.com>>"
argv[0] →  "<>"
argv[0] →  ""

As you can see from the last two lines in the preceding code, your Milter should be prepared to handle not only oddly formed addresses, but also bounce addresses and empty addresses as well.

If the envelope sender is followed by ESMTP extensions, each extension will be copied to a subsequent string in the order they appeared in the MAIL From: command. For example, the following MAIL From:

MAIL From: <bob@example.com> SIZE=1024 ENVID=ABCD

will yield the following values in argv:

argv[0] →  "<bob@example.com>"
argv[1] →  "SIZE=1024"
argv[2] →  "ENVID=ABCD"
argv[3] →  NULL

The xxfi_envfrom() function can return any of several values that determine the handling of the envelope sender and possibly the fate of the envelope:

SMFIS_CONTINUE

Allow the MAIL From: command, and thus the current envelope, and continue handling the current envelope. This is the default return value if you don’t declare an envelope-sender handler in smfiDesc (Milter smfi_register() on page 1194).

SMFIS_ACCEPT

Allow the MAIL From: command, and thus the current envelope. Your Milter will not be called again for this envelope.

SMFIS_REJECT

Reject the MAIL From: command (with a 5yz SMTP code), and thus the current envelope. Your Milter will not be called again for this envelope. Note that this rejects only the current envelope. If there are more envelopes on the current connection, your Milter will still be called for each.

SMFIS_DISCARD

Accept the MAIL From: command (with a 2yz SMTP code) and discard it, and thus discard the current envelope. Your Milter will not be called again for this envelope. Note that this discards only the current envelope. If there are more envelopes on the current connection, your Milter will still be called for each.

SMFIS_TEMPFAIL

Temp-fail the MAIL From: command (with a 4yz SMTP code), and thus the current envelope. Your Milter will not be called again for this envelope. Note that this temp-fails only the current envelope. If there are more envelopes on the current connection, your Milter will still be called for each.

SMFIS_NOREPLY (V8.14 sendmail and later)

Do not communicate any decision back to sendmail. Note that if you elect to return SMFIS_NOREPLY, you must only return SMFIS_NOREPLY and must first use xxfi_negotiate() (Milter xxfi_negotiate() on page 1220) to let the library know your intention.

Note that a MAIL From:, if called out of order, acts like an RSET and resets the envelope to a new envelope. In this instance, the xxfi_abort() function (Milter xxfi_abort() on page 1206), if used, will only be called if SMFIS_CONTINUE was previously returned. If anything other than SMFIS_CONTINUE is returned, xxfi_abort() will not be called no matter how the envelope is rejected or final-accepted.

Also note that if an earlier Milter rejected the MAIL From:, this xxfi_envfrom() function, if used, will not be called.

Milter xxfi_envrcpt()

Process an envelope recipient All Milter versions

After the connecting client has issued the SMTP MAIL From: command to specify the envelope sender, the connecting client then (normally) sends one or more envelope recipients using an RCPT To: SMTP command to send each. After the RCPT To: has been received, but before sendmail responds to that command, the xxfi_envrcpt() function, if used, is called.

The xxfi_envrcpt() function is called like this:

sfsistat
xxfi_envrcpt(SMFICTX *ctx, char **argv)

Here, ctx is the context pointer passed to all xxfi_ functions to maintain state in a multi-threaded environment. The argv is an array of pointers to strings. The zeroth string is always the envelope-recipient address. This is the address as it was received by sendmail and could easily be in an unexpected format:

argv[0] →  "you"
argv[0] →  "<you>"
argv[0] →  "<you <you@your.domain>>"

Your Milter should be prepared to handle oddly formed addresses.

If the envelope recipient is followed by one or more ESMTP extensions, each extension will be copied to a subsequent string in the order they appeared in the RCPT To: command. For example, the following RCPT To: command:

RCPT To: <you@your.domain> ORCPT=rfc822;you@your.sub.domain

will yield the following values in argv:

argv[0] →  "<you@your.domain>"
argv[1] →  "ORCPT=rfc822;you@your.sub.domain"
argv[2] →  NULL

The xxfi_envrcpt() function can return any of several values that determine the further handling of the envelope recipient:

SMFIS_CONTINUE

Allow the RCPT To: command and thus the current recipient and to continue handling any additional recipients. This is the default return value if you don’t declare an envelope recipient handler in smfiDesc (Milter smfi_register() on page 1194).

SMFIS_ACCEPT

Allow the RCPT To: command and thus the current recipient. Your Milter will still be called again for the next recipient, if any.

SMFIS_REJECT

Reject the RCPT To: command (with a 5yz SMTP code), and thus the current recipient. Your Milter will still be called again for the next recipient, if any.

SMFIS_DISCARD

Accept the RCPT To: command (with a 2yz SMTP code) and discard it, and thus discard the current envelope. Your Milter will not be called again for this envelope.

SMFIS_TEMPFAIL

Temp-fail the RCPT To: command (with a 4yz SMTP code), and thus the current recipient. Your Milter will still be called again for the next recipient, if any.

SMFIS_NOREPLY (V8.14 sendmail and later)

Do not communicate any decision back to sendmail. Note that if you elect to return SMFIS_NOREPLY, you must only return SMFIS_NOREPLY and must first use xxfi_negotiate() (Milter xxfi_negotiate() on page 1220) to let the library know your intention.

Note that xxfi_envrcpt() can reject or temp-fail all recipients and thereby leave an empty recipient list. If the envelope lacks recipients, the entire envelope will fail. But if an earlier Milter rejects or temp-fails all recipients, your xxfi_abort() function, if used, will be called.

Also note that each recipient is completely reviewed by all Milters before the next recipient is reviewed by any. But recall that if one Milter rejects the recipient, no following Milter will be able to review that recipient.

Milter xxfi_eoh()

Process end of headers All Milter versions

The message is passed in the DATA phase of the SMTP transaction. The message is composed of headers first, then a blank line, and lastly the message body. Each header line is processed by your xxfi_header() function, if used. After all headers have been processed, but before the message body is processed, this xxfi_eoh() function, if used, is called.

The xxfi_eoh() function is called like this:

sfsistat
xxfi_eoh(SMFICTX *ctx)

Here, ctx is the context pointer passed to all xxfi_ functions to maintain state in a multi-threaded environment, and it is the only argument.

It is up to you and your code to have cached any decisions about headers for later use by this xxfi_eoh() function.

The xxfi_eoh() function can return any of several values that determine the further handling of the current envelope:

SMFIS_CONTINUE

Allow the current envelope and continue handling the current envelope. This is the default return value if you don’t declare an end-of-headers handler in smfiDesc (Milter smfi_register() on page 1194).

SMFIS_ACCEPT

Accept the current envelope. Your Milter will not be called again for this envelope but will have xxfi_close() called at the end of the connection. Note that despite your acceptance, this envelope may still be rejected by a later Milter. Also note that this accepts only the current envelope. If there are more envelopes on the current connection, your Milter will still be called for each.

SMFIS_REJECT

Reject the current envelope (and thereby the final dot with a 5yz code). Your Milter will not be called again for this envelope but will have xxfi_close() called at the end of the connection. Note that this rejects only the current envelope. If there are more envelopes on the current connection, your Milter will still be called for each.

SMFIS_TEMPFAIL

Temp-fail the current envelope (and thereby the final dot with a 4yz code). Your Milter will not be called again for this envelope but will have xxfi_close() called at the end of the connection. Note that this temp-fails only the current envelope. If there are more envelopes on the current connection, your Milter will still be called for each.

SMFIS_DISCARD

Accept and silently discard the current envelope. Your Milter will not be called again for this envelope but will have xxfi_close() called at the end of the connection. Note that this only discards the current envelope. If there are more envelopes on the current connection, your Milter will still be called for each.

SMFIS_NOREPLY (V8.14 sendmail and later)

Do not communicate any decision back to sendmail. Note that if you elect to return SMFIS_NOREPLY, you must only return SMFIS_NOREPLY and must first use xxfi_negotiate() (Milter xxfi_negotiate() on page 1220) to let the library know your intention.

Note that to reject the headers is to reject the entire DATA phase of the SMTP envelope. Subsequent Milters, if any, will not be given that DATA phase for review. If your xxfi_eoh() function returns SMFIS_CONTINUE, and if a later Milter or sendmail has rejected the envelope, your Milter’s xxfi_abort() function will be called. But if your Milter final-accepts, rejects, temp-fails, or discards the envelope, your Milter’s xxfi_abort() function will not be called.

Milter xxfi_eom()

Process a header All Milter versions

The SMTP DATA phase of a message ends when the connecting client sends a dot on a line by itself. During that SMTP DATA phase, zero or more headers may have been sent, followed by a blank line and then the message body (possibly empty). After sendmail receives the final dot, but before sendmail replies to the final dot, this xxfi_eom() function, if used, is called.

The xxfi_eom() function is called like this:

sfsistat
xxfi_eom(SMFICTX *ctx)

Here, ctx is the context pointer passed to all xxfi_ functions to maintain state in a multithreaded environment. That is the only argument passed.

The xxfi_eom() function is special in that it is allowed to do many things that other xxfi_ functions are not allowed to do. Table 26-16 lists the smfi_ routines that only xxfi_eom() may call.

Table 26-16. smfi_ routines that only xxfi_eom() may call

Routine

§

Flag required (see Table 26-11 on page 1195)

smfi_addheader

Milter smfi_addheader() on page 1184

SMFIF_ADDHDRS

smfi_addrcpt

Milter smfi_addrcpt() on page 1185

SMFIF_ADDRCPT

smfi_addrcpt_par

Milter smfi_addrcpt_par() on page 1186

SMFIF_ADDRCPT_PAR

smfi_chgfrom

Milter smfi_chgfrom() on page 1187

SMFIF_CHGFROM

smfi_chgheader

Milter smfi_chgheader() on page 1188

SMFIF_CHGHDRS

smfi_delrcpt

Milter smfi_delrcpt() on page 1189

SMFIF_DELRCPT

smfi_insheader

Milter smfi_insheader() on page 1192

SMFIF_ADDHDRS

smfi_progress

Milter smfi_progress() on page 1193

No flags required.

smfi_quarantine

Milter smfi_quarantine() on page 1194

SMFIF_QUARANTINE

smfi_replacebody

Milter smfi_replacebody() on page 1196

SMFIF_CHGBODY

Note that the xxfi_eom() function will be called only if earlier xxfi_ functions for this envelope have returned SMFIS_CONTINUE. Instead, the xxfi_abort() function will be called if another Milter or sendmail has decided to reject, or temporally fail the current envelope, outside the control of your Milter. Note that xxfi_eom() and xxfi_abort() are mutually exclusive, meaning that if one is called the other will not be called. But if your Milter has decided to reject, temp-fail, or discard the current envelope, neither will be called for that particular envelope.

The xxfi_eom() function can return any of several values that determine the further handling of the current envelope:

SMFIS_CONTINUE or SMFIS_ACCEPT

Accept the current envelope. Note that SMFIS_CONTINUE is the default return value if you don’t declare an end-of-message handler in smfiDesc (Milter smfi_register() on page 1194). Your Milter will not be called again for this envelope but will have xxfi_close() called at the end of the connection. Note that despite your acceptance, this envelope may still be rejected by a later Milter. Also note that this accepts only the current envelope. If there are more envelopes on the current connection, your Milter will still be called for each.

SMFIS_REJECT

Reject the current envelope (and thereby the final dot with a 5yz code). Your Milter will not be called again for this envelope but will have xxfi_close() called at the end of the connection. Note that this rejects only the current envelope. If there are more envelopes on the current connection, your Milter will still be called for each.

SMFIS_TEMPFAIL

Temp-fail the current envelope (and thereby the final dot with a 4yz code). Your Milter will not be called again for this envelope but will have xxfi_close() called at the end of the connection. Note that this temp-fails only the current envelope. If there are more envelopes on the current connection, your Milter will still be called for each.

SMFIS_DISCARD

Accept and silently discard the current envelope. Your Milter will not be called again for this envelope but will have xxfi_close() called at the end of the connection. Note that this only discards the current envelope. If there are more envelopes on the current connection, your Milter will still be called for each.

The xxfi_eom() function is the final function called for the current envelope. This is the last opportunity for your Milter to deallocate envelope-specific allocations.

Milter xxfi_header()

Process a header All Milter versions

The message’s headers are sent first during the DATA phase of an SMTP transaction. They are followed by a blank line and then the message’s body. This xxfi_header() function handles each header and stops when there are no more headers to process. After that, the xxfi_eoh() function, if used, is called.

The xxfi_header() function is called like this:

sfsistat
xxfi_header(SMFICTX *ctx,  char *name,  char *value)

Here, ctx is the context pointer passed to all xxfi_ functions to maintain state in a multithreaded environment. The name is a pointer to a string that contains the name of the header (the part to the left of the colon), and value is the value of the header (the part to the right of the colon):

name: value

The name may be an empty string, but will never be NULL. The value may be an empty string, but will never be NULL. When a header occupies more than one line, the header is unfolded by sendmail and supplied to your Milter in unfolded form. For example, consider this value from a Received: header:

from example.com (mx.example.com [12.34.56.78])\r\n\tby your.domain with ESMTP id i08K
jvWt014695\r\n\tfor <bob@your.domain>; Fri, 14 Dec 2007 13:46:10 −0700

Here, the indentation character (a tab) is represented as \t, and a carriage-return/linefeed pair is represented as \r\n.

Before your Milter receives a header, sendmail has already reviewed that header for any values that are out of bounds or are illegal. If, for example, sendmail finds an absurdly long header, it will truncate that header’s value before passing it to your Milter.

Prior to V8.14, sendmail did not strip the high bit from header-value characters that had the high bit set. Beginning with V8.14, sendmail strips the high bit from header values before passing them to your Milter.

Prior to V8.14, the value of each header had its leading spaces removed before they were passed to your Milter:

To:            <bob@example.com>         became     "<bob@example.com>"

But beginning with V8.14, if your Milter enables the SMFIP_HDR_LEADSPC protocol during its xxfi_negotiate() function (Milter xxfi_negotiate() on page 1220), any leading spaces are preserved:

To:            <bob@example.com>         becomes     "            <bob@example.com>"

The xxfi_header() function can return any of several values that determine the further handling of the current envelope:

SMFIS_CONTINUE

Tentatively accept the current header and continue handling additional headers, as available. This is the default return value if you don’t declare a header handler in smfiDesc (Milter smfi_register() on page 1194).

SMFIS_ACCEPT

Accept the current header and thereby the entire envelope. Your Milter will not be called again for this envelope but will have xxfi_close() called at the end of the connection. Note that despite your acceptance, this envelope may still be rejected by a later Milter. Also note that this accepts only the current envelope. If there are more envelopes on the current connection, your Milter will still be called for each.

SMFIS_REJECT

Reject the current header, and thus the entire envelope (and thereby the final dot command with a 5yz code). Your Milter will not be called again for this envelope but will have xxfi_close() called at the end of the connection. Note that this rejects only the current envelope. If there are more envelopes on the current connection, your Milter will still be called for each.

SMFIS_TEMPFAIL

Temp-fail the current header, and thus the entire envelope (and thereby the final dot command with a 4yz code). Your Milter will not be called again for this envelope but will have xxfi_close() called at the end of the connection. Note that this temp-fails only the current envelope. If there are more envelopes on the current connection, your Milter will still be called for each.

SMFIS_DISCARD

Accept and silently discard the current header, and thus the entire envelope. Your Milter will not be called again for this envelope but will have xxfi_close() called at the end of the connection. Note that this only discards the current envelope. If there are more envelopes on the current connection, your Milter will still be called for each.

SMFIS_NOREPLY (V8.14 sendmail and later)

Do not communicate any decision back to sendmail. Note that if you elect to return SMFIS_NOREPLY, you must only return SMFIS_NOREPLY and must first use xxfi_negotiate() (Milter xxfi_negotiate() on page 1220) to let the library know your intention.

Note that rejecting a header rejects the entire DATA phase of the SMTP envelope. Subsequent Milters, if any, will not be given that DATA phase for review. If your xxfi_header() function returns SMFIS_CONTINUE, and if a later Milter or sendmail has rejected the envelope, your Milter’s xxfi_abort() function will be called. But if your Milter final-accepts, rejects, temporarily fails, or discards the envelope, your Milter’s xxfi_abort() function will not be called.

Milter xxfi_helo()

Process a HELO/EHLO command All Milter versions

After the client connects to the listening sendmail server, and after sendmail has sent its 220 greeting, the client will usually send a HELO or EHLO command to greet sendmail and to declare its use of ESMTP extensions:

220 your.host.domain ESMTP Sendmail 8.14.1/8.14.1; Fri, 12 Dec 2007 06:06:10 −0800
(PST)
HELO client name heredo not use ESMTP extensions
EHLO client name hereuse ESMTP extensions

After the client has sent its HELO or EHLO greeting, and before sendmail replies to that greeting, your xxfi_helo() function, if used, is called.

The xxfi_helo() function is called like this:

sfsistat
xxfi_helo(SMFICTX *ctx, char *helohost)

Here, ctx is the context pointer passed to all xxfi_ functions to maintain state in a multithreaded environment. The helohost is the client’s hostname as supplied along with the HELO or EHLO greeting. It is a zero-terminated string that contains the literal text supplied by the client to the HELO or EHLO greeting. That text should be a canonical hostname, but may turn out to be almost anything, so your xxfi_helo() function should practice defensive programming:

""
"foo"
"bob.is.a.happy.boy"

The HELO or EHLO command is optional. The connecting client may elect to omit sending this command and instead skip ahead and send the MAIL From: command. Thus, your xxfi_helo() may not be called for any given connection. If you wish to ensure that it is called, set one of the following in your mc configuration file:

define(`confPRIVACY_FLAGS',`goaway')               ← §24.9.86.2 on page 1066
define(`confPRIVACY_FLAGS',`needmailhelo')         ← §24.9.86.6 on page 1067

The HELO or EHLO command may be sent multiple times during a given connection. If it is, it resets the connection inside sendmail. Your xxfi_helo() function should be prepared to be called multiple times during any given connection.

The values the xxfi_helo() function can return and their meanings are:

SMFIS_CONTINUE

Allow the current connection and continue handling it. This is the default return value if you don’t declare a HELO/EHLO handler in smfiDesc (Milter smfi_register() on page 1194).

SMFIS_ACCEPT (and SMFIS_DISCARD)

Allow the current connection. Your Milter will not be called again for this connection until the connection terminates and your xxfi_close() function (Milter xxfi_close() on page 1208), if used, is called.

SMFIS_REJECT

Reject the HELO/EHLO command. The connecting client is given a 250 reply and all subsequent commands from that connecting client are each given a 550 reply, except for QUIT (given a 221 reply) and NOOP (given a 250 reply). Your Milter will not be called again for this connection, except that xxfi_close() will be called when the connection closes. Later Milters will not get a chance to review this connection.

SMFIS_TEMPFAIL

Temp-fail (with a 4yz SMTP code) the HELO/EHLO command. All subsequent commands are also temp-failed, except for QUIT (given a 221 reply) and NOOP (given a 250 reply). Your Milter will not be called again for this connection, except that xxfi_close() will be called when the connection closes. Later Milters will not get a chance to review this connection.

SMFIS_NOREPLY (V8.14 sendmail and later)

Do not communicate any decision back to sendmail. Note that if you elect to return SMFIS_NOREPLY, you must only return SMFIS_NOREPLY and must first use xxfi_negotiate() (Milter xxfi_negotiate() on page 1220) to let the library know your intention.

Note that the xxfi_helo() function is not told which of the HELO or EHLO greetings was given, nor does your Milter know what, if any, ESMTP extensions were offered.

Milter xxfi_negotiate()

Redefine one’s abilities at runtime V8.14 and later

Prior to V8.14, a Milter declared its intentions once from main() by calling the smfi_register() routine (Milter smfi_register() on page 1194). The arguments passed to smfi_register() are of type struct smfiDesc and look, in part, like this:

struct smfiDesc
{
    char            *name;
    int             version;
    unsigned long             flags;
    sfsistat             funct;        /* for xxfi_connect */
    sfsistat             funct;        /* for xxfi_helo */
    sfsistat             funct;        /* for xxfi_envfrom */
    sfsistat             funct;        /* for xxfi_envrcpt */
    sfsistat             funct;        /* for xxfi_header */
... etc.

Here, the flags state your intention to perform selected actions, such as to remove recipients, or to replace headers. Each of the funct lines provides a pointer to a function that will handle that phase of the SMTP Milter conversation. If the second funct, for example, were expressed as NULL, the xxfi_ function that handles HELO/EHLO will not be called. But if that second funct were instead a function name, such as the name xxfi_helo() or myDoHelo(), that function will be called to handle the HELO/EHLO phase of the SMTP transaction.

Beginning with V8.14 sendmail, more functions may be called at additional points in the SMTP conversation, and more flags may be set than with earlier sendmail versions. This can lead to problems when a single Milter is connected to by multiple sendmail servers (perhaps across a network of MTAs). One sendmail may be V8.14 and able to recognize the SMFIS_SKIP flag so that the xxfi_body() function can stop processing body parts but still have its xxfi_eom() function called. But another sendmail, like V8.13, lacks that new ability. This leads to the question: how, at runtime, is a Milter to know the capabilities possessed by each sendmail that connects to it?

Beginning with V8.14, each time an inbound connection begins and before the Milter library calls your xxfi_connect() function (Milter xxfi_connect() on page 1209), the Milter library calls this xxfi_negotiate() function. This xxfi_negotiate() function allows your Milter to redefine its capabilities at runtime.

The xxfi_negotiate() function is called like this:

sfsistat
xxfi_negotiate(SMFICTX *ctx,
        unsigned long flags, unsigned long proto,
        unsigned long x1, unsigned long x2,
        unsigned long *flagsp, unsigned long *protop,
        unsigned long *x1p, unsigned long *x2p)
{

Here, ctx is the context pointer passed to all xxfi_ functions to maintain state in a multithreaded environment. The ctx is followed by four unsigned long variables used by your Milter to receive information, and then four pointers to unsigned long variables to return information. The variables x1 and x2 are reserved for future expansion and may safely be ignored. The two variables x1p and x2p, are also reserved for future expansion, but must be set to zero if any value other than SMFIS_ALL_OPTS is returned.

The flags is the flags you specified to the Milter library when you earlier called the smfi_register() routine (Milter smfi_register() on page 1194), with one difference. The list of flags set in this variable represents those that the connecting sendmail supports. Because these flags are bits in a bit-field variable, you test them using the bitwise AND operator (&):

        if ((flags & SMFIF_QUARANTINE) != 0)
            /* this flag is available */

The flags argument corresponds to the flagsp argument. Any bits passed to your Milter in flags, that you wish to use as part of the current connection’s processing, you set in flagsp using the bitwise OR operator (|):

        *flagsp = 0L;
        if ((flags & SMFIF_QUARANTINE) != 0)
            *flagsp |= SMFIF_QUARANTINE;

This tells the Milter library that your xxfi_eom() function may want to quarantine an envelope by returning SMFIF_QUARANTINE.

Note, however, that the Milter library provides no mechanism for automatically saving the flags setting for your later examination. Instead, you must save them in a private data structure and retrieve them as needed using the smfi_getpriv() routine (Milter smfi_getpriv() on page 1189).

The proto and protop arguments specify the protocol settings available with the connecting sendmail program. These are also bits in a bit-field. The names of the bits and the meaning of each are shown in Table 26-17.

Table 26-17. Symbolic names for protocol settings supported in xxfi_negotiate()

Macro

Description

SMFIP_RCPT_REJ

The Milter requests that the MTA should, in addition to sending a good recipient, also send any recipients rejected at the RCPT To: command because the user is unknown (or for similar reasons), but to not send recipients rejected because of syntax, and similar, errors. If your Milter sets this flag, its xxfi_envrcpt() routine should receive and check the ${rcpt_mailer} macro’s value (${rcpt_mailer} on page 843). If that value is the literal error, the recipient was rejected by the MTA. Note that for each recipient error, the ${rcpt_host} (${rcpt_host} on page 843) and ${rcpt_addr} (${rcpt_addr} on page 842) macros, respectively, contain the enhanced status code and error text.

SMFIP_SKIP

Set this bit to allow the xxfi_body() function to return SMFIS_SKIP. Recall that SMFIS_SKIP tells sendmail that your Milter desires no more body chunks, but still wants its xxfi_eom() function called normally.

SMFIP_NR_CONN

The sendmail program understands the SMFIS_NOREPLY return code, (see after this table), and your Milter’s xxfi_connect() function will return that code.

SMFIP_NR_HELO

The sendmail program understands the SMFIS_NOREPLY return code (see after this table), and your Milter’s xxfi_helo() function will return that code.

SMFIP_NR_MAIL

The sendmail program understands the SMFIS_NOREPLY return code (see after this table), and your Milter’s xxfi_envfrom() function will return that code.

SMFIP_NR_RCPT

The sendmail program understands the SMFIS_NOREPLY return code (see after this table), and your Milter’s xxfi_envrcpt() function will return that code.

SMFIP_NR_DATA

The sendmail program understands the SMFIS_NOREPLY return code (see after this table), and your Milter’s xxfi_data() function will return that code.

SMFIP_NR_UNKN

The sendmail program understands the SMFIS_NOREPLY return code (see after this table), and your Milter’s xxfi_unknown() function will return that code.

SMFIP_NR_EOH

The sendmail program understands the SMFIS_NOREPLY return code (see after this table), and your Milter’s xxfi_eoh() function will return that code.

SMFIP_NR_BODY

The sendmail program understands the SMFIS_NOREPLY return code (see after this table), and your Milter’s xxfi_body() function will return that code.

SMFIP_NR_HDR

The sendmail program understands the SMFIS_NOREPLY return code (see after this table), and your Milter’s xxfi_header() function will return that code.

SMFIP_HDR_LEADSPC

The sendmail program can send header values with leading spaces preserved, and if so, will not add leading spaces to headers when they are added, inserted, or changed.

SMFIP_NOCONNECT

Set this bit to prevent the xxfi_connect() function from being called, even though you already declared in struct smfiDesc that it should be called.

SMFIP_NOHELO

Set this bit to prevent the xxfi_helo() function from being called, even though you already declared in struct smfiDesc that it should be called.

SMFIP_NOMAIL

Set this bit to prevent the xxfi_envfrom() function from being called, even though you already declared in struct smfiDesc that it should be called.

SMFIP_NORCPT

Set this bit to prevent the xxfi_envrcpt() function from being called, even though you already declared in struct smfiDesc that it should be called.

SMFIP_NOBODY

Set this bit to prevent the xxfi_body() function from being called, even though you already declared in struct smfiDesc that it should be called.

SMFIP_NOHDRS

Set this bit to prevent the xxfi_header() function from being called, even though you already declared in struct smfiDesc that it should be called.

SMFIP_NOEOH

Set this bit to prevent the xxfi_eoh() function from being called, even though you already declared in struct smfiDesc that it should be called.

SMFIP_NOUNKNOWN

Set this bit to prevent the xxfi_unknown() function from being called, even though you already declared in struct smfiDesc that it should be called.

SMFIP_NODATA

Set this bit to prevent the xxfi_data() function from being called, even though you already declared in struct smfiDesc that it should be called.

The symbolic bits whose names begin with SMFIP_NR_ define the ability for the corresponding xxfi_ function to not reply to sendmail. Normally, each xxfi_ function completes its work and returns a decision to sendmail in the form of a reply code (see Table 26-14 on page 1204). The net effect is that sendmail waits for your Milter to complete work in the xxfi_ functions, but there may be some functions, at times, for which there is no need to wait. An xxfi_envrcpt() function, for example, may only count recipients for later use, and normally never returns anything other than SMFIS_CONTINUE, even if there is an error. Such an xxfi_ function is a good candidate to return SMFIS_NOREPLY.

Note, however, that the Milter library provides no mechanism for automatically saving protocol settings for your later examination. Instead, you must save them in a private data structure and retrieve them as needed using the smfi_getpriv() routine (Milter smfi_getpriv() on page 1189).

The proto and protop settings that begin with SMFIP_NO state that an xxfi_ function that would otherwise be called should not be called for this connection. To illustrate, consider an xxfi_body() function that only ever examines the first chunk of a message. This function should be called for V8.14 and later sendmail connections, but not for earlier versions of sendmail (that do not understand the SMFIP_SKIP return code):

        *protop = 0L;
        if ((proto & SMFIP_SKIP) == 0)
            *protop |= SMFIP_NOBODY;

Here, the passed proto argument omitted the SMFIP_SKIP flag, which indicates that the connecting sendmail lacks that capability. The protop argument is updated with the SMFIP_NOBODY bit, which tells the Milter library not to call the xxfi_body() function for this connection.

Note that any xxfi_ function you omitted from the initial smfiDesc structure cannot be called, even if you omit an SMFIP_NO bit for that function.

Also note that the Milter library provides no mechanism for automatically saving protocol settings for your later examination. Instead, you must save them in a private data structure and retrieve them as needed using the smfi_getpriv() routine (Milter smfi_getsymval() on page 1194).

This xxfi_negotiate() function can return one of three values shown in Table 26-18.

Table 26-18. Return values from xxfi_negotiate()

Macro

Description

SMFIS_ALL_OPTS

If your Milter wishes to inspect the flags settings and protocol settings, but does not wish to pass back any settings of its own, it may return this value (which is the default if xxfi_negotiate() is not called).

SMFIS_REJECT

Decline to process the current connection. This Milter will not be contacted again for this connection.

SMFIS_CONTINUE

The Milter has, and must, set the output flags in the four pointer arguments. Any not used must be set to a value of 0L prior to the return.

Note that even though earlier Milter libraries lacked this xxfi_negotiate() function, those earlier Milters will still build and run just fine when linked with the newest Milter library.

Milter xxfi_unknown()

Handle unknown SMTP commands V8.14 and later

An unknown SMTP command is one that is either undefined by the standards, or currently not supported by the sendmail connecting to your Milter. In this case, the sendmail program always rejects unknown SMTP commands:

500 5.5.1 Command unrecognized: "bob's your uncle"

But beginning with V8.14, you may elect to access those unknown commands, and optionally change how they are rejected, by using this xxfi_unknown() function.

The xxfi_unknown() function is called like this:

sfsistat
xxfi_unknown(SMFICTX *ctx, char *badcmd)

Here, ctx is the context pointer passed to all xxfi_ functions to maintain state in a multithreaded environment. The badcmd is the literal bad text supplied to sendmail. It is a zero-terminated string. That text may be anything, including control and other characters, so be certain to practice defensive programming:

""
"^C"
"select * from passwd;"
"GET /"

This xxfi_unknown() function, if used, may be called multiple times during any given connection. The values the xxfi_unknown() function can return and their meanings are:

SMFIS_CONTINUE

Reject the unknown command in the normal manner. This is the default return value if you don’t declare an unknown-command handler in smfiDesc (Milter smfi_register() on page 1194).

SMFIS_REJECT

Reject the unknown command. This has the same effect as SMFIS_CONTINUE.

SMFIS_ACCEPT

This has the same effect as SMFIS_CONTINUE.

SMFIS_TEMPFAIL

Temp-fail the unknown command (with a 4yz SMTP code).

SMFIS_DISCARD

This has the same effect as SMFIS_CONTINUE, but the message is discarded.

SMFIS_NOREPLY (V8.14 sendmail and later)

Do not communicate any decision back to sendmail. Note that if you elect to return SMFIS_NOREPLY, you must only return SMFIS_NOREPLY and must first use xxfi_negotiate() (Milter xxfi_negotiate() on page 1220) to let the library know your intention.

The xxfi_unknown() function may be called during any phase of the SMTP transaction, so it is not specific to the connection phase, the envelope phase, or to the recipient phase. But despite its limitations, xxfi_unknown() can provide a valuable hook into understanding the types of attacks possible using SMTP, because sendmail normally does not log those rejections.



[442] * If you connect to port 25 to send outbound email, that mail can also be Milter-screened.

[443] * The location of sendmail can vary based on the version of Unix you are running.

[444] For Linux, the sendmail rpm package includes a prebuilt libmilter.

[445] * Use _FFR_MILTER with V8.10 or V8.11 sendmail.

[446] * When sendmail handles inbound email, the Milter’s socket must already exist. This is one reason why all Milters should be started before sendmail is started.

[447] * The T=C was added in V8.12 sendmail and was not available earlier.

[448] * Only trace headers, X- headers, and Milter-added headers may exist in multiple occurrences.

[449] * Solaris does not honor the permissions of Unix domain sockets, so place the socket in a protected directory.

[450] Levels higher than six are interpreted the same as six.

[451] Don’t count on this behavior, because smfi_setdbg() may return informative errors in a future release.

[452] * The Milter library has no idea if you have referenced string constants or used zero memory pointers. For the Milter library to undertake your job would be hazardous in the extreme.

[453] * You should try to allocate several times with a sleep(3) between each, just in case the problem is transient.

[454] * The body chunk may also contain interior zero values.