NAME
miltertest -- milter unit test utility
SYNOPSIS
miltertest [-D name[=value]] [-s script] [-v] [-V]
DESCRIPTION
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.
-v Increase verbose output. May be specified multiple times to
request more and more information.
-V Print version number and exit.
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)
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.
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. 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.
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.
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.
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.
EXAMPLE
-- 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, "j", "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.bodystring() unexpected reply"
end
-- verify that a signature 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.
VERSION
This man page covers version 1.2.2 of miltertest.
COPYRIGHT
Copyright (c) 2009, 2010, The OpenDKIM Project. All rights reserved.
SEE ALSO
Milter -- http://www.milter.org
Lua -- http://www.lua.org
The OpenDKIM Project miltertest(8)