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.
|
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 sendmail.org know by visiting this page:
| http://www.sendmail.org/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.
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/nullIf 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
↑
noteIf 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.
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 hereThe 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.
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.
|
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). |
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' )
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.
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).
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=valueThe recognized key letters and their meanings are shown in Table 26-3.
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 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 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.
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.
|
Key letter |
Default |
Description |
|
|
5 minutes |
Overall timeout from sending EOM to Milter to final EOM reply |
|
|
10 seconds |
Timeout for reading reply from the Milter |
|
|
10 seconds |
Timeout for sending information from the MTA to a Milter |
|
|
5 minutes |
Connection timeout |
The form for each key letter looks like this:
letter:valueSpace 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 :valueFor 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).
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).
|
Milter |
Screens what |
|
|
Connection information, such as hostname and IP address |
|
|
Connection information, such as hostname and IP address |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The message headers |
|
|
The message body |
|
|
The end of a message (a semaphore) |
|
|
The message headers |
|
|
The message body |
|
|
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 definedNote 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.
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.
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 laterIn 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.
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.
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=level← configuration file -OMilter.loglevel=level← command 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.
|
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 |
|
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. |
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.
|
mc option |
Configuration option |
§ |
|
|
|
Milter.macros.connect on page 1054 |
|
|
|
Milter.macros.envfrom on page 1054 |
|
|
|
Milter.macros.envrcpt on page 1055 |
|
|
|
Milter.macros.data on page 1055 |
|
|
|
Milter.macros.eoh on page 1056 |
|
|
|
Milter.macros.eom on page 1056 |
|
|
|
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.
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:
Review based on the IP address and hostname of the connecting site
Review based on the hostname given as part of the SMTP HELO or EHLO command
Review the envelope sender as supplied as part of the SMTP MAIL From: command
Review the envelope recipient as supplied as part of the SMTP RCPT To: command
Review the header portion of the email message
Review the SMTP DATA command
Signals the end of the header portion of the message
Review the message body, which can include MIME-encoded portions
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:
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:
A guide to and discussions about MILTERs in general.
The vilter program scans incoming email and rejects or flags the infected messages with a header line.
The amavis program is a mail virus scanner.
The vbsfilter program
will rename a variety of executable attachments to
.txt, thus
rendering them harmless.
Sendmail, Inc. offers several commercial Milters.
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.
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.
|
smfi routine |
§ |
Description |
|
|
Milter smfi_addheader() on page 1184 |
Conditionally add a header. |
|
|
Milter smfi_addrcpt() on page 1185 |
Add envelope recipient. |
|
|
Milter smfi_addrcpt_par() on page 1186 |
Add envelope recipient with ESMTP arguments (V8.14 and later). |
|
|
Milter smfi_chgfrom() on page 1187 |
Change envelope sender with ESMTP arguments (V8.14 and later). |
|
|
Milter smfi_chgheader() on page 1188 |
Change or delete a header. |
|
|
Milter smfi_delrcpt() on page 1189 |
Delete envelope recipient. |
|
|
Milter smfi_getpriv() on page 1189 |
Fetch private context data pointer. |
|
|
Milter smfi_getsymval() on page 1190 |
Fetch a sendmail macro’s value. |
|
|
Milter smfi_insheader() on page 1192 |
Unconditionally insert a header (V8.13 and later). |
|
|
Milter smfi_main() on page 1193 |
Run the Milter. |
|
|
Milter smfi_opensocket() on page 1193 |
Create the interface socket (V8.13 and above). |
|
|
Milter smfi_progress() on page 1193 |
Report operation in progress (V8.13 and above). |
|
|
Milter smfi_quarantine() on page 1194 |
Quarantine a message (V8.13 and above). |
|
|
Milter smfi_register() on page 1194 |
Declare which |
|
|
Milter smfi_replacebody() on page 1196 |
Replace message body. |
|
|
Milter smfi_setbacklog() on page 1197 |
Set the listen(2) queue size (V8.13 and above). |
|
|
Milter smfi_setconn() on page 1197 |
Specify the interface socket to use. |
|
|
Milter smfi_setdbg() on page 1198 |
Set the debugging level (V8.13 and above). |
|
|
Milter smfi_setmlreply() on page 1198 |
Set a multiline SMTP reply code (V8.13 and above). |
|
|
Milter smfi_setpriv() on page 1199 |
Initialize private context data pointer. |
|
|
Milter smfi_setreply() on page 1200 |
Set SMTP code and reply text. |
|
|
Milter smfi_setsymlist() on page 1201 |
Request macros to be sent (V8.14 and later). |
|
|
Milter smfi_settimeout() on page 1202 |
Set sendmail to Milter time out. |
|
|
Milter smfi_stop() on page 1202 |
Cause a controlled shutdown (V8.13 and above). |
|
|
Milter smfi_version() on page 1203 |
Fetch version of the runtime library (V8.14 and later). |
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=NOIf 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 headerexistingvalue withnewvalue
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.
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 tooAdd 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.
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.
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.
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.
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.
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 namesThe 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.
|
Macro |
Macro described |
Function |
Function described |
|
no defaults |
|
Milter xxfi_data() on page 1210 | |
|
no defaults |
|
Milter xxfi_eoh() on page 1214 | |
|
_ |
$_ on page 801 |
|
Milter xxfi_connect() on page 1209 |
|
|
${auth_authen} on page 804 |
|
Milter xxfi_envfrom() on page 1211 |
|
|
${auth_author} on page 805 |
|
Milter xxfi_envfrom() on page 1211 |
|
|
${auth_ssf} on page 806 |
|
Milter xxfi_envfrom() on page 1211 |
|
|
${auth_type} on page 806 |
|
Milter xxfi_envfrom() on page 1211 |
|
|
${cert_issuer} on page 809 |
|
Milter xxfi_helo() on page 1218 |
|
|
${cert_subject} on page 809 |
|
Milter xxfi_helo() on page 1218 |
|
|
${cipher} on page 809 |
|
Milter xxfi_helo() on page 1218 |
|
|
${cipher_bits} on page 810 |
|
Milter xxfi_helo() on page 1218 |
|
|
${daemon_name} on page 819 |
|
Milter xxfi_connect() on page 1209 |
|
|
$i on page 826 |
|
Milter xxfi_envfrom() on page 1211 |
|
|
${if_addr} on page 827 |
|
Milter xxfi_connect() on page 1209 |
|
|
${if_name} on page 828 |
|
Milter xxfi_connect() on page 1209 |
|
|
$j on page 830 |
|
Milter xxfi_connect() on page 1209 |
|
|
${mail_addr} on page 833 |
|
Milter xxfi_envfrom() on page 1211 |
|
|
${mail_host} on page 833 |
|
Milter xxfi_envfrom() on page 1211 |
|
|
${mail_mailer} on page 834 |
|
Milter xxfi_envfrom() on page 1211 |
|
|
${msg_id} on page 834 |
|
Milter xxfi_eom() on page 1215 |
|
|
${rcpt_addr} on page 842 |
|
Milter xxfi_envrcpt() on page 1213 |
|
|
${rcpt_host} on page 843 |
|
Milter xxfi_envrcpt() on page 1213 |
|
|
${rcpt_mailer} on page 843 |
|
Milter xxfi_envrcpt() on page 1213 |
|
|
${tls_version} on page 847 |
|
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.
|
Option |
§ |
|
|
Milter.macros.connect on page 1054 |
|
|
Milter.macros.data on page 1055 |
|
|
Milter.macros.envfrom on page 1054 |
|
|
Milter.macros.envrcpt on page 1055 |
|
|
Milter.macros.eoh on page 1056 |
|
|
Milter.macros.eom on page 1056 |
|
|
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}'')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 hereOmitting 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.
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).
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).
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:
smfi_progress(ctx);... a great deal of processing... 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.
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 hereNote 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.
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.
|
Flag |
Description |
|
|
This Milter may call |
|
|
This Milter may call |
|
|
This Milter may call |
|
|
This Milter may call |
|
|
This Milter may call |
|
|
This Milter may call |
|
|
This Milter may call |
|
|
This Milter may call |
|
|
This Milter may call use |
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.
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.
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.
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.
|
Prefix |
Description |
|
|
The socket will be a named pipe, the path to
which is specified, as for example, |
|
|
A synonym for |
|
|
The socket is for an IPv4 TCP/IP connection
which is specified as either a hostname or an IP
address, as, for example, |
|
|
The socket is for an IPv6 TCP/IP connection
which is specified as either a hostname or an IP
address, as, for example, |
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]
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.
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.
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.
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.
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.
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.
|
Value for stage |
Function called |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 goodThe 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.
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.
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().
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.
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.
|
sfsistat return value |
Description |
|
|
Continue processing the current connection, envelope, or recipient. |
|
|
For connection-oriented routines, reject
this connection (your |
|
|
Envelope-oriented and recipient-oriented
functions cause the enveloped to be accepted, but
silently discarded. Note that this |
|
|
Connection-oriented routines cause the
current connection to be accepted (your Milter
will only have |
|
|
Causes the corresponding SMTP command to
return a 4xx status code, which normally
temp-fails the command. All envelope-oriented
functions except |
|
|
(V8.14 and later) Currently this return
value is only allowed from within |
|
|
(V8.14 and later) Tell the MTA to not block
waiting for a reply from this Milter. If an
|
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.
|
Macro |
§ |
Description |
|
|
Milter xxfi_negotiate() on page 1220 |
Change your Milter’s relationship with sendmail dynamically at runtime (V8.14 and later) |
|
|
Milter xxfi_connect() on page 1209 |
Called once, upon initial connection by the sending site to the listening sendmail daemon |
|
|
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 |
|
|
Milter xxfi_envfrom() on page 1211 |
Called once per envelope, just after the
sending site sends its |
|
|
Milter xxfi_envrcpt() on page 1213 |
Called multiple times, once each time just
after the sending site sends one of its |
|
|
Milter xxfi_data() on page 1210 |
Called once when the DATA command is received (V8.14 and later) |
|
|
Milter xxfi_unknown() on page 1223 |
Called multiple times, once for each unknown SMTP command received (V8.14 and later) |
|
|
Milter xxfi_header() on page 1217 |
Called multiple times, once for each header that is received |
|
|
Milter xxfi_eoh() on page 1214 |
Called once per envelope, after all the headers have been received |
|
|
Milter xxfi_body() on page 1207 |
Called multiple times, once for each piece of the message’s body |
|
|
Milter xxfi_eom() on page 1215 |
Called once per envelope, after the entire
body has been received (either |
|
|
Milter xxfi_abort() on page 1206 |
Called once, if the message was rejected
outside the current Milter (either |
|
|
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.
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.
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_tlen)
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_CONTINUEAllow 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_ACCEPTAccept the current body chunk and thereafter the current envelope. Your Milter will not be called again for this envelope.
SMFIS_REJECTReject 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_DISCARDAccept 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_TEMPFAILTemp-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.
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.
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_CONTINUEAllow 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_ACCEPTAccept 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_REJECTReject 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_TEMPFAILTemp-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.
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_CONTINUEAllow 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_ACCEPTAccept the DATA command and thus the current
envelope. Your Milter will not be called again for
this envelope.
SMFIS_REJECTReject 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_DISCARDAccept 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_TEMPFAILTemp-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.
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_CONTINUEAllow 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_ACCEPTAllow the MAIL
From: command, and thus the current
envelope. Your Milter will not be called again for
this envelope.
SMFIS_REJECTReject 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_DISCARDAccept 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_TEMPFAILTemp-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.
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_CONTINUEAllow 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_ACCEPTAllow the RCPT
To: command and thus the current
recipient. Your Milter will still be called again
for the next recipient, if any.
SMFIS_REJECTReject 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_DISCARDAccept 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_TEMPFAILTemp-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.
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_CONTINUEAllow 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_ACCEPTAccept 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_REJECTReject 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_TEMPFAILTemp-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_DISCARDAccept 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.
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.
|
Routine |
§ |
Flag required (see Table 26-11 on page 1195) |
|
|
Milter smfi_addheader() on page 1184 |
|
|
|
Milter smfi_addrcpt() on page 1185 |
|
|
|
Milter smfi_addrcpt_par() on page 1186 |
|
|
|
Milter smfi_chgfrom() on page 1187 |
|
|
|
Milter smfi_chgheader() on page 1188 |
|
|
|
Milter smfi_delrcpt() on page 1189 |
|
|
|
Milter smfi_insheader() on page 1192 |
|
|
|
Milter smfi_progress() on page 1193 |
No flags required. |
|
|
Milter smfi_quarantine() on page 1194 |
|
|
|
Milter smfi_replacebody() on page 1196 |
|
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_ACCEPTAccept
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_REJECTReject 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_TEMPFAILTemp-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_DISCARDAccept 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.
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_CONTINUETentatively 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_ACCEPTAccept 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_REJECTReject 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_TEMPFAILTemp-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_DISCARDAccept 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.
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) HELOclient name here← do not use ESMTP extensions EHLOclient name here← use 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_CONTINUEAllow 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_REJECTReject 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_TEMPFAILTemp-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.
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 longflags, unsigned longproto, unsigned longx1, unsigned longx2, 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.
|
Macro |
Description |
|
|
The Milter requests that the MTA should, in
addition to sending a good recipient, also send
any recipients rejected at the |
|
|
Set this bit to allow the |
|
|
The sendmail program
understands the |
|
|
The sendmail program
understands the |
|
|
The sendmail program
understands the |
|
|
The sendmail program
understands the |
|
|
The sendmail program
understands the |
|
|
The sendmail program
understands the |
|
|
The sendmail program
understands the |
|
|
The sendmail program
understands the |
|
|
The sendmail program
understands the |
|
|
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. |
|
|
Set this bit to prevent the |
|
|
Set this bit to prevent the |
|
|
Set this bit to prevent the |
|
|
Set this bit to prevent the |
|
|
Set this bit to prevent the |
|
|
Set this bit to prevent the |
|
|
Set this bit to prevent the |
|
|
Set this bit to prevent the |
|
|
Set this bit to prevent the |
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.
|
Macro |
Description |
|
|
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
|
|
|
Decline to process the current connection. This Milter will not be contacted again for this connection. |
|
|
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.
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_CONTINUEReject 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_REJECTReject the unknown command. This has the
same effect as SMFIS_CONTINUE.
SMFIS_ACCEPTThis has the same effect as SMFIS_CONTINUE.
SMFIS_TEMPFAILTemp-fail the unknown command (with a 4yz SMTP code).
SMFIS_DISCARDThis 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.