## 12 `mtasim` — a testing tool ¶

The `mtasim` utility is a MTA simulator for testing `mailfromd` filter scripts. By default it operates in stdio mode, similar to that of `sendmail -bs`. In this mode it reads SMTP commands from standard input and sends its responses to the standard output. There is also another mode, called daemon, where `mtasim` opens a TCP socket and listens on it much like any MTA does. In both modes no actual delivery is performed, the tool only simulates the actions an MTA would do and responses it would give.

This tool is derived from the program `mta`, which I wrote for GNU Anubis test suite.

### 12.1 `mtasim` interactive mode mode ¶

If you start `mtasim` without options, you will see the following:

    220 mtasim (mailfromd 9.1) ready
    (mtasim) _

The first line is an usual RFC 2821 reply. The second one is a prompt, indicating that `mtasim` is in interactive mode and ready for input. The prompt appears only if the package is compiled with GNU Readline and `mtasim` determines that its standard input is connected to the terminal. This is called interactive mode and is intended to save the human user some typing by offering line editing and history facilities (see Command Line Editing in GNU Readline Library). If the package is compiled without GNU Readline, you will see:

    220 mtasim (mailfromd 9.1) ready
    _

where ‘`_`’ represents the cursor. Whatever the mode, `mtasim` will wait for further input.

The input is expected to consist of valid SMTP commands and special `mtasim` statements. The utility will act exactly like a RFC 2821-compliant MTA, except that it will not do actual message delivery or relaying. Try typing `HELP` to get the list of supported commands. You will see something similar to:

    250-mtasim (mailfromd 9.1); supported SMTP commands:
    250- EHLO
    250- HELO
    250- MAIL
    250- RCPT
    250- DATA
    250- HELP
    250- QUIT
    250- HELP
    250  RSET

