1miltertest(8) System Manager's Manual miltertest(8)
2
3
4
6 miltertest - milter unit test utility
7
9 miltertest [-D name[=value]] [-s script] [-u] [-v] [-V] [-w]
10
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
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
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
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
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
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
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
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
411 Milter -- http://www.milter.org
412
413 Lua -- http://www.lua.org
414
415
416
417 The Trusted Domain Project miltertest(8)