miltertest - milter unit test utility
miltertest [-D name[=value]] [-s script] [-u] [-v] [-V]
[-w]
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.
- -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.
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). If the environment variable
MILTERTEST_RETRY_SPEED_FACTOR is set and appears to contain an integer,
the value of interval (if set) will be multiplied by the value
found in that environment variable. This is included to allow tests in a
large test suite to be easily adjusted on slow systems without
reconfiguring the entire test suite.
- 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[,
polite]))
- Sends a "quit" message to the specified connection and then
closes that connection. The specified conn handle should no longer
be used. If polite is defined, it must be a Boolean indicating
whether a normal disconnect should be done (true) or an abrupt disconnect
should be done (false). An abrupt disconnect skips standard protocol
shutdown steps.
- 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.
- 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.
- 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.
- mt.macro(conn,
type, name, value[, name2, value2[, ...]])
- 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.
- mt.set_timeout(n)
- Sets the read timeout to n seconds. The default is ten seconds.
Returns nothing.
- mt.sleep(n)
- Sleeps for n seconds. The value may be an integer (for whole
seconds) or a floating-point value (for partial seconds).
- mt.signal(n)
- 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.
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.
-- Echo that the test is starting
mt.echo("*** begin test")
-- start the filter
mt.startfilter("../myfilter", "-p",
"inet:12345@localhost")
mt.sleep(2)
-- try to connect to it
conn = mt.connect("inet:12345@localhost")
if conn == nil then
error "mt.connect() failed"
end
-- send connection information
-- mt.negotiate() is called implicitly
if mt.conninfo(conn, "localhost", "127.0.0.1")
~= nil then
error "mt.conninfo() failed"
end
if mt.getreply(conn) ~= SMFIR_CONTINUE then
error "mt.conninfo() unexpected reply"
end
-- send envelope macros and sender data
-- mt.helo() is called implicitly
mt.macro(conn, SMFIC_MAIL, "i", "test-id")
if mt.mailfrom(conn, "user@example.com") ~= nil then
error "mt.mailfrom() failed"
end
if mt.getreply(conn) ~= SMFIR_CONTINUE then
error "mt.mailfrom() unexpected reply"
end
-- send headers
-- mt.rcptto() is called implicitly
if mt.header(conn, "From", "user@example.com")
~= nil then
error "mt.header(From) failed"
end
if mt.getreply(conn) ~= SMFIR_CONTINUE then
error "mt.header(From) unexpected reply"
end
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)
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.
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.
This man page covers version 1.5.0 of miltertest.
Copyright (c) 2009-2014, The Trusted Domain Project. All rights
reserved.
Milter -- http://www.milter.org
Lua -- http://www.lua.org