Command Section

miltertest(8)           FreeBSD System Manager's Manual          miltertest(8)

NAME
       miltertest - milter unit test utility

SYNOPSIS
       miltertest [-D name[=value]] [-s script] [-u] [-v] [-V] [-w]

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.

       -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).  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.

       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.  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.

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, "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)

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.5.0 of miltertest.

COPYRIGHT
       Copyright (c) 2009-2014, The Trusted Domain Project.  All rights
       reserved.

SEE ALSO
       Milter -- http://www.milter.org

       Lua -- http://www.lua.org

                          The Trusted Domain Project             miltertest(8)

Command Section

man2web Home...