You can try a simple SMTP session now:

    220 mtasim (mailfromd 9.1) ready
    (mtasim) ehlo localhost
    250-pleased to meet you
    250 HELP
    (mtasim) mail from: <me@localhost>
    250 Sender OK
    (mtasim) rcpt to: <him@domain>
    250 Recipient OK
    (mtasim) data
    354 Enter mail, end with `.' on a line by itself
    (mtasim) .
    250 Mail accepted for delivery
    (mtasim) quit
    221 Done

Notice, that `mtasim` does no domain checking, so such thing as ‘`rcpt to: <him@domain>`’ was eaten without complaints.

So far so good, but what all this has to do with `mailfromd`? Well, that’s what we are going to explain. To make `mtasim` consult any milter, use `--port` (`-X`) command line option. This option takes a single argument that specifies the milter port to use. The port can be given either in the usual Milter format (See milter port specification, for a short description), or as a full `sendmail.cf` style `X` command, in which case it allows to set timeouts as well:

    $ mtasim --port=inet:999@localhost
    # This is also valid:
    $ mtasim --port='mailfrom, S=inet:999@localhost, F=T, T=C:100m;R:180s'

If the milter is actually listening on this port, `mtasim` will connect to it and you will get the following initial prompt:

    220-mtasim (mailfromd 9.1) ready
    220 Connected to milter inet://localhost:999
    (mtasim)

Notice, that it makes no difference what implementation is listening on that port, it may well be some other filter, not necessarily `mailfromd`.

However, let’s return to `mailfromd`. If you do not want to connect to an existing `mailfromd` instance, but prefer instead to create a new one and run your tests with it (a preferred way, if you already have a stable filter running but wish to test a new script without disturbing it), use `--port=auto`. This option instructs `mtasim` to do the following:

1. Create a unique temporary directory in `/tmp`.
2. Start a new instance of `mailfromd`. This instance is configured to communicate over a UNIX socket in that temporary directory. Additional arguments and options for the new instance may be given in the command line after a double-dash marker (‘`--`’).
3. Connect to that filter.

When `mtasim` exits, it terminates the subsidiary `mailfromd` process and removes the temporary directory it has created. For example, the following command will start `mailfromd -I. -I../mflib test.rc`:

    $ mtasim -Xauto -- -I. -I../mflib test.rc
    220-mtasim (mailfromd 9.1) ready
    220 Connected to milter unix:/tmp/mtasim-j6tRLC/socket
    (mtasim)

The `/tmp/mtasim-j6tRLC` directory and any files within it will exist as long as `mtasim` is running and will be removed when you exit from it.30 You can also instruct the subsidiary `mailfromd` to use this directory as its state directory (see statedir). This is done by `--statedir` command line option:

    $ mtasim -Xauto --statedir -- -I. -I../mflib test.rc

(notice that `--statedir` is the `mtasim` option, therefore it must appear before ‘`--`’)

You can use an existing directory instead of creating a temporary one. To do so, use the `-Xdir:name` option, e.g.:

    $ mtasim -Xdir:/var/lib/teststate --statedir -- -I. -I../mflib test.rc

Special care should be taken when using `mtasim` from root account, especially if used with `-Xauto` and `--statedir`. The `mailfromd` utility executed by it will switch to privileges of the user given in its configuration (see Starting and Stopping) and will not be able to create data in its state directory, because the latter was created using ‘`root`’ as owner. To help in this case, `mtasim` understands `--user` and `--group` command line options, that have the same meaning as for `mailfromd`.

Now, let’s try `HELP` command again:

    250-mtasim (mailfromd 9.1); supported SMTP commands:
    250- EHLO
    250- HELO
    250- MAIL
    250- RCPT
    250- DATA
    250- HELP
    250- QUIT
    250- HELP
    250- RSET
    250-Supported administrative commands:
    250- \Dname=value [name=value...]       Define Sendmail macros
    250- \Ecode [regex]                     Expect given SMTP reply code
    250- \L[name] [name...]                 List macros
    250- \Uname [name...]                   Undefine Sendmail macros
    250  \Sfamily hostname address [port]   Define sender socket address

While the SMTP commands do not need any clarification, some words about the administrative commands are surely in place. These commands allow to define, undefine and list arbitrary Sendmail macros. Each administrative command consists of a backslash followed by a command letter. Just like SMTP ones, administrative commands are case-insensitive. If a command takes arguments, the first argument must follow the command letter without intervening whitespace. Subsequent arguments can be delimited by arbitrary amount of whitespace.

For example, the `\D` command defines Sendmail macros:

    (mtasim) \Dclient_addr=192.168.10.1 f=sergiusz@localhost i=testmsg
    (mtasim)

Notice that `mailfromd` does not send any response to the command, except if there was some syntactic error, in which case it will return a ‘`502`’ response.

Now, you can list all available macros:

    (mtasim) \L
    220-client_addr=192.168.10.1
    220-f=sergiusz@localhost
    220 i=testmsg
    (mtasim)

or just some of them:

    (mtasim) \Lclient_addr
    220 client_addr=192.168.10.1
    (mtasim)

To undefine a macro, use `\U` command:

    (mtasim) \Ui
    (mtasim) \l
    220-client_addr=192.168.10.1
    220 f=sergiusz@localhost
    (mtasim)

The `\S` command declare sender socket and host name. These parameters are passed to the `connect` handler, if one is declared (see connect handler). To give you a chance to use this command, `mtasim` does not invoke `connect` handler right after connecting to the milter. Instead it waits until either the `\S` command or any SMTP command (except ‘`HELP`’) is given. After calling `connect` handler the `\S` is disabled (to reflect it, it also disappears from the `HELP` output).

The `\S` command takes 1 to 4 arguments. The first argument supplies the socket family (see Table 4.3). Allowed values are: ‘`stdio`’, ‘`unix`’, ‘`inet`’, ‘`inet6`’ or numbers from ‘`0`’ to ‘`3`’.

The `\S stdio` (or `\S 0`) command needs no additional arguments. It indicates that the SMTP connection is obtained from the standard input. It is the default if sender socket is not declared explicitly.

The command `\S unix` indicates that the connection is accepted from a UNIX socket. It requires two more argument. The first one supplies sender host name and the second one supplies full path name of the socket file. For example:

    \S unix localhost /var/run/smtp.sock

The commands `\S inet` and `\S inet6` indicate that the connection came from an ‘`INET`’ IPv4 or IPv6 socket, correspondingly31. They require all four arguments to be specified. The additional arguments are: host name, IP address, and port number, in that order. For example:

    \S inet relay.gnu.org.ua 213.130.31.41 34567

or

    \S inet6 relay.gnu.org.ua 2001:470:1f0a:1be1::2 34567

Sender socket address can also be configured from the command line (see sender-socket).

Now, let’s try a real-life example. Suppose you wish to test the greylisting functionality of the filter script described in Example of a Filter Script File. To do this, you start `mtasim`:

    $ mtasim -Xauto -- -I. -I../mflib test.rc
    220-mtasim (mailfromd 9.1) ready
    220 Connected to milter unix:/tmp/mtasim-ak3DEc/socket
    (mtasim)

The script in `test.rc` needs to know `client_addr` macro, so you supply it to `mtasim`:

    (mtasim) \Dclient_addr=10.10.1.13

Now, you try an SMTP session:

    (mtasim) ehlo yahoo.com
    250-pleased to meet you
    250 HELP
    (mtasim) mail from: <gray@yahoo.com>
    250 Sender OK
    (mtasim) rcpt to: <gray@localhost>
    450 4.7.0 You are greylisted for 300 seconds

OK, this shows that the greylisting works. Now quit the session:

    (mtasim) quit
    221 Done

### 12.2 `mtasim` expect commands ¶

Until now we were using `mtasim` interactively. However, it is often useful in shell scripts, for example the `mailfromd` test suite is written in shell and `mtasim`. To avoid the necessity to use auxiliary programs like `expect` or `DejaGNU`, `mtasim` contains a built-in expect feature. The administrative command `\E` introduces the SMTP code that the next command is expected to yield. For example,

    \E250
    rcpt to: <foo@bar.org>

tells `mtasim` that the response to `RCPT TO` command must begin with ‘`250`’ code. If it does, `mtasim` continues execution. Otherwise, it prints an error message and terminates with exit code 1. The error message it prints looks like:

    Expected 250 but got 470

The expected code given with the `\E` command may have less than 3 digits. In this case it specifies the first digits of expected reply. For example, the command ‘`\E2`’ matches replies ‘`200`’, ‘`220`’, etc.

If `\E` is passed two arguments, the second one is treated as an extended regular expression. The subsequent command will then succeed if its return code matches the one supplied as the first argument, and its extended SMTP code and textual message match the supplied regular expression. If the regular expression contains whitespace, enclose it in a pair of double quotes. Within double quotes, backslash can be used to escape double quote, and backslash character.

This feature can be used to automate your tests. For example, the following script tests the greylisting functionality (see the previous section):

    # Test the greylisting functionality
    #
    \E220
    \Dclient_addr=10.10.1.13
    \E250
    ehlo yahoo.com
    \E250
    mail from: <gray@yahoo.com>
    \E450
    rcpt to: <gray@localhost>
    \E221
    quit

This example also illustrates the fact that you can use ‘`#`’-style comments in the `mtasim` input.

