1miltertest(8)               System Manager's Manual              miltertest(8)
2
3
4

NAME

6       miltertest - milter unit test utility
7

SYNOPSIS

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

DESCRIPTION

12       miltertest  simulates  the  MTA  side  of an MTA-milter interaction for
13       testing a milter-aware filter application.  It takes as input a  script
14       using  the Lua language, and by exporting some utility functions, makes
15       it possible for users to write scripts that exercise a filter.
16
17       See documentation on Lua (e.g. http://www.lua.org) for  the  syntax  of
18       the  language  in general.  The documentation below describes functions
19       that are added to Lua by this application to make testing possible.
20
21       Documentation on milter can be found at http://www.milter.org.  A  par‐
22       ticular  transaction  must  follow  a  series of steps to be completed,
23       namely negotiate, connection  information,  envelope  sender,  envelope
24       recipient(s),  header  field(s),  end-of-header, body chunk(s), end-of-
25       message.  To make the work of writing tests  with  miltertest  simpler,
26       any  of  these  steps  prior  to end-of-message that is skipped will be
27       filled in using arbitrary, but legal, data.
28
29       Interspersed with these protocol phases are optional macro  (key/value)
30       deliveries  from  the  MTA.  miltertest will never send these automati‐
31       cally.  If they are needed for your tests, you must send them  as  part
32       of your test script.
33

OPTIONS

35       -D name[=value]
36              Defines  a  global  variable called name to the Lua interpreter.
37              If a value is provided, the global variable is set to that value
38              (as a string, although Lua can convert strings to numbers inter‐
39              nally).  If no value is provided, the global variable is set  to
40              1.
41
42       -s script
43              Use  the  contents  of  file script as the Lua script to be exe‐
44              cuted.  The default is to read from standard input.
45
46       -u     After the filter being tested is  terminated,  report  user  and
47              system time consumed.  See getrusage(2).
48
49       -v     Increase  verbose  output.   May  be specified multiple times to
50              request more and more information.
51
52       -V     Print version number and exit.
53
54       -w     Don't wait for child status to be returned when testing is  com‐
55              plete.
56

FUNCTIONS

58       The  following functions are made available to Lua scripts for exercis‐
59       ing a filter.  All functions return Lua constant "nil" on success or an
60       error string on failure, unless otherwise indicated.
61
62       mt.abort(conn)
63              Aborts the transaction in progress on the specified connection.
64
65       mt.bodyfile(conn, file)
66              Sends  the  contents of the named file to the connection as body
67              data.  If there is any error opening file for reading, the  test
68              aborts.
69
70       mt.bodyrandom(conn, n)
71              Sends  at  least n bytes of random-length lines of random print‐
72              able ASCII data as body chunks to the specified connection.
73
74       mt.bodystring(conn, str)
75              Sends str as a chunk of body text on the specified connection.
76
77       mt.chdir(directory)
78              Changes the current working directory to the named directory.
79
80       mt.connect(sockinfo[, count, interval])
81              Makes a connection to a filter listening at the socket described
82              by  sockinfo.  Returns a handle referring to that connection, or
83              the Lua constant "nil" on error.   If  count  and  interval  are
84              included,  they specify the number of times to try to connect to
85              the filter and the delay  between  each  connection  in  seconds
86              (with  floating  point  values  permitted).   If the environment
87              variable MILTERTEST_RETRY_SPEED_FACTOR is  set  and  appears  to
88              contain  an integer, the value of interval (if set) will be mul‐
89              tiplied by the value found in that environment  variable.   This
90              is  included  to  allow tests in a large test suite to be easily
91              adjusted on slow systems without reconfiguring the  entire  test
92              suite.
93
94       mt.conninfo(conn, host, ip)
95              Sends information about a new SMTP connection to the MTA, repre‐
96              sented by connection conn,  from  the  host  named  host  at  IP
97              address  ip  (both strings).  If host is the Lua constant "nil",
98              the string "localhost" is assumed.  If ip is  the  Lua  constant
99              "nil",  a  DNS  query  will  be made for the IP address matching
100              host; if none is found, the test will abort.  The ip may also be
101              the  special  string "unspec", which will tell the filter that a
102              connection came in from an unknown protocol family.
103
104       mt.data(conn)
105              Announces the DATA command on the  specified  connection,  which
106              occurs between the last RCPT TO command and the beginning of the
107              header block.
108
109       mt.disconnect(conn[, polite]))
110              Sends a "quit" message to  the  specified  connection  and  then
111              closes  that  connection.   The  specified conn handle should no
112              longer be used.  If polite is defined,  it  must  be  a  Boolean
113              indicating  whether a normal disconnect should be done (true) or
114              an abrupt disconnect should be done (false).  An abrupt  discon‐
115              nect skips standard protocol shutdown steps.
116
117       mt.echo(string)
118              Prints  the  specified string on standard output.  Returns noth‐
119              ing.
120
121       mt.eoh(conn)
122              Announces end-of-header on the specified connection.
123
124       mt.eom(conn)
125              Announces end-of-message on the specified connection, and begins
126              capturing  any other operations the filter might perform in that
127              phase.
128
129       mt.eom_check(conn, op, param[, ...])
130              Checks the captured set of EOM operations (see above) to  deter‐
131              mine  whether  or  not specific milter actions were requested by
132              the filter.  Returns a Boolean value (true or false).   See  the
133              EOM CHECKS section for details.
134
135       mt.getheader(conn, hdr, n)
136              Retrieves  the  value  of the nth instance of header field named
137              hdr added during end-of-message processing on the specified con‐
138              nection.   This  can  be  used  by the script to verify that the
139              header thus added contains the right thing.  Returns  the  value
140              as a string, or the Lua constant "nil" on error.
141
142       mt.getcwd()
143              Returns the current working directory as a string.
144
145       mt.getreply(conn)
146              Returns  the  last milter reply received from the specified con‐
147              nection, as an integer.  This can be  compared  to  any  of  the
148              SMFIR_*  constants  defined  by  milter  to  see  if  the filter
149              responded as expected.  This value is initially set to the  NULL
150              character.
151
152       mt.header(conn, name, value)
153              Sends  the header with the given name and value to the specified
154              connection.
155
156       mt.helo(conn, name)
157              Sends HELO/EHLO information using  the  specified  name  as  the
158              parameter given.
159
160       mt.macro(conn, type, name, value[, name2, value2[, ...]])
161              Declares a macro called name whose value is value and whose type
162              (matching protocol element) is type.  Valid types are SMFIC_CON‐
163              NECT,  SMFIC_HELO,  SMFIC_MAIL  and  SMFIC_RCPT.  Multiple macro
164              names and values can be provided, but they must appear in pairs.
165
166       mt.mailfrom(conn, envfrom[, ...])
167              Announces envfrom as the  envelope  sender  of  a  new  message.
168              ESMTP parameters as additional arguments are permitted.
169
170       mt.negotiate(conn, version, actions, steps)
171              Performs  milter  option  negotiation  with the connection conn,
172              advertising  that  the  specified  protocol  version,   protocol
173              actions  and protocol steps are offered by the MTA.  Returns the
174              Lua constant "nil" on success or an error string on failure.  If
175              any  of  the protocol parameters are "nil", the current defaults
176              (defined in libmilter/mfdef.h, provided with the  milter  source
177              code) will be used.
178
179       mt.rcptto(conn, envrcpt[, ...])
180              Announces  envrcpt as an envelope recipient of a message.  ESMTP
181              parameters as additional arguments are permitted.
182
183       mt.set_timeout(n)
184              Sets the read timeout to n seconds.  The default is ten seconds.
185              Returns nothing.
186
187       mt.sleep(n)
188              Sleeps  for  n  seconds.  The value may be an integer (for whole
189              seconds) or a floating-point value (for partial seconds).
190
191       mt.signal(n)
192              Sends the specified signal number n to the running filter.
193
194       mt.startfilter(path, arg1, arg2, ...)
195              Starts the filter whose binary is located at path with  argument
196              vector  comprised  of  strings path, arg1, arg2, etc.  Basically
197              this is almost the same syntax  as  execl(3)  except  that  mil‐
198              tertest  also  does  the  fork  for  you,  and will remember the
199              process ID in order to request a clean  shutdown  using  SIGTERM
200              and  wait(2) at the end of the test script.  If the filter could
201              not be started, an exception is generated with an error  message
202              returned.
203
204       mt.test_action(conn, action)
205              Tests   whether  or  not  the  connection  represented  by  conn
206              requested the specified milter protocol action, specified by  an
207              SMFIF_* constant, during option negotiation.  (See the libmilter
208              documentation and/or include files for details.)
209
210       mt.test_option(conn, option)
211              Tests  whether  or  not  the  connection  represented  by   conn
212              requested  the specified milter protocol option, specified by an
213              SMFIP_* constant, during option negotiation.  (See the libmilter
214              documentation and/or include files for details.)
215
216       mt.unknown(conn, str)
217              Announces  that  the  unknown  SMTP command str arrived over the
218              connection represented by conn.
219

