Man Linux: Main Page and Category List

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)