`mtasim` scripts can be used in shell programs, for example:

    mtasim -Xauto --statedir -- -P../mflib test.rc < scriptfile
    if $? -ne 0; then
      echo "Greylisting test failed"
    fi

### 12.3 Trace Files ¶

It is possible to log an entire SMTP session to a file. This is called session tracing. Two options are provided for this purpose:

`--trace-file=file` ¶

  Sets the name of the trace file, i.e. a file to which the session transcript will be written. Both the input commands, and the `mtasim` responses are logged. If the file file exists, it will be truncated before logging. This, however, can be changed using the following option:

`-a` ¶
`--append`

  If the trace file exists, append new trace data to it.

### 12.4 Daemon Mode ¶

To start `mtasim` in daemon mode, use the `--daemon` (or `-bd`) command line option. This mode is not quite the same as Sendmail `-bd` mode. When started in daemon mode, `mtasim` selects the first available TCP port to use from the range ‘`1024 -- 65535`’. It prints the selected port number on the standard output and starts listening on it. When a connection comes, it serves a single SMTP session and exits immediately when it is ended.

This mode is designed for use in shell scripts and automated test cases.

### 12.5 Summary of the `mtasim` Administrative Commands ¶

This section provides a summary of administrative commands available in `mtasim`.

mtasim command: \D name=value [name=value...] ¶

  Defines Sendmail macro name to the given value. Any number of name=value pairs can be given as arguments.

  See D command.