EOM CHECKS

221       The mt.eom_check() function is used to determine what  changes  to  the
222       message  the filter requested during its EOM callback.  The changes can
223       be requested in any order.  The first  parameter,  op,  indicates  what
224       operation is of interest, and it also dictates what the possible param‐
225       eter list is.  Valid values and corresponding parameters for op are  as
226       follows:
227
228       MT_HDRADD
229              Checks to see if a header field was added to the message.  If no
230              parameters are given, the function returns true  if  any  header
231              field  was  added.   If  one  parameter  was given, the function
232              returns true only if the named header field was  added  (regard‐
233              less  of  its value).  If two parameters are given, the function
234              returns true only if the named header field was added  with  the
235              specified value.
236
237       MT_HDRCHANGE
238              Checks  to  see  if an existing header field was changed.  If no
239              parameters are given, the function returns true  if  any  header
240              field  was  modified.   If one parameter was given, the function
241              returns true  only  if  the  named  header  field  was  modified
242              (regardless of its new value).  If two parameters are given, the
243              function returns true only if the named header field  was  modi‐
244              fied to have the specified new value.
245
246       MT_HDRDELETE
247              Checks  to  see  if an existing header field was deleted.  If no
248              parameters are given, the function returns true  if  any  header
249              field  was  deleted.   If  one parameter was given, the function
250              returns true only if the named header field was deleted.
251
252       MT_HDRINSERT
253              Checks to see if a header field was inserted into  the  message.
254              If  no  parameters  are  given, the function returns true if any
255              header field was added.  If one parameter was given,  the  func‐
256              tion  returns  true  only  if  the  named header field was added
257              (regardless of its value).  If two  parameters  are  given,  the
258              function  returns  true only if the named header field was added
259              with the specified value.  If three parameters  are  given,  the
260              function  returns  true only if the named header field was added
261              with the specified value at the specified index.
262
263       MT_RCPTADD
264              Checks to see if an envelope  recipient  was  added.   Currently
265              only one parameter may be provided.
266
267       MT_RCPTDELETE
268              Checks  to  see if an envelope recipient was deleted.  Currently
269              only one parameter may be provided.
270
271       MT_BODYCHANGE
272              Checks to see if the message's body was replaced by  other  con‐
273              tent.  With no parameters, the function returns true only if the
274              body was changed (regardless of  the  new  content).   With  one
275              parameter,  the  function  returns  true  only  if  the body was
276              changed to the specified new content.
277
278       MT_QUARANTINE
279              Checks to see if the filter requested quarantining of  the  mes‐
280              sage.   With  no  parameters,  the function returns true only if
281              quarantine was requested.   With  one  parameter,  the  function
282              returns true only if quarantine was requested with the specified
283              reason string.
284
285       MT_SMTPREPLY
286              Checks to see if the filter requested a specific SMTP reply mes‐
287              sage.   With  no parameters, the function returns true only if a
288              specific reply was requested.  With one parameter, the  function
289              returns  true  only  if  a specific reply was requested with the
290              specified SMTP code.  With two parameters, the function  returns
291              true  only  if a specific reply was requested with the specified
292              SMTP code and enhanced status code.  With three parameters,  the
293              function  returns  true  only  if a specific reply was requested
294              with the specified SMTP code, enhanced status code, and text.
295

