miltertest
simulates the MTA side of an MTA-milter interaction for testing a milter-aware
filter application. It takes as input a script using the Lua language,
and by exporting some utility functions, makes it possible for users to
write scripts that exercise a filter.
See documentation on Lua (e.g. http://www.lua.org) for the syntax
of the language in general. The documentation below describes functions
that are added to Lua by this application to make testing possible.
Documentation on milter can be found at http://www.milter.org. A particular
transaction must follow a series of steps to be completed, namely
negotiate, connection information, envelope sender, envelope recipient(s),
header field(s), end-of-header, body chunk(s), end-of-message. To make the
work of writing tests with
miltertest
simpler, any of these steps prior to end-of-message that is skipped will be
filled in using arbitrary, but legal, data.
Interspersed with these protocol phases are optional macro (key/value)
deliveries from the MTA.
miltertest
will never send these automatically. If they are needed for your tests,
you must send them as part of your test script.
OPTIONS
-D name[=value]
Defines a global variable called
name
to the Lua interpreter. If a
value
is provided, the global variable is set to that value (as a string,
although Lua can convert strings to numbers internally). If no
value
is provided, the global variable is set to 1.
-s script
Use the contents of file
script
as the Lua script to be executed. The default is to read from standard
input.
-u
After the filter being tested is terminated, report user and system time
consumed. See
getrusage(2).
-v
Increase verbose output. May be specified multiple times to request
more and more information.
-V
Print version number and exit.
-w
Don't wait for child status to be returned when testing is complete.
FUNCTIONS
The following functions are made available to Lua scripts for exercising
a filter. All functions return Lua constant "nil" on success or an error
string on failure, unless otherwise indicated.
mt.abort(conn)
Aborts the transaction in progress on the specified connection.
mt.bodyfile(conn, file)
Sends the contents of the named
file
to the connection as body data. If there is any error opening
file
for reading, the test aborts.
mt.bodyrandom(conn, n)
Sends at least
n
bytes of random-length lines of random printable ASCII data as body
chunks to the specified connection.
mt.bodystring(conn, str)
Sends
str
as a chunk of body text on the specified connection.
mt.chdir(directory)
Changes the current working directory to the named
directory.
mt.connect(sockinfo[, count, interval])
Makes a connection to a filter listening at the socket described by
sockinfo.
Returns a handle referring to that connection, or the Lua constant
"nil" on error. If
count
and
interval
are included, they specify the number of times to try to connect to the filter
and the delay between each connection in seconds (with floating point values
permitted).
mt.conninfo(conn, host, ip)
Sends information about a new SMTP connection to the MTA, represented by
connection
conn,
from the host named
host
at IP address
ip
(both strings). If
host
is the Lua constant "nil", the string "localhost" is assumed. If
ip
is the Lua constant "nil", a DNS query will be made for the IP address
matching
host;
if none is found, the test will abort. The
ip
may also be the special string "unspec", which will tell the filter that
a connection came in from an unknown protocol family.
mt.data(conn)
Announces the DATA command on the specified connection, which occurs between
the last RCPT TO command and the beginning of the header block.
mt.disconnect(conn)
Sends a "quit" message to the specified connection and then closes that
connection. The specified
conn
handle should no longer be used.
mt.echo(string)
Prints the specified
string
on standard output. Returns nothing.
mt.eoh(conn)
Announces end-of-header on the specified connection.
mt.eom(conn)
Announces end-of-message on the specified connection, and begins capturing
any other operations the filter might perform in that phase.
mt.eom_check(conn, op, param[, ...])
Checks the captured set of EOM operations (see above) to determine whether
or not specific milter actions were requested by the filter. Returns
a Boolean value (true or false). See the EOM CHECKS section for details.
mt.getheader(conn, hdr, n)
Retrieves the value of the
nth
instance of header field named
hdr
added during end-of-message processing on the specified connection.
This can be used by the script to verify that the header thus added contains
the right thing. Returns the value as a string, or the Lua constant
"nil" on error.
mt.getcwd()
Returns the current working directory as a string.
mt.getreply(conn)
Returns the last milter reply received from the specified connection,
as an integer. This can be compared to any of the SMFIR_* constants defined
by milter to see if the filter responded as expected. This value is initially
set to the NULL character.
mt.header(conn, name, value)
Sends the header with the given
name
and
value
to the specified connection.
mt.helo(conn, name)
Sends HELO/EHLO information using the specified
name
as the parameter given.
Declares a macro called
name
whose value is
value
and whose type (matching protocol element) is
type.
Valid types are SMFIC_CONNECT, SMFIC_HELO, SMFIC_MAIL and SMFIC_RCPT. Multiple
macro names and values can be provided, but they must appear in pairs.
mt.mailfrom(conn, envfrom[, ...])
Announces
envfrom
as the envelope sender of a new message. ESMTP parameters as additional
arguments are permitted.
mt.negotiate(conn, version, actions, steps)
Performs milter option negotiation with the connection
conn,
advertising that the specified protocol
version,
protocol
actions
and protocol
steps
are offered by the MTA. Returns the Lua constant "nil" on success
or an error string on failure. If any of the protocol parameters are
"nil", the current defaults (defined in
libmilter/mfdef.h,
provided with the milter source code) will be used.
mt.rcptto(conn, envrcpt[, ...])
Announces
envrcpt
as an envelope recipient of a message. ESMTP parameters as additional
arguments are permitted.
Sends the specified signal number
n
to the running filter.
mt.startfilter(path, arg1, arg2, ...)
Starts the filter whose binary is located at
path
with argument vector comprised of strings
path,arg1,arg2,
etc. Basically this is almost the same syntax as
execl(3)
except that
miltertest
also does the fork for you, and will remember the process ID in order to
request a clean shutdown using SIGTERM and
wait(2)
at the end of the test script. If the filter could not be started, an
exception is generated with an error message returned.
mt.test_action(conn, action)
Tests whether or not the connection represented by
conn
requested the specified milter protocol
action,
specified by an SMFIF_* constant, during option negotiation. (See the
libmilter documentation and/or include files for details.)
mt.test_option(conn, option)
Tests whether or not the connection represented by
conn
requested the specified milter protocol
option,
specified by an SMFIP_* constant, during option negotiation. (See the
libmilter documentation and/or include files for details.)
mt.unknown(conn, str)
Announces that the unknown SMTP command
str
arrived over the connection represented by
conn.
EOM CHECKS
The
mt.eom_check()
function is used to determine what changes to the message the filter
requested during its EOM callback. The changes can be requested
in any order. The first parameter,
op,
indicates what operation is of interest, and it also dictates what the
possible parameter list is. Valid values and corresponding parameters for
op
are as follows:
MT_HDRADD
Checks to see if a header field was added to the message. If no parameters
are given, the function returns true if any header field was added. If
one parameter was given, the function returns true only if the named
header field was added (regardless of its value). If two parameters are
given, the function returns true only if the named header field was added
with the specified value.
MT_HDRCHANGE
Checks to see if an existing header field was changed. If no parameters
are given, the function returns true if any header field was modified. If
one parameter was given, the function returns true only if the named
header field was modified (regardless of its new value). If two parameters
are given, the function returns true only if the named header field was
modified to have the specified new value.
MT_HDRDELETE
Checks to see if an existing header field was deleted. If no parameters
are given, the function returns true if any header field was deleted. If
one parameter was given, the function returns true only if the named
header field was deleted.
MT_HDRINSERT
Checks to see if a header field was inserted into the message. If no
parameters are given, the function returns true if any header field was
added. If one parameter was given, the function returns true only if the
named header field was added (regardless of its value). If two parameters are
given, the function returns true only if the named header field was added
with the specified value. If three parameters are given, the function
returns true only if the named header field was added with the specified
value at the specified index.
MT_RCPTADD
Checks to see if an envelope recipient was added. Currently only one
parameter may be provided.
MT_RCPTDELETE
Checks to see if an envelope recipient was deleted. Currently only one
parameter may be provided.
MT_BODYCHANGE
Checks to see if the message's body was replaced by other content. With
no parameters, the function returns true only if the body was changed
(regardless of the new content). With one parameter, the function returns
true only if the body was changed to the specified new content.
MT_QUARANTINE
Checks to see if the filter requested quarantining of the message.
With no parameters, the function returns true only if quarantine was
requested. With one parameter, the function returns true only if quarantine
was requested with the specified reason string.
MT_SMTPREPLY
Checks to see if the filter requested a specific SMTP reply message.
With no parameters, the function returns true only if a specific reply
was requested. With one parameter, the function returns true only if
a specific reply was requested with the specified SMTP code. With two
parameters, the function returns true only if a specific reply was
requested with the specified SMTP code and enhanced status code. With three
parameters, the function returns true only if a specific reply was
requested with the specified SMTP code, enhanced status code, and text.
if mt.header(conn, "Date", "Tue, 22 Dec 2009 13:04:12 -0800") ~= nil then
error "mt.header(Date) failed"
end
if mt.getreply(conn) ~= SMFIR_CONTINUE then
error "mt.header(Date) unexpected reply"
end
if mt.header(conn, "Subject", "Signing test") ~= nil then
error "mt.header(Subject) failed"
end
if mt.getreply(conn) ~= SMFIR_CONTINUE then
error "mt.header(Subject) unexpected reply"
end
-- send EOH
if mt.eoh(conn) ~= nil then
error "mt.eoh() failed"
end
if mt.getreply(conn) ~= SMFIR_CONTINUE then
error "mt.eoh() unexpected reply"
end
-- send body
if mt.bodystring(conn, "This is a test!\r\n") ~= nil then
error "mt.bodystring() failed"
end
if mt.getreply(conn) ~= SMFIR_CONTINUE then
error "mt.bodystring() unexpected reply"
end
-- end of message; let the filter react
if mt.eom(conn) ~= nil then
error "mt.eom() failed"
end
if mt.getreply(conn) ~= SMFIR_ACCEPT then
error "mt.eom() unexpected reply"
end
-- verify that a test header field got added
if not mt.eom_check(conn, MT_HDRINSERT, "Test-Header") then
error "no header added"
end
-- wrap it up!
mt.disconnect(conn)
NOTES
If a filter negotiates one of the SMFIP_NO* protocol option bits and a
script attempts to perform one of those protocol steps, an error is returned.
It is up to the test author to use
mt.test_option()
function to see if performing a protocol step has been explicitly disabled
by the filter.
MILTER NOTES
When
mt.macro()
is called, it replaces all previous macros of the same
type
with the ones provided in the argument list. Thus, one call should be made
that lists the complete set rather than one call per name-value pair.
Also, as each stage in the milter process is executed, all macros corresponding
stages after the current one are discarded. For example, calling
mt.helo(),
which corresponds to SMFIC_HELO, will cause all prior macros of type
SMFIC_MAIL and SMFIC_RCPT to be discarded as they represent a milter stage
that comes later than SMFIC_HELO.
Since the milter protocol and the internals of libmilter itself are not
formally documented, there are myriad other subtleties of the milter protocol
and implementation that are not documented here and may not be documented
elsewhere, and could change without notice. Caveat emptor.
VERSION
This man page covers version 1.4.0 of
miltertest.
COPYRIGHT
Copyright (c) 2009-2011, The OpenDKIM Project. All rights reserved.