mtasim command: \E code [regex] ¶

  Instructs `mtasim` to expect next SMTP command to return given code (a three-digit decimal number). Optional regex argument is an extended POSIX regular expression. If supplied, the program will also require that the extended SMTP code (if any) and textual message returned by the command match this expression.

  See `mtasim` expect commands.

mtasim command: \L [name...] ¶

  Lists defined macros. See L command.

mtasim command: \U name [name...] ¶

  Undefines macros given as its arguments.

mtasim command: \S family [hostname address [port]] ¶

  Declares the sender socket parameters. See S command, for a detailed description and examples.

  This command is available only at the initial stage of a `mtasim` session, before the first SMTP command was given. It is disabled if the `--sender-socket` option was given in the command line (see sender-socket). The `help` output reflects whether or not this command is available.

  If neither this command nor the `--sender-socket` option were given, `mtasim` behaves as if given the `\S stdio` command.

  The family argument supplies the socket family, i.e. the first argument to the `connect` handler (see connect handler). It can have either literal or numeric value, as described in the table below:

  Literal | Numeric | Meaning
  --- | --- | ---
  stdio | 0 | Standard input/output (the MTA is run with `-bs` option)
  unix | 1 | UNIX socket
  inet | 2 | IPv4 protocol
  inet6 | 3 | IPv6 protocol

  Table 12.1: Socket families

  See also Table 4.3.

  Depending on the family, the rest of arguments supply additional parameters:

  stdio

    The hostname argument can be specified. It defines the first argument of the `connect` handler (see hostname in connect handler).

  inet
  inet6

    All arguments must be specified.

    argument | `connect` argument | meaning
    --- | --- | ---
    hostname | 1 | Sender host name
    address | 4 | Sender IP address
    port | 3 | Sender port number

  unix

    Hostname and address must be supplied. The address argument must be a full pathname of the UNIX socket.

### 12.6 `mtasim` command line options ¶

This section summarizes all available `mtasim` command line options.

`--append` ¶
`-a`

  Append to the trace file. See Trace Files.

`--body-chunk=number` ¶

  Set the body chunk length (bytes) for `xxfi_body` calls.

`--daemon` ¶
`-bd`

  Run as daemon. See Daemon Mode.

`--define=macro=value` ¶
`-D macro=value`

  Define Sendmail macro macro to the given value. It is similar to the `\D` administrative command (see D command)

`--gacopyz-log=level` ¶

  Set desired logging level for `gacopyz` library (see Gacopyz). See gacopyz-log option, for a detailed description of level. Notice, that unless this option is used, the `--verbose` (`-v`) command line option implies `--gacopyz-log=debug`.

`--group=name` ¶
`-g name`

  When switching to user’s privileges as requested by the `--user` command line option, retain the additional group name. Any number of `--group` options may be given to supply a list of additional groups.