EXAMPLE

297       -- Echo that the test is starting
298       mt.echo("*** begin test")
299       -- start the filter
300       mt.startfilter("../myfilter", "-p", "inet:12345@localhost")
301       mt.sleep(2)
302
303       -- try to connect to it
304       conn = mt.connect("inet:12345@localhost")
305       if conn == nil then
306            error "mt.connect() failed"
307       end
308
309       -- send connection information
310       -- mt.negotiate() is called implicitly
311       if mt.conninfo(conn, "localhost", "127.0.0.1") ~= nil then
312            error "mt.conninfo() failed"
313       end
314       if mt.getreply(conn) ~= SMFIR_CONTINUE then
315            error "mt.conninfo() unexpected reply"
316       end
317
318       -- send envelope macros and sender data
319       -- mt.helo() is called implicitly
320       mt.macro(conn, SMFIC_MAIL, "i", "test-id")
321       if mt.mailfrom(conn, "user@example.com") ~= nil then
322            error "mt.mailfrom() failed"
323       end
324       if mt.getreply(conn) ~= SMFIR_CONTINUE then
325            error "mt.mailfrom() unexpected reply"
326       end
327
328       -- send headers
329       -- mt.rcptto() is called implicitly
330       if mt.header(conn, "From", "user@example.com") ~= nil then
331            error "mt.header(From) failed"
332       end
333       if mt.getreply(conn) ~= SMFIR_CONTINUE then
334            error "mt.header(From) unexpected reply"
335       end
336       if mt.header(conn, "Date", "Tue, 22 Dec 2009 13:04:12  -0800")  ~=  nil
337       then
338            error "mt.header(Date) failed"
339       end
340       if mt.getreply(conn) ~= SMFIR_CONTINUE then
341            error "mt.header(Date) unexpected reply"
342       end
343       if mt.header(conn, "Subject", "Signing test") ~= nil then
344            error "mt.header(Subject) failed"
345       end
346       if mt.getreply(conn) ~= SMFIR_CONTINUE then
347            error "mt.header(Subject) unexpected reply"
348       end
349       -- send EOH
350       if mt.eoh(conn) ~= nil then
351            error "mt.eoh() failed"
352       end
353       if mt.getreply(conn) ~= SMFIR_CONTINUE then
354            error "mt.eoh() unexpected reply"
355       end
356
357       -- send body
358       if mt.bodystring(conn, "This is a test!\r\n") ~= nil then
359            error "mt.bodystring() failed"
360       end
361       if mt.getreply(conn) ~= SMFIR_CONTINUE then
362            error "mt.bodystring() unexpected reply"
363       end
364       -- end of message; let the filter react
365       if mt.eom(conn) ~= nil then
366            error "mt.eom() failed"
367       end
368       if mt.getreply(conn) ~= SMFIR_ACCEPT then
369            error "mt.eom() unexpected reply"
370       end
371
372       -- verify that a test header field got added
373       if not mt.eom_check(conn, MT_HDRINSERT, "Test-Header") then
374            error "no header added"
375       end
376
377       -- wrap it up!
378       mt.disconnect(conn)
379

NOTES

381       If  a filter negotiates one of the SMFIP_NO* protocol option bits and a
382       script attempts to perform one of those protocol  steps,  an  error  is
383       returned.  It is up to the test author to use mt.test_option() function
384       to see if performing a protocol step has been  explicitly  disabled  by
385       the filter.
386

MILTER NOTES

388       When  mt.macro() is called, it replaces all previous macros of the same
389       type with the ones provided in  the  argument  list.   Thus,  one  call
390       should  be  made  that  lists the complete set rather than one call per
391       name-value pair.  Also, as each stage in the  milter  process  is  exe‐
392       cuted,  all  macros corresponding stages after the current one are dis‐
393       carded.   For  example,  calling  mt.helo(),   which   corresponds   to
394       SMFIC_HELO,  will  cause  all  prior  macros  of  type  SMFIC_MAIL  and
395       SMFIC_RCPT to be discarded as they represent a milter stage that  comes
396       later than SMFIC_HELO.
397
398       Since the milter protocol and the internals of libmilter itself are not
399       formally documented, there are myriad other subtleties  of  the  milter
400       protocol and implementation that are not documented here and may not be
401       documented elsewhere, and could change without notice.  Caveat emptor.
402

VERSION

404       This man page covers version 1.5.0 of miltertest.
405
407       Copyright (c)  2009-2014,  The  Trusted  Domain  Project.   All  rights
408       reserved.
409

SEE ALSO

411       Milter -- http://www.milter.org
412
413       Lua -- http://www.lua.org
414
415
416
417                          The Trusted Domain Project             miltertest(8)
Impressum