`--user=name` ¶
`-u name`

  Run with this user privileges. This option and the `--group` option have effect only if `mtasim` was started with root privileges.

`--help`
`-?`

  Display a short help summary

`--milter-version=version` ¶

  Force using the given Milter protocol version number. The version argument is either a numeric version (e.g. ‘`2`’), or a version string in form ‘`major.minor[.patch]`’, where square brackets indicate optional part. The default is ‘`1.0.0`’. If version is any of ‘`2`’, ‘`3`’ or ‘`1.0.0`’, the default protocol capabilities and actions for that version are set automatically. This option is intended for development and testing of the Gacopyz library (see Gacopyz).

`--milter-proto=bitmask` ¶

  Set Milter protocol capabilities. See `gacopyz/gacopyz.h` for the meaning of various bits in the bitmask. Look for the C macros with the prefix ‘`SMFIP_`’.

`--milter-timeout=values` ¶

  Set timeouts for various Milter operations. Values is a comma-separated list of assignments ‘`T=V`’, where T is a timeout code, indicating which timeout to set, and V is its new value. Valid timeout codes are:

  C

    Timeout for connecting to a filter.

  W
  S

    Timeout for sending information from the simulator to a filter.

  R

    Timeout for reading reply from the filter.

  E

    Overall timeout between sending end-of-message to filter and receiving final acknowledgment. Indirectly, it configures the upper limit on the execution time of the `eom` handler (see eom handler).

`--milter-actions=bitmask` ¶

  Set Milter actions. See `gacopyz/gacopyz.h` for the meaning of various bits in the bitmask. Look for the C macros with the prefix ‘`SMFIF_`’.

`--no-interactive` ¶

  Not-interactive mode (disable readline). See Command Line Editing in GNU Readline Library.

`--port=port` ¶
`-X port`

  Communicate with given Milter port. Valid values for port are:

  port specification

    See milter port specification, for a detailed discussion. Example:

        --port inet:999@localhost

  `sendmail.cf` `X` command format

    For example:

        --port='mailfrom, S=inet:999@localhost, F=T, T=C:100m;R:180s'

  `auto`

    Create a temporary directory and start an instance of `mailfromd` configured to communicate over a UNIX socket in that directory. If `--statedir` is also given, the created `mailfromd` instance will use that directory as its state directory (see statedir). Additional arguments for `mailfromd` may be supplied after the `--` delimiter.

    Before termination, `mtasim` will stop the `mailfromd` instance it created and remove the temporary directory.

  `dir`:dirname

    Same as `auto`, except that instead of the temporary directory the directory dirname is used. This directory is not removed when `mtasim` terminates. Example:

        --port dir:/tmp/state

  See mtasim milter port, for a detailed discussion of the `--port` option and its use.

`--prompt=string` ¶

  Set readline prompt. The default prompt string is ‘`(mtasim)` ’.

`--sender-socket=family[,hostname,address[,port]]` ¶

  Declare sender socket address. This option has the same effect as the `S command`. See S command, for a detailed discussion and a description of its arguments.

`--statedir` ¶

  When using `-Xauto`, use the temporary directory name as `mailfromd` state directory (see statedir mtasim option).

`--stdio` ¶
`-bs`

  Use the SMTP protocol on standard input and output. This is the default mode for `mtasim`. See `mtasim` interactive mode mode.

`--trace-file=file` ¶

  Set name of the trace file. See Trace Files.

`--usage`

  Display option summary

`--verbose` ¶
`-v`

  Increase verbosity level. Implies `--gacopyz-log=debug`, unless that option is used explicitly.

`--version`
`-V`

  Print program version

#### Footnotes

##### (30)

However, this is true only if the program is exited the usual way (via `QUIT` or end-of-file). If it is aborted with a signal like `SIGINTR`, the temporary directory is not removed.

##### (31)

Depending on how `mailfromd` is configured, ‘`inet6`’ may be not available.