     [file version.texi not included]
     [file rendition.texi not included]
     [file macros.texi not included]
     [file values.texi not included]

Published by the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA

Copyright © 2005, 2006 Sergey Poznyakoff

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, with the Front-Cover texts being ``Mailfromd Manual'', and with the Back-Cover Texts as in (a) below.  A copy of the license is included in the section entitled ``GNU Free Documentation License''.

(a) The FSF's Back-Cover Text is: ``You have freedom to copy and modify this GNU Manual, like GNU software.  Copies published by the Free Software Foundation raise funds for GNU development.''

Mailfromd
*********

This edition of the Mailfromd Manual, last updated UPDATED, documents mailfromd Version VERSION.

Introduction to mailfromd
*************************

Mailfromd is a general-purpose mail filtering daemon for Sendmail (See http://www.sendmail.org). It is able to filter both incoming and outgoing messages using criteria of arbitrary complexity supplied by the administrator in the form of a script file.  The daemon interfaces with Sendmail using Milter protocol.

The program name -- mailfromd -- stems from the fact that the original implementation was a simple filter implementing the sender address verification technique.  Since then the program has changed dramatically, and now it is actually a specialized system providing a set of interfacing functions, along with a language translator and runtime evaluator for executing user-defined filter programs.

The first part of this manual is an overview, describing the features mailfromd offers in general.

The second part is a tutorial, which provides an introduction for those who have not used mailfromd previously.  It moves from topic to topic in a logical, progressive order, building on information already explained.  It offers only the principal informations needed to master basic practical usage of mailfromd, while omitting many subtleties.

The other parts are meant to be used as a reference for those who know mailfromd well enough, but need to look up some notions from time to time.  Each chapter presents everything that needs to be said about a specific topic.

The manual assumes that the reader has a good knowledge of the SMTP protocol and Sendmail mail transport system.

Mailfromd at a first glance
===========================

In contrast to the most existing milter filters, mailfromd does not implement any default filtering policies.  Instead, it depends entirely on a filter script, supplied to it by the administrator.  The script, written in a specialized and simple to use language (see Filter Script), is supposed to perform some set of tests and to decide whether the message should be accepted by the MTA or not. To perform the tests, the script can examine the values of Sendmail macros, use an extensive set of built-in functions and define and invoke user-defined functions.

Sender Address Verification.
============================

Sender address verification is the basic mail verification technique, implemented by mailfromd.  It consists in probing each MX server for the given address, until one of them gives a definite (positive or negative) reply.  Using this technique you can block a sender address if it is not deliverable, thereby cutting off a large amount of spam.  Verify this:It can also be useful to block mail for undeliverable recipients, for example on a mail relay host that does not have a list of all the valid recipient addresses.  This prevents undeliverable junk mail from entering the queue, so that your MTA doesn't have to waste resources trying to send MAILER-DAEMON messages back.

Let's illustrate how it works on an example:

Suppose that the user <jsmith@somedomain.net> is trying to send mail to one of your local users.  The remote machine connects to your MTA and issues MAIL FROM: <jsmith@somedomain.net> command.  However, your MTA does not have to take its word for it, so it uses mailfromd to verify the sender address validity.  Mailfromd strips the domain name from the address (somedomain.net) and queries DNS about MX records for that domain.  Suppose, it receives the following list

10   relay1.somedomain.net
20   relay2.somedomain.net

It then connects to first MX server, using SMTP protocol, as if it were going to send a message to <jsmith@somedomain.net>.  This is called sending a probe message.  If the server accepts the recipient address, the mailfromd accepts the incoming mail.  Otherwise, if the server rejects the address, the mail is rejected as well.  If the MX server cannot be connected, mailfromd selects next server from the list and continues this process until it finds the answer or the list of servers is exhausted.

The probe message is like a normal mail except that no data are ever being sent. The probe message in our example might look as follows (S: meaning messages sent by remote MTA, C: meaning those sent by mailfromd):

     C: HELO mydomain.net
     S: 220 OK, nice to meet you
     C: MAIL FROM: <>
     S: 220 <>: Sender OK
     C: RCPT TO: <jsmith@somedomain.net>
     S: 220 <jsmith@remote.net>: Recipient OK
     C: QUIT

Probe messages are never delivered, deferred or bounced; they are always discarded.

The described method of address verification is called standard method throughout this document.  Mailfromd also implements a method we call strict.  When using strict method, mailfromd first resolves IP address of sender machine to a fully qualified domain name.  Then it obtains MX records for this machine, and then proceeds with probing as described above.

So, the difference between the two methods is in the set of MX records that are being probed: standard method queries MXs based on the sender email domain, strict method works with MXs for the sender IP address.

Strict method allows to cut off much larger amount of spam, although it does have many drawbacks.  Returning to our example above, consider the following situation: <jsmith@somedomain.net> is a perfectly normal address, but it is being used by a spammer from some other domain, say otherdomain.com.  Standard method is not able to cope with such cases, whereas strict one is.

An alert reader will ask: what happens if mailfromd is not able to get a definite answer from any of MX servers?  Actually, it depends entirely on how you will instruct it to act in this case, but the general practice is to return temporary failure, which will urge the remote party to retry sending his/her message later.

After receiving a definite answer, mailfromd will cache it in its database, so that next time your MTA receives a message from that address (or from the sender IP/email address pair, for strict method), it will not waste its time trying to reach MX servers again.  The records remain in the cache database for a certain time, after which they are discarded.

Limitations of Sender Address Verification
==========================================

Before deciding whether and how to use sender address verification, you should be aware of its limitations.

Both standard and strict methods suffer from the following limitations:

  * The sender verification methods will perform poorly on highly loaded sites.  The traffic and/or resource usage overhead may not be feasible for you.  However, you may experiment with various mailfromd options to find an optimal configuration.

  * Some sites may blacklist your MTA if it probes them too often.  This drawback can to a certain extent be eliminated by raising the expiration timeout in your cache database.

  * When verifying the remote address, no attempt to actually deliver the message is made.  If MTA accepts the address, mailfromd assumes it is OK.  However in reality, mail for a remote address can bounce after the nearest MTA accepts the recipient address.

     This drawback can often be avoided by combinig sender address verification with greylisting (see Greylisting).

  * If the remote server rejects the address, no attempt is being made to discern between various reasons for rejection (client rejected, HELO rejected, MAIL FROM rejected, etc.)

  * Unfortunately, some major sites such as yahoo.com do not reject unknown addresses in reply to the RCPT TO command, but report a delivery failure in response to end of DATA after a message is transferred.  Of course, sender address verification does not work with such sites.  However, a combination of address verification and greylisting (see Greylisting) may be a good choice in such cases.

In addition, strict verification breaks forward mail delivery.  This is obvious, since mail forwarding is based on delivering unmodified message to another location, so the sender address domain will most probably not be the same as that of the MTA doing the forwarding.

Configuring Mail Sending Rate.
==============================

Another feature offered by mailfromd is controlling user mail sending rate.  A mail sending rate for a given user is defined as a number of messages he is allowed to send within the predefined interval of time.

Mailfromd filter language offers a special function rate (see rate) that computes the sending rate for the given user.  Depending on this value a decision can be made whether to accept or to refuse the message.

For this feature to work, the package must be configured with DBM support (see next chapter).

Building the Package
********************

This chapter contains the detailed list of steps you need to undertake in order to configure and build the package.

 1. Make sure you have the necessary software installed.

     To build mailfromd you will need to have following packages on your machine:

      A. GNU mailutils version 1.0 or newer.

          It is available from http://www.gnu.org/software/mailutils.

      B. A DBM library. Mailfromd is able to link with two flavors of DBM libraries: Berkeley DB or gdbm.  Notice that you can compile mailfromd without DBM too (This is true for version 2.0.  Future releases will, most probably, refuse to build without DBM.), but this is not recommended, since it will disable many useful features.  In particular, it disables caching, which can lead to increase in CPU usage, traffic outage and other disastrous effects. You have been warned.

          So, you want to select DBM implementation to use.  The following table will help you do that.  The column DB type lists types of DBM databases supported by mailfromd. The column confMAPDEF lists the value of confMAPDEF Sendmail configuration macro corresponding to that database type.  It is here because it is usually wise to configure mailfromd to use the same database type as your Sendmail (If you don't know what confMAPDEF is, you probably are wasting your time reading this.  Refer to Sendmail configuration guide first).  Finally, the column configure option lists the option you should give to configure to enable using this database.  N/A in any column means there is no support for this database in Sendmail or mailfromd.

          DB type   confMAPDEF   configure option
          NDBM          -NNDBM       N/A
          Berkeley DB   -NNEWDB      --with-berkeley-db
          GDBM          N/A          --with-gdbm

          The --with-berkeley-db needs a special note.  By default it will try to determine the version of Berkeley DB installed on your machine.  If, however, this autodetection fails, you can explicitly specify the version or the library name to use as the argument to the option.  Unless the argument begins with a digit, it is taken as a library name, without the lib prefix and library type suffix, so that

               ./configure --with-berkeley-db=db-3.1

          instructs configure to use library libdb-3.1.so (or libdb-3.1.a).  Otherwise, if the argument begins with a digit, it is understood as a version number of the library to link to.  In this case configure assumes a Slackware-like installation layout.  Thus, the option

                 --with-berkeley-db=3.1

          tells configure to use library libdb-3.1.so (or libdb-3.1.a)  and header file /usr/include/db31/db.h.

 2. Decide what user privileges will be used to run mailfromd

     After startup, the program drops the root privileges.  By default, it switches to the privileges of user mail, group mail. If there is no such user on your system, or you wish to use another user account for this purpose, override it using DEFAULT_USER environment variable.  For example for mailfromd to run as user nobody, use

          ./configure DEFAULT_USER=nobody

     The user name can also be changed at run-time (see --user).

 3. Decide where to install mailfromd and where its configuration and data files will be located.

     As usual, default value for installation prefix is /usr/local.  If it does not suit you, specify another location using --prefix option, e.g.: --prefix=/usr.

     During installation phase, the build system will install several files.  These files are:

     File   Purpose
     PREFIX/sbin/mailfromd   Main daemon
     PREFIX/etc/mailfromd.rc   Configuration
     file (The configuration file will be installed only if it is
     not already there. Thus, if you are upgrading to a newer version of
     mailfromd, your old configuration file will be preserved
     with all your changes.)
     PREFIX/info/mailfromd.info   Documentation

     At startup, mailfromd will look for its configuration settings and the filter script in the file PREFIX/etc/mailfromd.rc.

     It is advisable to use the same settings for file name prefixes as those you used when configuring mailutils.  In particular, try to use the same --sysconfdir, since it will facilitate configuring the whole system.

     Another important point is the location of local state directory, i.e. a directory where mailfromd will keep its data files (communication socket, pidfile and cache database).  By default, its full name is LOCALSTATEDIR/mailfromd.

 4. Select default communication socket. This is the socket used to communicate with sendmail. The default value is unix:LOCALSTATEDIR/mailfromd/mailfrom.  To alter this, use DEFAULT_SOCKET environment variable, e.g.:

          ./configure DEFAULT_SOCKET=inet:999@localhost

     The socket setting can also be changed at run-time (see --port).

 5. Select default expiration interval. The default value is 86400 seconds, i.e. 24 hours. It is OK for most sites.  If, however, you wish to change it, use DEFAULT_EXPIRE_INTERVAL environment variable.

     This setting can also be changed at run-time (see database).

 6. Run configure with all the desired options.

     For example, the following command:

          ./configure DEFAULT_SOCKET=inet:999@localhost --with-db2

     will configure the package to use Berkeley DB2 database, and inet:999@localhost as the default communication socket.

 7. Run make.

 8. Run make install.

 9. Make sure LOCALSTATEDIR/mailfromd has the right owner and mode.

 10. Examine filter script file (SYSCONFDIR/mailfromd.etc) and edit it, if necessary.  If you are upgrading from an older copy of mailfromd, your configuration file will remain untouched.

If you are upgrading from version 1.x to 2.0, you will have to do the following:

 1. Edit your configuration file and enclose the entire code section into:

          prog envfrom
          do
            ...
          done

     See Handlers, for more information about the prog statement.

 2. If your code contained any rate statements, convert them to function calls (see rate), using the following scheme:

          Old statement: if rate KEY LIMIT / EXPR
          New statement: if rate(KEY, interval("EXPR")) > LIMIT

     For example,

             rate $f 180 / 1 hour 25 minutes

     should become

             rate($f, interval("1 hour 25 minutes")) > 180

 3. Rebuild your databases using the following command:

          mailfromd --compact --all

     This is necessary since the format of mailfromd databases has changed in version 2.0: the key field now includes the trailing NUL character, which is also reflected in its length.  This allows for empty (zero-length) keys.  See Database Maintenance, for more information about the database compaction.

Tutorial
********

This chapter contains a tutorial introduction, guiding you through various mailfromd configurations, starting from the simplest ones and proceeding up to the most advanced forms.  It omits most complicated details, concentrating mainly on the common practical tasks.

If you are familiar to mailfromd you can skip this chapter and go directly to the next ones, which contain detailed discussion of the configuration language and mailfromd interaction with the Mail Transport Agent.

Start Up
========

The mailfromd runs as a standalone daemon program and listens on a predefined communication channel for requests from the Mail Transfer Agent (MTA, for short).  When processing each message, the MTA installs communication with mailfromd, and goes through several states, collecting the necessary data from the sender.  At each state it sends the relevant information to mailfromd, and waits for it to reply.  The mailfromd filter receives the message data through Sendmail macros and runs a configuration program defined for the given state.  The result of this run is a response code, that it returns to the MTA:

continue
     Continue message processing.

accept
     Accept this message for delivery.  After receiving this code the MTA continues processing this message without further consulting mailfromd filter.

reject
     Reject this message.  The message processing stops at this stage, the sender receives the reject reply.  No further mailfromd handlers are called for this message.

discard
     Silently discard the message.  This means that MTA will continue processing this message as if it were going to deliver it, but will discard it after receiving.  No further interaction with mailfromd occurs.

tempfail
     Temporarilly reject the message. The message processing stops at this stage, the sender receives the tempfail reply.  No further mailfromd handlers are called for this message.

The instructions on how to process the message are supplied to mailfromd in its configuration file.  It is normally called /usr/local/etc/mailfromd.rc (but can be located elsewhere, see --config-file) and contains a set of milter state handlers, or subroutines to be executed in various SMTP states.  Each interaction state can be supplied its own handling procedure.  A missing procedure implies continue response code.

A filter script can define up to eight milter state handlers, called after the names of milter states: connect, helo, envfrom, envrcpt, header, eoh, body, and eom.  The diagram below shows the control flow when processing an SMTP transaction.  The lines marked with C: show SMTP commands issued by the remote machine (the client), those marked with => show called handlers with their arguments.  An [R] appearing at the right end of a line indicates that this part of the transaction can be repeated any number of times:

Figure: Mailfromd Control Flow

     => connect(IP address)
     C: HELO DOMAIN
     helo(DOMAIN)
     for each message transaction
     do
             C: MAIL FROM SENDER
             => envfrom(SENDER)

             C: RCPT TO RECIPIENT                  [R]
             => envrcpt(RECIPIENT)

             C: DATA
             C: HEADER: VALUE                [R]
             => header(HEADER, VALUE)

             C:
             => eoh

             C: BODY-LINE                          [R]
             => /* Collect lines into blocks BLK of
             =>  * length LEN and call:
             =>  */
             => body(BLK, LEN)

             C: .
             => eom
      done

This control flow is maintained for as long as each called handler returns continue (see Actions).  Otherwise, if any handler returns accept or discard, the message processing continues, but no other handlers are called.  In the case of accept, the MTA will accept the message for delivery, in the case of discard it will silently discard it.

If any of the handlers returns reject or tempfail, the result depends on the handler: the envrcpt handler will cause this particular recipient address to be rejected, all other handlers will cause the whole message will be rejected.

The reject and tempfail actions executed by helo handler do not take effect immediately.  Instead their action is deferred until the next SMTP command from the client, which is usually MAIL FROM.

Simplest Configurations
=======================

The mailfromd configuration file contains a series of declarations of the handler procedures. Each declaration has the form:

     prog NAME
     do
         ...
     done

where prog, do and done are the keywords, and NAME is the state name for this handler.  The dots in the above example represent the actual code, or a set of commands, instructing mailfromd how to process the message.

For example, the declaration:

     prog envfrom
     do
         accept
     done

installs a handler for envfrom state, which always approves the message for delivery, without any further interaction with mailfromd.

The word accept in the above example is an action. Action is a special language statement that causes the execution of the program to stop and to return a response code to the Sendmail.  There are five actions, one for each response code: continue, accept, reject, discard, and tempfail.  Among these, reject and discard can optionally take one to three arguments.  The first argument is a three-digit RFC-2821 reply code.  It must begin with 5 for reject and with 4 for tempfail.  If two arguments are supplied, the second argument must be either an extended reply code (RFC 1893/2034) or a textual string to be returned along with the SMTP reply.  Finally, if all three arguments are supplied, then the second one must be an extended reply code and the third one must give the textual string.  The following examples illustrate the possible ways of using the reject statement:

     reject
     reject 503
     reject 503 5.0.0
     reject 503 "Need HELO command"
     reject 503 5.0.0 "Need HELO command"

Please note the quotes around the textual string.

Conditional Execution
=====================

Programs consisting of a single action are rarely useful, in most cases you will want to do some checking and decide whether to process the message depending on its result.  For example, if you do not want to accept messages from the address <badguy@some.net>, you could write the following program:

     prog envfrom
     do
         if $f = "badguy@some.net"
             reject
         else
             accept
         fi
     done

This example illustrates several important concepts.  First or all, $f in the third line is a Sendmail macro reference.  Sendmail macros are referenced the same way as in sendmail.cf, with the only difference that curly braces around macro names are optional, even if the name consists of several letters.  The value of a macro reference is always a string.

The equality operator (=) compares its left and right arguments and evaluates to true if the two strings are exactly the same, or to false otherwise.  Apart from equality, you can use the regular relational operators: !=, >, >=, < and <=.  Notice that string comparison in mailfromd is always case sensitive.  See tolower, and toupper, for the ways to translate strings to lower or upper case.)

The if statement decides what actions to execute depending on the value its condition evaluates to.  Its usual form is:

     if EXPRESSION THEN-BODY [else ELSE-BODY] fi

The THEN-BODY is executed if the EXPRESSION evaluates to true (i.e. to any non-zero value).  The optional ELSE-BODY is executed if the EXPRESSION yields false.  Both THEN-BODY and ELSE-BODY can contain other if statements, their nesting depth is not limited.  To facilitate writing complex conditional statements, the elif keyword can be used to introduce alternative conditions, for example:

     prog envfrom
     do
         if $f = "badguy@some.net"
             reject
         elif $f = "other@domain.com"
             tempfail 470 "Please try again later"
         else
             accept
         fi
     done

See switch, for more elaborate forms of conditional branching.

Domain Name System
==================

Site administrators often do not wish to accept mail from hosts not having a proper reverse delegation in the Domain Name System. The mailfromd language has a special function hostname, that looks up in the DNS the name corresponding to the IP address specified as its argument.  If there is no corresponding name, the function returns its argument unchanged.  This can be used to test if the IP was resolved, as illustrated in the example below:

     prog envfrom
     do
         if hostname $client_addr = $client_addr
            reject
         fi
     done

This example illustrates an important concept about mailfromd functions: if the function takes one argument, the actual argument is simply the next word after the function name.  For consistency with other programming languages, mailfromd language allows to give the argument within parentheses, like this: hostname($client_addr), but this is not required (In fact, parentheses in function calls are required only if the function takes several arguments, see Functions.).

An orthogonal function, which resolves the symbolic name to the corresponding IP address is provided as well.  Its name is resolve.

Checking Sender Address
=======================

The main purpose of mailfromd is verification of the sender address, therefore a special construct is provided for this purpose:

         on poll $f do
         when success:
             accept
         when not_found or failure:
             reject 550 5.1.0 "Sender validity not confirmed"
         when temp_failure:
             tempfail 450 4.1.0 "Try again later"
         done

The on poll construct runs standard verification (see standard verification) for the email address specified as its argument (in the example above it is the value of the Sendmail macro $f).  The check can result in the following conditions:

success
     The address exists.

not_found
     The address does not exist.

failure
     Some error of permanent nature occurred during the check.  The existence of the address cannot be verified.

temp_failure
     Some temporary failure occurred during the check.  The existence of the address cannot be verified at the moment.

The when branches of the on poll statement introduce statements, that should be executed upon each of these conditions.  If any condition occurs that is not handled within the on block, the runtime evaluator will signal an exception (For more information about exceptions and their handling, please refer to Exceptions.) and return temporary failure, therefore it is advisable to always handle all four conditions.  In fact, the condition handling in the above example is preferable for most normal configuration: the mail is accepted if the sender address is proved to exist and rejected otherwise.  If a temporary failure occurs, the remote party is urged to retry the transaction some time later.

The poll statement itself has a number of options that control the type of the verification.  These are discussed in detail in poll.

It is worth noticing that there is one special email address which is always available on any host, it is the null address <> used in error reporting.  It is best to not verify this address:

     prog envfrom
     do
         if $f == ""
             accept
         else
             on poll $f do
             when success:
                 accept
             when not_found or failure:
                 reject 550 5.1.0 "Sender validity not confirmed"
             when temp_failure:
                 tempfail 450 4.1.0 "Try again later"
             done
         fi
     done

SMTP Timeouts
=============

When using polling functions, it is important to take into account possible delay, which can occur in SMTP transactions.  Most often such delays are due to low network bandwidth, but sometimes remote sites insert them willingly, as a spam-fighting measure (My private opinion is that such practice is completely lame.)

Mailfromd polling functions implement three distinct timeout values:

Connection timeout
     Maximum time for establishing the initial TCP connection to the remote host.  If the connection is not established within this time interval, the polling returns temp_failure.

Initial response timeout
     Maximum time before receiving initial SMTP response from the remote host.

I/O timeout
     Maximum amount of time for finishing an input/output operation with the remote SMTP server.

These three timeouts can be set using the following pragmatic comments (see Pragmatic comments, for the detailed description of pragmatic comments.) in the configuration file:

     #pragma option connect-timeout INTERVAL
     #pragma option initial-response-timeout INTERVAL
     #pragma option io-timeout INTERVAL

Here, INTERVAL is the time interval, expressed in usual time units (see time interval specification, for its detailed description.), for example:

     #pragma option initial-response-timeout 1 minute 30 seconds

The default values are:

     #pragma option connect-timeout CONNECT-TIMEOUT
     #pragma option initial-response-timeout INITIAL-RESPONSE-TIMEOUT
     #pragma option io-timeout IO-TIMEOUT

You will most certainly encounter some servers that deliberately delay issuing the initial SMTP reply (in particular, I have noted that most CommuniGate Pro servers are guilty of this lame practice).  The default initial-response-timeout value should be enough to cope with most of them.  If you encounter some server that delays more than 30 seconds, you can raise the initial-response-timeout value.  However, in this case, I'd recommend rather informing its admin that his server settings are not tolerable.

Avoiding Verification Loops
===========================

An envfrom program consisting only of the on poll statement will work smoothly for incoming mails, but will create infinite loops for outgoing mails.  Indeed, upon sending the outgoing mail mailfromd will start the verification procedure, which will start an SMTP transaction with the own server.  This transaction will in turn trigger execution of on poll statement, etc. ad infinitum.  To avoid this, any properly written filter script should not run the verification procedure on the email addresses in those domains that are relayed by the server it runs on.  This can be achieved using relayed function.  The function returns true if its argument is contained in one of the predefined domain list files.  These files correspond to Sendmail plain text files used in F class definition forms (see Sendmail Installation and Operation Guide, chapter 5.3), i.e. they contain one domain name per line, with empty lines and lines started with # being ignored.  The domain files consulted by relayed function are defined using the special #pragma option relay statement:

     #pragma option relay "/etc/mail/local-host-names"
     #pragma option relay "/etc/mail/relay-domains"

The above example declares two domain list files, most commonly used in Sendmail installations to keep hostnames of the server (class w, see Sendmail Installation and Operation Guide, chapter 5.2.) and names of the domains, relayed by this server (class R).

Given all this, we can improve our filter program:

     #pragma option relay "/etc/mail/local-host-names"
     #pragma option relay "/etc/mail/relay-domains"

     prog envfrom
     do
         if $f == ""
             accept
         elif relayed hostname ${client_addr}
             accept
         else
             on poll $f do
             when success:
                 accept
             when not_found or failure:
                 reject 550 5.1.0 "Sender validity not confirmed"
             when temp_failure:
                 tempfail 450 4.1.0 "Try again later"
             done
         fi
     done

Proposed by Jan:

Another way to avoid infinite looping caused by endless recursive
triggering of on poll, is to accept relaying of all email originating
from the local IP cluster with (trusted) clients and SMTP servers,
provided that the server running mailfromd falls within this IP range:

     prog envfrom
     do
         if $f == ""
             accept
         elif match_cidr (${client_addr}, "199.232.0.0/16")
             accept
         else
             on poll $f do
             when success:
                 accept
             when not_found or failure:
                 reject 550 5.1.0 "Sender validity not confirmed"
             when temp_failure:
                 tempfail 450 4.1.0 "Try again later"
             done
         fi
     done

Here, triggering the on poll statement with more than 1 recursion is avoided for all local emails, originating from non-local IPs (outside of CIDR range 193.232.0.0/16) - when such an email arrives, handler execution falls through to on poll, which will cause the server to connect back to itself for local email verification, but this time, the on poll check will be skipped, as the server's own IP address will be catched by match_cidr statement. This method has particular advantage over the previous one: it does not rely on sendmail's relay-domains control, which can be, alone, too wide for sane relaying control.

HELO Domain
===========

Some of the mail filtering conditions may depend on the value of helo domain name, i.e. the argument to the SMTP EHLO (or HELO) command.  If you ever need such conditions, take into account the following caveats.  Firstly, although Sendmail passes the helo domain in $s macro, it does not do this consistently.  In fact, the $s macro is available only to helo handler, all other handlers won't see it, no matter what the value of the corresponding Milter.macros.HANDLER statement.  So, if you wish to access its value within an other handler, you will have to store it in a variable in helo handler and than use this variable value in the other handler.  This brings us to the concept of variables in mailfromd scripts.

A variable is defined using set statement, as shown below:

     set VARIABLE VALUE

where VARIABLE is the variable name and VALUE is the value to be assigned to it.  To get the value of a variable, the following notation is used: %VARIABLE.  The set statement can occur within handler declarations as well as outside of them.  All mailfromd variables are global, i.e. they are available to all handlers and functions.

The following example illustrates the approach that allows to use the helo domain name in any handler:

     # Declare the helohost variable
     set helohost ""

     prog helo
     do
         # Save the host name for further use
         set helohost $s
     done

     prog envfrom
     do
         # Reject hosts claiming to be localhost
         if %helohost = "localhost"
             reject 570 "Please specify real host name"
         fi
     done

Notice, that for this approach to work, the Sendmail statement Milter.macros.helo must contain s (see Sendmail Configuration).  This requirement can be removed by using the handler argument of helo.  Each mailfromd handler is given one or several arguments.  The exact number of arguments and their meaning are handler-specific and are described in handler-arguments, and milter-control-flow. The arguments are referenced by their ordinal number, using the notation $N.  The helo handler takes one argument, whose value is the helo domain.  Using this information, the helo handler from the example above can be rewritten as follows:

     prog helo
     do
         # Save the host name for further use
         set helohost $1
     done

Consider explaining this:

     prog envfrom
     do
         if %helohost = localhost
            or (%helohost matches '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$'
                and hostname %helohost = %helohost)
            or resolve %helohost = "0"
             reject 570 "Please specify real host name"
         fi
     done

Controlling Number of Recipients
================================

The simplest way to control the number of recipients per message in Sendmail is using the option MaxRecipientsPerMessage (Sendmail (tm) Installation and Operation Guide, chapter 5.6, O -- Set Option.).  However, it has its limitations and is not flexible, so you are often better off using mailfromd for this purpose.

Mailfromd keeps the number of recipients collected so far in variable %rcpt_count, which can be controlled in envrcpt handler as shown in the example below:

     prog envrcpt
     do
         if %rcpt_count > 10
             reject 550 5.7.1 "Too many recipients"
         fi
     done

This filter will accept no more than 10 recipients per message. You may achieve finer granularity by using additional conditions.  For example, the following code will allow any number of recipients if the mail is coming from a domain relayed by the server, while limiting it to 10 for incoming mail from other domains:

     prog envrcpt
     do
         if not relayed hostname $client_addr and %rcpt_count > 10
             reject 550 5.7.1 "Too many recipients"
         fi
     done

There are three important features to notice in the above code. First of all, it introduces two boolean operators: and, which evaluates to true only if both left-side and right-side expressions are true, and not, which reverses the value of its argument.

Secondly, the scope of an operation is determined by its precedence, or binding strength.  Not binds more tightly than and, so its scope is limited by the next expression between it and and.  Using parentheses to underline the operator scoping, the above if condition can be rewritten as follows:

         if (not (relayed hostname $client_addr)) and (%rcpt_count > 10)

Finally, it is important to notice that all boolean expressions are computed using shortcut evaluation.  To understand what it is, let's consider the following expression: X and Y.  Its value is true only if both X and Y are true.  Now suppose that we evaluate the expression from left to right and we find that X is false.  This means that no matter what the value of Y is, the resulting expression will be false, therefore there is no need to compute Y at all. So, the boolean shortcut evaluation works as follows:

X and Y
     If X => false, do not evaluate Y and return false.

X or Y
     If X => true, do not evaluate Y and return true.

To further enhance our sample filter, you may wish to make the reject output more informative, to let the sender know what the recipient limit is.  To do so, you can use the fact that in mailfromd adjacent string expressions concatenate, so the enhanced filter version could look like:

     set max_rcpt 10
     prog envrcpt
     do
         if not relayed hostname $client_addr and %rcpt_count > 10
             reject 550 5.7.1 "Too many recipients, max=" %max_rcpt
         fi
     done

When evaluating the third argument to reject, mailfromd will first convert %max_rcpt to string and then concatenate both strings together, producing string Too many recipients, max=10.

Sending Rate
============

We have introduced the notion of mail sending rate in Rate Limit.  Mailfromd keeps the computed rates in the special rate database (see Databases).  Each record in this database consists of a key, for which the rate is computed, and the rate value, in form of a double precision floating point number, representing the average number of messages per second sent by this key within the last sampling interval.  In the simplest case, the key can be the sender email address, however we recommend to use a conjunction EMAIL-SENDER_IP instead, so the actual EMAIL owner won't be blocked by actions of some spammer abusing his/her address.

To control and update sending rates, the rate function is provided.  It takes two arguments: key, whose meaning is described above, and interval, or the number of seconds, to which the actual sending rate value is converted.  Remember, that it is stored internally as a floating point number, and thus cannot be used in mailfromd filters, which operate only on integer numbers. To use the rate value, it is first converted to messages per given interval, which is an integer number.  For example, the number 0.138888 converted to 1-hour interval gives 500 (messages per hour).

Wherever the rate function is called, it recomputes and updates the rate record for the given KEY, and returns its value, converted to messages per interval.  For example, the following code limits the mail sending rate for each email address-IP combination to 180 per hour.  If the rate value is exceeded, the sender is returned a temporary failure response:

     prog envfrom
     do
         if rate($f "-" ${client_addr}, 3600) > 180
             tempfail 450 4.7.0 "Mail sending rate exceeded. Try again later"
         fi
     done

Notice argument concatenation, used to produce the key.

It is often inconvenient to specify intervals in seconds, therefore a special interval function is provided, which converts its argument, which is a textual string representing time interval in English, to the corresponding number of seconds.  Using this function, the function invocation would be:

          rate($f "-" ${client_addr}, interval("1 hour"))

For more information about rate function, rate.  The interval function is described in interval and time intervals are discussed in time interval specification.

Greylisting
===========

Greylisting is a simple method of defending against the spam proposed by Evan Harris.  In few words, it consists in recording the sender IP-sender email-recipient email triplet of mail transactions.  Each time the unknown triplet is seen, the corresponding message is rejected with tempfail code.  If the mail is legitimate, this will make the originating server will retry the delivery later, at which time the destination will accept it.  If, however, the mail is a spam, it will probably never be retried, so the users will not be bothered by it.  Even if the spammer will retry the delivery, the greylisting period will give spam-detection systems, such as DNSBLs, enough time to detect and blacklist it, so by the time the destination host starts accepting emails from this triplet, it will already be blocked by other means.

You will find the detailed description of the method in The Next Step in the Spam Control War: Greylisting (http://projects.puremagic.com/greylisting/whitepaper.html), the original whitepaper by Evan Harris.

The mailfromd implementation of greylisting is based on greylist function.  The function takes two arguments: the key, identifying the greylisting triplet, and the interval.  The function looks up the key in the greylisting database.  If such a key is not found, a new entry is created for it and the function returns true.  If the key is found, greylist returns false if it was inserted to the database more than interval seconds ago, and true otherwise. Thus, from the point of view of the  greylisting algorithm, the function returns true when the message delivery should be blocked, and the simplest implementation of the algorithm would be this:

     prog envrcpt
     do
      if greylist(${client_addr} "-" $f "-" ${rcpt_addr}, interval("1 hour"))
        tempfail 451 4.7.1 "You are greylisted"
      fi
     done

However, the message returned by this example, is not informative enough.  In particular, it does not tell when the message will be accepted.  To help you produce more informative messages the greylist function stores the number of seconds left to the end of the greylisting period in the global variable %greylist_seconds_left, so the above example could be enhanced as:

     prog envrcpt
     do
      set gltime interval("1 hour")
      if greylist(${client_addr} "-" $f "-" ${rcpt_addr}, %gltime)
        if %greylist_seconds_left = %gltime
           tempfail 451 4.7.1
              "You are greylisted for " %gltime " seconds"
        else
           tempfail 451 4.7.1
              "Still greylisted for " %greylist_seconds_left " seconds"
        fi
      fi
     done

In real life you will have to avoid greylisting some messages, in particular those coming from the <> address and from the IP addresses in your relayed domain.  It can easily be done using the techniques described in previous sections and is left as an exercises to the reader.

One special case is whitelisting, which is often used together with greylisting.  To implement it, mailfromd provides the function dbmap, which takes two arguments: dbmap(FILE, KEY).  The first argument is the name of the DBM file where to search for the key, the second one is the key to be searched.  Assuming you keep your whitelist database in file /var/run/whitelist.db, a more practical example will be:

     prog envrcpt
     do
      set gltime interval("1 hour")

      if not ($f = "" or relayed hostname ${client_addr}
              or dbmap("/var/run/whitelist.db", ${client_addr}))
        if greylist(${client_addr} "-" $f "-" ${rcpt_addr}, %gltime)
          if %greylist_seconds_left = %gltime
             tempfail 451 4.7.1
                "You are greylisted for " %gltime " seconds"
          else
             tempfail 451 4.7.1
                "Still greylisted for " %greylist_seconds_left " seconds"
          fi
        fi
      if
     done

Local Account Verification
==========================

In your filter script you may need to verify if the given user name is served by the server, in other words, to verify if it is a local account.  Notice that in this context, the word local does not necessarily mean that the account is local for the server running mailfromd, it simply means any account whose mailbox is served by the mail servers using mailfromd.

To verify the account, function validuser can be used.  The function takes one argument, the user name, and returns true if this name corresponds to a local account.  To verify this, the function relies on libmuauth, a powerful authentication library shipped with GNU mailutils, more precisely, it invokes a list of authorization functions.  Each function is responsible for looking up the user name in a particular source of information, such as system passwd database, SQL database, etc.  The search is terminated when either one of the functions finds the name in question or the list is exhausted.  In the former case, the account is local, in the latter it is not.  This concept is discussed in detail in see Authorization and Authentication Principles (GNU Mailutils Manual)), here we will give only the practical advices for implementing it in mailfromd filters.

The actual list of authorization module available depends on your mailutils, installation.  Usually it includes, apart from traditional UNIX passwd database, the functions for verifying PAM, RADIUS and SQL database accounts.  Each of the authorization methods is configured using special command line options.  Run mailfromd --help and you will get the listing of those under Authentication options section.  For the exact meaning of each particular option, refer to Authentication and Authorization Options (GNU Mailutils Manual).  Giving all required options in the command line can be inconvenient, you are advised to use mailutils system configuration file. See Mailutils Configuration File (GNU Mailutils Manual), for its description.  In short, its use boils down to placing there all the necessary authorization options, prefixed by the label :mailfromd.  For example, the following mailutils.rc statement:

     :mailfromd --authorization=pam:system \
                --pam-service=mailfromd

sets up the authorization using PAM and system passwd database.  The name of PAM service to use is mailfromd.

Databases
=========

Some mailfromd functions use DBM databases to save their persistent state data.  Each database has a unique identifier, and is assigned several pieces of information for its maintenance: the database file name and the expiration period, i.e. the time after which a record is considered expired.

To obtain the list of available databases along with their preconfigured settings, run mailfromd --show-defaults.  You will obtain a similar output:

     version:             VERSION
     configuration file:  /etc/mailfromd.rc
     user:                mail
     statedir:            /var/run//mailfromd
     socket:              unix:/var/run/mailfromd/mailfrom
     pidfile:             /var/run/mailfromd/mailfromd.pid
     database format:     Berkeley DB 2.x
     cache database:      /var/run//mailfromd/mailfromd.db
     cache expiration:    86400
     cache negative expiration: 43200
     greylist database:      /var/run//mailfromd/greylist.db
     greylist expiration:    86400
     rate database:      /var/run//mailfromd/rates.db
     rate expiration:    86400

The rows after database format describe the available databases.  Notice that the cache database, in contrast to the rest of databases, has two expire periods associated with it.  This is explained in the table below.

Database Formats
----------------

The version VERSION runs the following database types (or formats:

cache
     Cache database keeps the information about external emails, obtained using sender verification functions (see Checking Sender Address).  The key entry to this database is the email address or EMAIL:SENDER-IP string for addresses checked using strict verification.  The data its stores for each email are:

      1. Address validity.  This field can be either success or not_found, meaning the address is confirmed to exists or it does not.

      2. The time when the entry was entered into the database.  It is used to check for expired entries.

     The cache database has two expiration periods: a positive expiration period, that is applied to  entries with the first field set to success, and a negative expiration period, applied to entries marked as not_found.

rate
     The mail sending rate data, maintained by rate function (see rate).  A record consists of the following fields:

      1. Timestamp.  The time when the entry was entered into the database.  It is used to check for expired entries.

      2. Interval during which the rate was measured (seconds).

      3. Number of mails sent during this interval.

      4. Actual mail sending rate.

      5. Expected rate, i.e. the mail sending rate that would have been achieved if this sender had sent an email right now.

greylist
     This database is maintained by greylist function (see Greylisting).  Each record keeps only the timestamp.

Basic Database Operations
-------------------------

To list the contents of a database, use --list option. When used without any arguments it will list the cache database:

     $ mailfromd --list
     abrakat@mail.com           success Thu Aug 24 15:28:58 2006
     baccl@EDnet.NS.CA          not_found Fri Aug 25 10:04:18 2006
     bhzxhnyl@chello.pl       not_found Fri Aug 25 10:11:57 2006
     brqp@aaanet.ru:24.1.173.165  not_found Fri Aug 25 14:16:06 2006

You can also list data for any particular key or keys.  To do so, give the keys as arguments to mailfromd:

     $ mailfromd --list abrakat@mail.com brqp@aaanet.ru:24.1.173.165
     abrakat@mail.com           success Thu Aug 24 15:28:58 2006
     brqp@aaanet.ru:24.1.173.165  not_found Fri Aug 25 14:16:06 2006

To list another database, give its format identifier with the --format (-H) option.  For example, to list the rate database:

     $ mailfromd --list --format=rate
     sam@mail.net-62.12.4.3 Wed Sep  6 19:41:42 2006  139   3 0.0216 6.82e-06
     axw@rame.com-59.39.165.172 Wed Sep  6 20:26:24 2006  0  1  N/A  N/A

The --format option can be used with any database management option, described below.

Another useful operation you can do while listing rate database is the prediction of estimated time of sending, i.e. the time when the user will be able to send mail if currently his mail sending rate has exceeded the limit.  This is done using --predict option.  The option takes an argument, specifying the mail sending rate limit, e.g.:

     $ mailfromd --list --format=rate --predict="180 per 1 minute"
     ed@fae.net-21.10.1.2 Wed Sep 13 03:53:40 2006  0 1 N/A N/A; free to send
     service@19.netlay.com-69.44.129.19 Wed Sep 13 15:46:07 2006 7 2
        0.286   0.0224; in 46 sec. on Wed Sep 13 15:49:00 2006

(the second line is split for readability)

To delete an entry from the database, use --delete option, for example: mailfromd --delete abrakat@mail.com.  You can give any number of keys to delete in the command line.

Database Maintenance
--------------------

There are two principal operations of database management: expiration and compaction.  Expiration consists in removing expired entries from the database.  In fact, it is rarely needed, since the expired entries are removed in the process of normal mailfromd work, but sometimes it can be needed (for example, before dumping the database to another format, to avoid transferring useless information), so a special option is provided.

The command line option --expire instructs mailfromd to expire entries from the specified database.  As usual, the database is specified using --format option.  If it is not given explicitly, cache is assumed.

While removing expired entries the space they occupied is marked as free, so it can be used by subsequent inserts.  So, the database does not shrink after expiration is finished.  To actually return the unused space to the file system you should compact your database.

This is done by running mailfromd --compact (and, optionally, specifying the database to operate upon with --format option.  Notice, that compacting a database needs roughly as much disk space on the partition where the database resides as the database currently occupies.  The simultaneous access prevention while compacting the database depends on the underlying DBM implementation.  In general, it is locked, so the running copy of mailfromd is not able to write to it, but still can read the data from it.  However, I need to verify the BerkeleyDB case, just to be sure.  Although I have never experienced any problems while compacting BDBs.

Both --compact and --expire can be applied to all databases by combining them with --all.  It is useful, for example, in crontab files.  For example, I have the following monthly job in my crontab:

     0 1 1 * * /usr/local/sbin/mailfromd --compact --all

Testing Configuration
=====================

It is important to check your filter script before actually starting to use it.  There are several ways to do so.

To test the syntax of your filter script, use the --lint (-l) option.  It will cause mailfromd to exit immediately after attempting to compile the script file.  If the compilation succeeds, the program will exit with code 0.  Otherwise, it will exit with error code 78 (configuration error).  In the latter case, mailfromd will also print a diagnostic message, describing the error along with the exact location where the error was diagnosed, for example:

     mailfromd: /etc/mailfromd.rc:39: syntax error, unexpected ACT_REJECT,
     expecting OR or AND or ')'

If the script passes syntax check, the next step is often to test if it works as you expect it to.  This is done with --test (-t) command line option.  This option runs the envfrom handler (or another one, see below) and prints the result of its execution.  The values of the variables and Sendmail macros are given in the command line in the form of assignment: f=VALUE, for the macro f, and %x=VALUE, for the variable x.

This option is often useful in conjunction with options --debug, --trace and --transcript (see Logging and Debugging.  The following example shows what the author got while debugging the filter script described in Configuration File Example:

     $ mailfromd --test --debug=50 f=gray@gnu.org client_addr=127.0.0.1
     MX 20 mx20.gnu.org
     MX 10 mx10.gnu.org
     MX 10 mx10.gnu.org
     MX 20 mx20.gnu.org
     getting cache info for gray@gnu.org
     found status: success (0), time: Thu Sep 14 14:54:41 2006
     getting rate info for gray@gnu.org-127.0.0.1
     found time: 1158245710, interval: 29, count: 5, rate: 0.172414
     rate for gray@gnu.org-127.0.0.1 is 0.162162
     updating gray@gnu.org-127.0.0.1 rates
     SET REPLY 450 4.7.0 Mail sending rate exceeded. Try again later
     State envfrom: tempfail

To test any handler other than envfrom, give its name as the argument to --test option.  Since this argument is optional, it is important that it be given immediately after the option, without any intervening white space, for example mailfromd --test=helo, or mailfromd -thelo.

This method may not suite for some sophisticated configurations, for example if some values are passed between the handlers via global variables.  To test such configurations we recommend the following approach:

 1. Prepare both mailfromd and sendmail configuration files.

 2. Start mailfromd, preferably in foreground mode (see --foreground) with the desired debugging options.

 3. On another console, start sendmail -bs.  This will start the SMTP conversation on standard input and output.  Enter SMTP commands and see if the MTA reaction is appropriate.  To check mailfromd diagnostics, flip to the tty where it runs.

Logging and Debugging
=====================

Depending on its operation mode mailfromd tries to guess whether it is appropriate to print its diagnostics and informational messages on standard error or to send them to syslog.  By default standard error is used if the program is given one of the database management options (see Databases, or --test option (see Testing Configuration.  Otherwise, syslog is used.  To alter these defaults, two command line options are provided: --stderr to print everything to standard error and --syslog to output all diagnostics to syslog.

The syslog facility where the messages are being sent is controlled by --log-facility option.  Argument to this option is a valid syslog facility name, i.e. one of: user, daemon, auth (As of mailutils 1.0, authpriv is not supported), mail, and local0 through local7. The argument can be given in upper on mixed cases as well, and it can be prefixed by log_

Default syslog facility is mail, although that depends on the defaults mailutils has been compiled with.

Another syslog-related parameter that can be configured is the tag used to identify mailfromd messages.  By default the program name is used.  The --log-tag (-L) option can be used to change it.

As all UNIX utilities, mailfromd is very quiet unless it has something important to communicate, for example an error condition or something similar.  A set of command line options is provided that controls the output verbosity.

First of all, the --trace option enables tracing actions executed during the message verification.  When this option is given, any accept, discard, continue, etc. triggered during the execution of your filter program will leave its trace in the log file.  Here is an example of how it looks like (syslog time stamp, tag and pid removed for readability):

     k8DHxvO9030656: /etc/mailfromd.rc:45: reject 550 5.1.1 Sender validity
     not confirmed

This shows that while verifying the message with ID k8DHxvO9030656 the reject action was executed by filter script /etc/mailfromd.rc at line 45.

The appearance of the message ID in the log deserves a special notice. The program will always identify its log messages with the Message-Id, when it is available.  You responsibility as administrator is to make sure it is available by configuring Sendmail to export the macro i to mailfromd. The rule of thumb is: make i available to the very first handler mailfromd executes.  It is not necessary to export it to the rest of the handlers, since mailfromd will cache it.  For example, if your filter script contains envfrom and envrcpt handlers, export i for envfrom. See Sendmail Configuration, for the instructions on how to ensure it.

To push the log verbosity further, use #pragma option debug (see pragma debug) or its command line equivalent, --debug (-d, see --debug).  It takes a numeric argument specifying the relative verbosity level in the range between 0 and 100.  Zero means disabling verbose output, while 100 produces impractically verbose output, suitable only for developers of mailfromd.

To control execution of sender verification functions (see Polling functions), you may wish to use --transcript (-X) command line option which enables transcripting SMTP sessions in the logs.  Here is an example of the output produced running mailfromd --debug=1 --transcript:

     k8DHxlCa001774: RECV: 220 spf-jail1.us4.outblaze.com ESMTP Postfix
     k8DHxlCa001774: SEND: HELO mail.gnu.org.ua
     k8DHxlCa001774: RECV: 250 spf-jail1.us4.outblaze.com
     k8DHxlCa001774: SEND: MAIL FROM: <>
     k8DHxlCa001774: RECV: 250 Ok
     k8DHxlCa001774: SEND: RCPT TO: <t1Kmx17Q@malaysia.net>
     k8DHxlCa001774: RECV: 550 <>: No thank you rejected: Account
      Unavailable: Possible Forgery
     k8DHxlCa001774: poll exited with status: not_found; sent
      "RCPT TO: <t1Kmx17Q@malaysia.net>", got "550 <>: No thank you
      rejected: Account Unavailable: Possible Forgery"
     k8DHxlCa001774: SEND: QUIT

Filter Script Language
**********************

Mailfromd configuration file is located in SYSCONFDIR/mailfromd.rc (by default /usr/local/etc/mailfromd.rc).  It allows to control all aspects of program behavior.

The file has a simple syntax, similar to that of Bourne shell.  The input language is designed so that it does not need any special terminating or separating characters (like, e.g. newlines and semicolons in shell).  All syntactical entities are separated by any amount of white-space characters (i.e. spaces, tabulations or newlines).

The following sections describe filter script syntax in detail.

Comments
========

Two types of comments are allowed: C-style, enclosed between /* and */, and shell-style, starting with # character and extending up to the end of line:

     /* This is
        a comment. */
     # And this too.

There are, however, two special cases, where # is not treated as a comment.

If # is followed by word include (with optional whitespace between them), this statement requires inclusion of the specified file, as in C (A note for C programmers: quotes around include arguments are optional.  You can use either double or single quotes, yet you cannot use angle brackets (< and >).):

     #include /etc/mailfromd/settings

This statement produces the same effect as if the contents of the named file were inserted at this line.

If # is immediately followed by word pragma (with optional whitespace between them), such a construct introduces a pragmatic comment, i.e. an instruction that controls some configuration setting.  Following types of pragmatic comments are currently implemented: #pragma option changes a value of a run-time option, #pragma database configures database settings, and #pragma regex controls use of regular expressions in the configuration file.

Following subsections discuss each of these pragma types.

Pragma option
-------------

The syntax of #pragma option is:

     #pragma option NAME VALUE

where NAME is the name of the command line option (without leading dashes) to set, and VALUE is its new value.  The effect of this statement is the same as that of the command line option --NAME=VALUE.

The VALUE is specific for each particular option.  The supported value types are:

number
     A decimal number.

string
     A string of characters.  It should be enclosed in a pair of quotes if it contains whitespace characters (use either single or double quotes, at your option).

boolean
     A boolean value: yes, true or t to indicate a true value, and no, false or nil to indicate a false value.

address
     An IP address in ``dotted-quad'' notation or a fully-qualified host name.

interval
     The time interval specification is a string that defines an interval, much the same way we do this in English: it consists of one or more pairs number-time unit.  For example, the following are valid interval specifications:

          1 hour
          2 hours 35 seconds
          1 year 7 months 2 weeks 2 days 11 hours 12 seconds

     The pairs can occur in any order, however unusual it could sound to a human ear, e.g. 2 days 1 year.  If the time unit is omitted, seconds are supposed.

     When used in a #pragma option statement, interval values need not be quoted.

The following options control various database expiration intervals.  The use of these options is deprecated, please use the corresponding #pragma database option.

 -- pragma option: expire-interval INTERVAL
     Sets expiration interval for all databases.

 -- pragma option: positive-expire-interval INTERVAL
     Sets expiration interval for positive cache entries.  Use #pragma database cache positive-expire-interval instead.

 -- pragma option: negative-expire-interval INTERVAL
     Sets expiration interval for negative cache entries.  Use #pragma database cache negative-expire-interval instead.

 -- pragma option: rates-expire-interval INTERVAL
     Sets expiration interval for entries in the rates database.  Use #pragma database rate expire-interval instead.

The following options control I/O operations over TCP.

 -- pragma option: source ADDRESS
     Sets source address for TCP connections.

 -- pragma option: connect-timeout INTERVAL
     Sets initial connection timeout.  If the connection is not established within this time, the corresponding probing function returns temporary failure.  The default value is CONNECT-TIMEOUT. See SMTP Timeouts, for the detailed description.

 -- pragma option: initial-response-timeout INTERVAL
     Sets the time to wait for the initial SMTP response.  Default is INITIAL-RESPONSE-TIMEOUT.  See SMTP Timeouts, for the detailed description.

 -- pragma option: io-timeout INTERVAL
 -- pragma option: timeout INTERVAL
     Sets I/O operation timeout in seconds. Mailfromd will wait the given amount of time for the success of each I/O operation with the remote MX.  Default timeout is IO-TIMEOUT. See SMTP Timeouts, for the detailed description.

     The form timeout is retained for backward compatibility and is considered deprecated.

The following options provide the default settings for poll statement (see Polling).

 -- pragma option: mailfrom STRING
     Sets default email addresses used in MAIL FROM: SMTP command when probing the remote host.  This value can be overridden by as parameter to poll command (see poll).  For example:

          #pragma option mailfrom "postmaster@my.domain.com"

     You can set this value to a comma-separated list of email addresses, in which case the probing will try each address until either the remote party accepts it or the list of addresses is exhausted, whichever happens first.

     It is not necessary to enclose emails in angle brackets, as they will be added automatically where appropriate.  The only exception is null return address, when used in a list of addresses.  In this case, it should always be written as <>.  For example:

          #pragma option mailfrom postmaster@my.domain.com, <>

     Default value is "" (empty string), resulting in <> email address.

 -- pragma option: ehlo STRING
     Sets default domain used in EHLO (or HELO) SMTP command when probing the remote host.  This value can be overridden by from parameter to poll command (see poll).

The following options control debugging and logging facilities:

 -- pragma option: debug STRING
     Sets debugging level.  STRING is a decimal number in the range 0 -- 100.  Level 0 effectively disables debugging, while level 100 enables the most verbose debugging output.  See Logging and Debugging.

 -- pragma option: source-info BOOLEAN
     When used together with #pragma option debug, this option includes source information into the debugging output.  Use this if you are debugging mailfromd.

These options control program privileges after startup:

 -- pragma option: user STRING
     Switch to this user's privileges after startup. Describe it. Refer to --group.

 -- pragma option: group STRING
     Retain the supplementary group STRING when switching to user privileges.  By default mailfromd clears the list of supplementary groups when switching to user privileges, this option allows to retain the given group.  It can be specified multiple times to retain several groups.  Use of this option can be necessary to maintain proper access rights for various files.  See Starting and Stopping.

The following options control interaction with Sendmail:

 -- pragma option: port STRING
     Sets the communication port data.  The STRING value is a port setting in Milter notation:

     unix:FILE
     local:FILE
          A named pipe (socket).

     inet:PORT@ADDRESS
          An IPV4 connection to host ADDRESS at port PORT.  Port must be specified either as a decimal number or a string representing the port name in /etc/services.

     inet6:PORT@ADDRESS
          An IPV6 connection to host ADDRESS at port PORT.  This port type is not yet supported.

 -- pragma option: milter-timeout INTERVAL
     Set the timeout value for connection between the filter and the MTA.  Default value is MILTER-TIMEOUT.  You normally do not need to change this value.

The following options can be used to tune database file locking:

 -- pragma option: lock-retry-count NUMBER
     Set maximum number of attempts to acquire the lock.  The time between each two successive attempts is given by lock-retry-timeout pragma (see below).  After NUMBER failed attempts, mailfromd gives in and opens file in read-only mode.

 -- pragma option: lock-retry-timeout INTERVAL
     Set the time span between the two locking attempts.

Other options:

 -- pragma option: relay STRING
     Specifies the file containing names of email domains relayed by this machine.  Multiple pragmas accumulate.  The constructed list of domains is analyzed by relayed function (see relayed).  For example:

          #pragma option relay "/etc/mail/sendmail.cw"
          #pragma option relay "/etc/mail/relay-domains"

 -- pragma option: pidfile STRING
     Introduces the full name of a file where mailfromd will store its PID number.  The file must be writable for the user mailfromd runs as (see pragma user).

Pragma database
---------------

The #pragma database statement is used to control file names and expiration periods used for various DBM databases.  Its syntax is:

#pragma database DBNAME file FILENAME
     Set the database file name for the database DBNAME.

#pragma database DBNAME expire-interval INTERVAL
     Set the expiration interval for the database DBNAME. (see time interval specification, for the syntax of INTERVAL).

The parameter DBNAME can be one of the following: cache, rate and greylist for main cache, sending rate and greylisting databases, correspondingly.  Since the cache database implements two different timeouts, the latter statement has two special forms for it:

#pragma database cache positive-expire-interval INTERVAL
     Set expiration interval for positive (success) cache entries.

#pragma database cache negative-expire-interval INTERVAL
     Set expiration interval for negative (not_found) cache entries.

Pragma regex
------------

Another pragmatic comment, #pragma regex, controls type of regular expressions used in the filter script.  You can use any number of such pragma directives in your mailfromd.rc.  The scope of #pragma regex extends to the next occurrence of this directive or to the end of configuration file, whichever occurs first.

The syntax of this pragma is:

     #pragma regex LIST-OF-FLAGS

where LIST-OF-FLAGS is a whitespace-separated list of regex flags.  Each regex-flag is a word specifying some regex feature.  It can be preceded by + to enable this feature (this is the default), by - to disable it or by = to reset regex flags to its value.  Valid regex-flags are:

extended
     Use POSIX Extended Regular Expression syntax when interpreting regex.  If not set, POSIX Basic Regular Expression syntax is used.

icase
     Do not differentiate case.  Subsequent regex searches will be case insensitive.

newline
     Match-any-character operators don't match a newline.

     A non-matching list ([^...]) not containing a newline does not match a newline.

     Match-beginning-of-line operator (^) matches the empty string immediately after a newline.

     Match-end-of-line operator ($) matches the empty string immediately before a newline.

For example, the following pragma enables POSIX extended, case insensitive matching (a good thing to start your mailfromd.rc with):

     #pragma regex +extended +icase

Data Types
==========

The mailfromd filter script language operates on entities of two types: numeric and string.

The numeric type is represented internally as an unsigned long integer.  Depending on the machine architecture, it can have different size.  On machines with Intel-based CPUs it is 32 bits long.

A string is a string of characters of arbitrary length. Strings can contain any characters except ASCII NUL (0).

Literals
========

A literal is any sequence of alpha-numeric characters that is not a reserved word (see Reserved Words), or any sequence of characters enclosed in double or single quotation marks.

Literals consisting of only numeric characters represent decimal unsigned integers.

Quoted literals and literals beginning with a letter or underscore represent strings.

After tempfail and reject actions two special kinds of literals are recognized: three-digit numeric values represent RFC-2821 reply codes, and literals consisting of tree digit groups separated by dots represent an extended reply code as per RFC 1893/2034.  For example:

     510   # A reply code
     5.7.1 # An extended reply code

Double-quoted strings
---------------------

String literals enclosed in double quotation marks (double-quoted strings) are subject to backslash interpretation, macro expansion, variable interpretation and back reference interpretation.

Backslash interpretation is performed at compilation time.  It consists in replacing the following escape sequences with the corresponding single characters:

Table: Backslash escapes

Sequence   Replaced with
\a   Audible bell character (ASCII 7)
\b   Backspace character (ASCII 8)
\f   Form-feed character (ASCII 12)
\n   Newline character (ASCII 10)
\r   Carriage return character (ASCII 13)
\t   Horizontal tabulation character (ASCII 9)

In addition, the sequence \NEWLINE has the same effect as \n, for example:

     "a string with\
      embedded newline"
     "a string with\n embedded newline"

Any escape sequence of the form \xHH, where H denotes any hex digit is replaced with the character whose ASCII value is HH.  For example:

     "\x61nother" => "another"

Similarly, an escape sequence of the form \0OOO, where O is an octal digit, is replaced with the character whose ASCII value is OOO.

Macro expansion and variable interpretation occur at runtime.  During these phases all Sendmail macros and mailfromd variables, referenced in the string, are replaced by their actual values. See Variables and Macros, for the description of macro and variable referencing syntax.  For example, if the Sendmail macro $f has the value postmaster@gnu.org.ua and the variable
last_ip has the value 127.0.0.1, then the string (Implementation note: actually, the references
are not interpreted within the string, instead, each such string is
split at compilation time into a series of concatenated atoms.  Thus,
our sample string will actually be compiled as:)

     $f " last connected from " %last_ip ";"

See Concatenation, for the description of this construct.  You can easily see how various strings are interpreted by using --dump-tree option (see --dump-tree).  For our sample string it will produce:

       CONCAT:
         CONCAT:
           CONCAT:
             CONCAT:
               CONSTANT: ""
               SYMBOL: f
             CONSTANT: " last connected from "
           VARIABLE last_ip (13)
         CONSTANT: ";"

     "$f last connected from %last_ip;"

will be expanded to

     "postmaster@gnu.org.ua last connected from 127.0.0.1;"

A back reference is a sequence \D, where D is a decimal number.  It refers to the Dth parenthesized subexpression in the last matches statement (The subexpressions are numbered by the positions of their opening parentheses, left to right.).  Any back reference occurring within a double-quoted string is replaced by the value of the corresponding subexpression.  See Special comparisons, for the detailed description of this process.  Back reference interpretation is performed at run time.

Single-quoted strings
---------------------

The characters enclosed in single quotation marks are read unmodified.

The following examples contain pairs of equivalent strings:

     "a string"
     'a string'

     "\\(.*\\):"
     '\(.*\):'

Notice the last example.  Single quotes are particularly useful in writing regular expressions (see Special comparisons).

Notice also, that although unquoted literals are allowed, their use is not encouraged, since they create syntactic ambiguities that can make discovering real syntax errors difficult.  Therefore mailfromd will always warn about such literals.

Here Documents
==============

Special form of a string literal is here-document, allowing to specify multiline strings without having to recurse to backslash escapes.  The format of here-documents is:

     <<[FLAGS]WORD
     ...
     WORD

The <<WORD construct instructs the parser to read all the following lines up to the line containig only WORD, with possible trailing blanks.  The lines thus read are concatenated together into a single string.  For example:

     set str <<EOT
     A multiline
     string
     EOT

The body of a hear-document is interpreted the same way as double-quoted strings (see Double-quoted strings).  For example, if Sendmail macro f has the value jsmith@some.com and the variable %count is set to 10, the following string:

     set s <<EOT
     <$f> has tried to send %count mails.
     Please see docs for more info.
     EOT

will be expanded to:

     <jsmith@some.com> has tried to send 10 mails.
     Please see docs for more info.

If the WORD is quoted, either by enclosing it in single quote characters or by prepending it with a backslash, all interpretations and expansions within the document body are suppressed.  For example:

     set s <<'EOT'
     The following line is read verbatim:
     <$f> has tried to send %count mails.
     Please see docs for more info.
     EOT

Optional FLAGS in the here-document construct control the way leading white space is handled.  If FLAGS is - (a dash), then all leading tab characters are stripped from input lines and the line containing WORD.  Furthermore, if - is followed by a single space, all leading whitespace is stripped from them.  This allows here-documents within configuration scripts to be indented in a natural fashion.  Examples:

     <<- TEXT
         <$f> has tried to send %count mails.
         Please see docs for more info.
     TEXT

Here-documents are particularly useful with reject actions (see reject.

Variables and Macros
====================

There are two kinds of variable entities in mailfromd scripts: Sendmail macros and proper variables.

Sendmail macros are referenced exactly the same way they are in sendmail.cf configuration file, i.e. $NAME, where NAME represents the macro name.  Notice, that the notation is the same for both single-character and multi-character macro names. For consistency with the Sendmail configuration the ${NAME} notation is also accepted.

Sendmail macros evaluate to string values.

Notice, that to reference a macro, you must properly export it in sendmail.cf using Milter.macros statement (see Sendmail Configuration).  Attempt to reference a not exported macro will result in raising an unhandled exception at the run time (see Exceptions).

Variables are referenced using the notation %NAME, where NAME is the variable name.  Variable names must begin with a letter or underscore and must consist of letters, digits and underscores.  Variables are not declared, they spring into existence when they are assigned a value using set statement.  All mailfromd variables are global.

Several variables are predefined. In mailfromd version VERSION these are:

 -- Variable: Predefined Variable number cache_used
     This variable is set by stdpoll and strictpoll built-ins (and, consequently, by the on poll statement).  Its value is 1 if the function used the cached data instead of directly polling the host, and 0 if the polling took place. See Polling functions.

     You can use this variable to make your reject message more informative for the remote party.  The common paradigm is to define a function, returning empty string if the result was obtained from polling, or some notice if cached data were used, and to use the function in the reject text, for example:

          func cachestr() returns s
          do
            if %cache_used
              return "[CACHED] "
            else
              return ""
            fi
          done

     Then, in prog envfrom one can use:

          on poll $f
          do
            when not_found or failure:
                reject 550 5.1.0 cachestr() "Sender validity not confirmed"
          done

 -- Predefined Variable: string clamav_virus_name
     Name of virus identivied by ClamAV.  Set by clamav function (see ClamAV).

 -- Predefined Variable: number greylist_seconds_left
     Number of seconds left to the end of the greylisting period.  Set by greylist function (see Special test functions).

 -- Predefined Variable: string ehlo_domain
     Name of the domain used by polling functions in SMTP EHLO or HELO command.  It is set by #pragma option ehlo directive (see pragma ehlo), or via --ehlo command line option.

 -- Variable: Predefined Variable string last_poll_host
     Polling functions (see Polling functions) set this variable before returning.  It contains the host name or IP address of the last polled host.

 -- Variable: Predefined Variable string last_poll_recv
     Polling functions (see Polling functions) set this variable before returning.  It contains the last SMTP reply received from the remote host.  In case of multi-line replies, only first line is stored.  If nothing was received the variable contains the string nothing.

 -- Variable: Predefined Variable string last_poll_send
     Polling functions (see Polling functions) set this variable before returning.  It contains the last SMTP command sent to the polled host.  If nothing was sent, last_poll_send contains the string nothing.

 -- Predefined Variable: string mailfrom_address
     Email address used by polling functions in SMTP MAIL FROM command.  Set by #pragma option mailfrom directive (see pragma mailfrom) or via --mailfrom command line option.

 -- Predefined Variable: number sa_code
     Spam score for the message, set by sa function (see sa).

 -- Predefined Variable: number sa_threshold
     Spam threshold, set by sa function (see sa).

 -- Predefined Variable: string sa_keywords
     Spam keywords for the message, set by sa function (see sa).

 -- Predefined Variable: number rcpt_count
     The variable rcpt_count keeps the number of recipients given so far by RCPT TO commands.  It is defined only in envrcpt handlers.

Back references
===============

A back reference is a sequence \D, where D is a decimal number.  It refers to the Dth parenthesized subexpression in the last matches statement (The subexpressions are numbered by the positions of their opening parentheses, left to right.).  Any back reference occurring within a double-quoted string is replaced by the value of the corresponding subexpression.  For example:

     if $f matches '.*@\(.*\)\.gnu\.org\.ua'
       set host \1
     fi

If the value of f macro is smith@unza.gnu.org.ua, the above code will assign the string unza to the variable host.

Notice that each occurrence of matches will reset the table of back references, so try to use them as early as possible.  The following example illustrates a possible error, when the back reference is used after the reference table was reused by another matching:

     # Wrong!
     if $f matches '.*@\(.*\)\.gnu\.org\.ua'
       if $f matches 'some.*'
         set host \1
       fi
     fi

This will produce the following run time error:

     mailfromd: RUNTIME ERROR near file.rc:3: Invalid back-reference number

because the inner match (some.*) does not have any parethesized subexpressions.

See Special comparisons, for more information about matches operator.

Handlers
========

Milter stage handler (or handler, for short) is a subroutine responsible for processing a particular milter state. There are eight handlers available.  Their order of invocation and arguments are described in milter-control-flow.

A handler is defined using the following construct:

     prog HANDLER-NAME
     do
         HANDLER-BODY
     done

where HANDLER-NAME is the name of the handler (see handler names), HANDLER-BODY is the list of filter statements composing the handler body.  Some handlers take arguments, which can be accessed from within the HANDLER-BODY using the notation $N, where N is the ordinal number of the argument.  The following table shows each handler with its arguments:

Table: State Handler Arguments

Handler   $1   $2
connect   IP address of the remote host   N/A
helo   HELO domain   N/A
envfrom   Sender email address   N/A
envrcpt   Recipient email address   N/A
header   Header name   Header value
eoh   N/A   N/A
body   Body segment (string)   Length of the segment (numeric)
eom   N/A   N/A

Functions
=========

A function is a named mailfromd subroutine, which takes zero or more parameters and returns certain value. Depending on the return value, functions can be subdivided into string functions and number functions.  The number of parameters is fixed for each function.  When invoked, the function must be supplied exactly as many actual arguments as it has parameters.

The function invocation syntax is:

       NAME (ARGS)

where NAME is the function name and ARGS is a comma-separated list of variables.  Single-argument functions compose a special subclass: for convenience they can be invoked without parentheses around their argument.  Summarizing, following are valid function calls:

     # Single-argument functions:
       foo 10
       interval "1 hour"
       interval("1 hour")
     # Multi-argument functions:
       greylist("/var/my.db", 180)

The number of parameters a function takes and their data types compose the function signature.  When actual arguments are passed to the function, they are converted to types of corresponding formal parameters.

There are major groups of functions: built-in and user-defined.

Built-in Functions
------------------

The built-in functions are always available for use in your filter-scripts.

String Manipulation Functions
.............................

 -- Built-in Function: string domainpart (string STR)
     Returns the domain part of STR, if it is a valid email address, otherwise returns STR itself.

          domainpart "gray" => "gray"
          domainpart "gray@gnu.org.ua" => "gnu.org.ua"

 -- Built-in Function: number interval (string STR)
     Converts STR, which should be a valid time interval specification (see time interval specification), to seconds.

 -- Built-in Function: number length (string STR)
     Returns the length of the string STR in bytes.

          length "string" => 6

 -- Built-in Function: string localpart (string STR)
     Returns the local part of STR if it is a valid email address, otherwise returns STR unchanged.

          localpart "gray" => "gray"
          localpart "gray@gnu.org.ua" => "gray"

 -- Built-in Function: string substring (string STR,
     number START, number END)
     Returns a substring of STR between offsets START and END, inclusive.  To obtain a substring from START to the end of the string, use substring(STR, START, -1):

          substring("mailfrom", 0, 3) => "mail"
          substring("mailfrom", 2, 5) => "ilfr"
          substring("mailfrom", 4, -1 ) => "from"
          substring("mailfrom", 4, length("mailfrom") - 1) => "from"

     This function signals range exception if either START or END are outside the string length.

 -- Built-in Function: string tolower (string STR)

     Returns a copy of the string STR, with all the upper-case characters in translated to their corresponding lower-case counterparts. Non-alphabetic characters are left unchanged.

          tolower "MAIL" => "mail"

 -- Built-in Function: string toupper (string STR)

     Returns a copy of the string STR, with all the lower-case characters translated to their corresponding upper-case counterparts. Non-alphabetic characters are left unchanged.

          toupper "mail" => "MAIL"

Polling Functions
.................

We have described the base notions about sender address verification (or polling, for short) in Checking Sender Address.  Here we will describe the functions mailfromd offers for this purpose.

 -- Built-in Function: number _pollhost
     (string IP, string EMAIL, string DOMAIN,
     string MAILFROM)
     Poll SMTP host IP for email address EMAIL, using DOMAIN as EHLO domain and MAILFROM as MAIL FROM.  Returns 0 or 1 depending on the result of the test. In contrast to strictpoll function, this function does not use cache database and does not fall back to polling MX servers if the main poll tempfails.  The function can throw one of the following exceptions: failure, temp_failure.

 -- Built-in Function: number _pollmx
     (string IP, string EMAIL, string DOMAIN,
     string MAILFROM)
     Poll MX-s of the DOMAIN for email address EMAIL, using DOMAIN as EHLO domain and MAILFROM as MAIL FROM address.  Returns 0 or 1 depending on the result of the test. In contrast to stdpoll function, _pollmx does not use cache database and does not fall back to polling the IP if the poll fails.  The function can throw one of the following exceptions: failure, temp_failure.

 -- Built-in Function: number stdpoll
     (string EMAIL, string DOMAIN, string MAILFROM)

     Performs standard poll for EMAIL, using DOMAIN as EHLO domain and MAILFROM as MAIL FROM address. Returns 0 or 1 depending on the result of the test.  Can raise one of the following exceptions: failure, temp_failure.

     In on statement context, it is synonymous to poll without explicit HOST.  more details and references

 -- Built-in Function: number strictpoll
     (string HOST, string EMAIL,
     string DOMAIN, string MAILFROM)

     Performs strict poll for EMAIL on host HOST. See the description of stdpoll for the detailed information.

     In on context, it is synonymous to poll host HOST.

The MAILFROM argument can be a comma-separated list of email addresses, which can be useful for servers that are unusually picky about sender addresses.  It is advised, however, that this list always contain <> address.  For example:

     _pollhost($client_addr, $f, "domain", "postmaster@my.net,<>")

See also pragma mailfrom.

Before returning, all described functions set following built-in variables:

Table: Variables set by polling functions

Variable   Contains
last_poll_host   Host name or IP address of the last polled
host.
last_poll_send   Last SMTP command, sent to this
host.  If nothing was sent, it contains literal string nothing.
last_poll_recv   Last SMTP reply received from this
host.  In case of multi-line replies, only first line is stored.  If
nothing was received the variable contains the string nothing.
cache_used   1 if cached data were used instead of
polling, 0 otherwise.  This variable is set by stdpoll
and strictpoll.  If it equals 1, none of the above
variables are modified.  See cache_used example, for an example.

DNS and IP-related Functions
............................

 -- Built-in Function: boolean hasmx (string DOMAIN)
     Returns true if the domain name given by its argument has any MX records.

 -- Built-in Function: string hostname (string IP)
     The IP argument should be a string representing an IP address in dotted-quad notation.  The function returns the canonical name of the host with this IP address obtained from DNS lookup.  For example

          hostname ${client_addr}

     returns the fully qualified domain name of the host represented by Sendmail variable client_addr.

     Notice that the hostname function caches its results, so no matter how many times hostname ${client_addr} will be used in mailfromd.rc, it will result in at most one DNS lookup.

 -- Built-in Function: boolean ismx (string DOMAIN,
     string HOST)
     The DOMAIN argument is any valid domain name, the HOST is a host name or IP address.

     The function returns true if HOST is one of the MX records for the DOMAIN.

 -- Built-in Function: string resolve (string HOST)
     Reverse of hostname.  The resolve function returns the IP address for the host name specified by HOST argument.  If the host name cannot be resolved, the function returns "0".

 -- Built-in Function: boolean match_cidr (string IP,
     string CIDR)
     Returns true if the IP address IP pertains to the IP range CIDR.  The first argument, IP, is a string representation of an IP address.  The second argument, CIDR, is a string representation of a IP range in CIDR notation, i.e. "A.B.C.D/N", where A.B.C.D is an IPv4 address and N specifies the prefix length -- the number of shared initial bits, counting from the left side of the address.

     The following example will reject the mail if the IP address of the sending machine does not belong to the block 10.10.1.0/19:

          if not match_cidr(${client_addr}, "10.10.1.0/19")
             reject
          fi

Database Functions
..................

 -- Built-in Function: boolean dbmap (string DB,
     string KEY)
     Looks up KEY in the DBM file DB and returns true if it is found.  See whitelisting, for more detailed discussion.

 -- Built-in Function: boolean relayed (string DOMAIN)
     Returns true if the string DOMAIN is found in one of relayed domain files (see pragma relay, and see option relay). The usual construct is:

          relayed hostname ${client_addr}

     which yields true if the IP address from Sendmail variable client_addr is relayed by the local machine.

Interfaces to Third-Party Programs
..................................

A set of functions is defined for interfacing with other filters via TCP.  Currently implemented are interfaces with SpamAssassin spamd daemon and with ClamAV antivirus.

These functions can be used only in eom handler.

Both interfaces work much the same way: the remote filter is connected and the message is passed to it.  If the remote filter confirms that the message matches its requirements, the function returns true.  Notice that in practice that means that such a message should be rejected or deferred.

The address of the remote filter is supplied as the first argument in the form of a standard URL:

     PROTO://PATH[:PORT]

The PROTO part specifies the connection protocol.  It should be tcp for the TCP connection and file or socket for the connection via UNIX socket.  In the latter case the PROTO part can be omitted.  When using TCP connection, the PATH part gives the remote host name or IP address and the optional PORT specifies the port number or service name to use.  For example:

     # connect to remote.filter.net on port 3314:
     tcp://remote.filter.net:3314

     # the same, using symbolic service name (must be defined in
     # /etc/services):
     tcp://remote.filter.net:spamd

     # Connect via local UNIX socket (equivalent forms):
     /var/run/filter.sock
     file:///var/run/filter.sock
     socket:///var/run/filter.sock

The description of the interface functions follows.

 -- Built-in Function: boolean sa (string URL, number PREC)
     Pass the message to the SpamAssassin daemon (spamd) at URL.  Return true if SpamAssassin considers it a spam, false othervise.  The second arguments, PREC, gives the precision, in decimal digits, to be used when converting SpamAssassin diagnostic data and storing them into mailfromd variables. The floating point SpamAssassin data are converted to the integer mailfromd variables using the following relation:

          VAR = int(SA-VAR * PREC)

     where SA-VAR stands for the SpamAssassin value and VAR stands for the corresponding mailfromd one.  int() means taking the integer part.

     The function returns additional information via the following variables:

     sa_score
          The spam score, converted to integer as described above.

     sa_threshold
          The threshold, converted to integer.

     sa_keywords
          A string of comma-separated SpamAssassin keywords identifying this message.

     The sa function can signal the following exceptions: failure if the connection fails, url if the supplied URL is invalid and range if the supplied port number is out of the range 1--65535.

     The simplest way to use the function is:

          prog eom
          do
            if sa("tcp://192.168.10.1:3333", 3)
               reject 550 5.7.0
                   "Spam detected, score %sa_score with threshold %sa_threshold"
            fi
          done

     Here is the more advanced example:

          /* Convert a SpamAssassin code $1 back to fixed point form
             using the given number of digits ($2) */
          func to_double_str(s,n) returns s
          do
            if length($1) > $2
              return substring($1, 0, length($1)-$2-1) '.'
                     substring($1, length($1)-$2, -1)
            else
              return $1
            fi
          done

          prog eom
          do
            set prec 3
            if sa("tcp://192.168.10.1:3333", %prec)
              add "X-Spamd-Status" "SPAM"
            else
              add "X-Spamd-Status" "OK"
            fi
            add "X-Spamd-Score" to_double_str(%sa_score, %prec)
            add "X-Spamd-Threshold" to_double_str(%sa_threshold, %prec)
            add "X-Spamd-Keywords" %sa_keywords
          done

 -- Built-in Function: boolean clamav (string URL)
     Pass the message to the ClamAV daemon at URL.  Return true if it detects a virus in it.  Return virus name in clamav_virus_name global variable.

     The clamav function can signal the following exceptions: failure if connection failed, url if the supplied URL is invalid and range if the supplied port number is out of the range 1--65535.

     An example usage:

          prog eom
          do
            if clamav("tcp://192.168.10.1:6300")
               reject 550 5.7.0 "Infected with %clamav_virus_name"
            fi
          done

Special Test Functions
......................

 -- Built-in Function: number greylist
     (string KEY, number INTERVAL)

     Returns true if the KEY is found in the greylist database (controlled by #pragma database greylist pragma, see database).  The argument INTERVAL gives the greylisting interval in seconds.  The function sets internal variable greylist_seconds_left to the number of seconds left to the end of greylisting period.  See Greylisting, for the detailed explanation.

     greylist function can signal dbfailure exception.

 -- Built-in Function: boolean listens (string HOST)
     Returns true if the IP address or host name given by HOST argument listens on port 25.

 -- Built-in Function: number rate (string KEY,
     number SAMPLE-INTERVAL)

     Returns the mail sending rate for KEY per SAMPLE-INTERVAL.  See Sending Rate, for the detailed discussion.

 -- Built-in Function: boolean validuser (string NAME)
     Returns true if the authenticity of user NAME is confirmed using mailutils authentication system.  See Local Account Verification, for more details.

User-Defined Functions
----------------------

Complicated filter scripts can often be simplified and made more manageable by defining new functions.

Definitions of functions can appear anywhere between the handler declarations in a filter program.  The only requirement is that the function definition occurs before the place where the function is invoked.

The definition of a function named NAME looks like this:

     func NAME (PARAM-TYPES) returns DATA-TYPE
     do
       FUNCTION-BODY
     done

NAME is the name of the function to define, PARAM-TYPES is a comma-separated list of PARAMETER TYPES and DATA-TYPE specifies the type of the value the function returns.  Data types are represented by single letters: s meaning string and n meaning numeric.  For example, the following code defines the function FOO that takes two string parameters and returns a numeric value:

     func samedomain(s,s) returns n

The FUNCTION-BODY is any list of valid mailfromd statements.  In addition to the statements discussed below (see Statements) it can also contain the return statement, which is used to return a value from the function.  The syntax of the return statement is

       return VALUE

The function can access its actual arguments using the notation $N, where N is the ordinal number of the argument. Arguments are counted from left to right.  The first argument is $1.

Some Useful Functions
.....................

This subsection introduces several functions that can be useful in your scripts.  It can serve both as an example of writing user-defined functions and as a repository of such functions (until mailfromd has a library facility, which is planned for one of the next releases).

To extract any of these functions into files suitable for inclusion in you mailfromd configuration, change to the directory /doc of the distribution and run make extract.  By default, it will extract all functions into separate files.  The names of the created files will be printed on the standard output:

     $ make extract
     Creating file `revip.mf'
     Creating file `strip_domain_part.mf'
     Creating file `match_dnsbl.mf'
     Creating file `match_rhsbl.mf'

If you wish to extract only selected functions, type the comma-separated list of their names in the variable SELECT, for example:

     $ make extract SELECT=revip,match_dnsbl
     Creating file `revip.mf'
     Creating file `match_dnsbl.mf'

Use the #include statement to include the extracted files in your mailfromd configuration (see include).

 -- Sample Function: string revip (string IP)
     Reverses octets in IP.

     Example:

     revip("127.0.0.1") => "1.0.0.127"

          #pragma regex +extended
          func revip(s) returns s
          do
              if $1 matches '([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)'
                  return "\4.\3.\2.\1"
              fi
              return $1
          done

 -- Sample Function: string strip_domain_part (string DOMAIN,
     number N)

     Returns at most N last components of the domain name DOMAIN. If N is 0 the function is equivalent to domainpart.

     Examples:

     -output strip_domain_part("puszcza.gnu.org.ua", 2) => "org.ua" strip_domain_part("puszcza.gnu.org.ua", 0) => "gnu.org.ua"

          #pragma regex +extended

          func strip_domain_part(s,n) returns s
          do
          	if $2 = 0
          		return domainpart $1
          	elif domainpart($1) matches '.*((\.[^.]+){' $2 '})'
          		return substring(\1, 1, -1)
          	else
          		return $1
          	fi
          done

The following four functions are contributed by Jan Rafaj:

 -- Sample Function: string ip_plusminus (string IP,
     number N, number DELTA)

     Add DELTA to the Nth octet of IP address IP:

     -output ip_plusminus("1.2.3.4", 3, 7) => "1.2.10.4" ip_plusminus("1.2.3.4", 4, -2) => "1.2.3.2" ip_plusminus("1.2.3.4", 4, -6) => "1.2.3.0" ip_plusminus("1.2.3.4", 1, 270) => "255.2.3.4"

          func ip_plusminus(s,n,n) returns s
          do
              if $2 < 1 or $2 > 4
                  return $1
              fi

              if $1 matches '^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$'
                  set ip1 "\1"
          	set ip2 "\2"
          	set ip3 "\3"
          	set ip4 "\4"
              else
          	return $1
              fi

              switch $2
              do
              case 1:
          	set x %ip1 + $3
          	if %x > 255
          	   set x 255
          	elif %x < 0
          	   set x 0
          	fi
                  return %x "." %ip2 "." %ip3 "." %ip4
              case 2:
          	set x %ip2 + $3
          	if %x > 255
          	   set x 255
          	elif %x < 0
          	   set x 0
          	fi
          	return %ip1 "." %x "." %ip3 "." %ip4
              case 3:
          	set x %ip3 + $3
          	if %x > 255
          	   set x 255
          	elif %x < 0
          	   set x 0
          	fi
          	return %ip1 "." %ip2 "." %x "." %ip4
              case 4:
          	set x %ip4 + $3
          	if %x > 255
          	   set x 255
          	elif %x < 0
          	   set x 0
          	fi
          	return %ip1 "." %ip2 "." %ip3 "." %x
              done
          done

 -- Sample Function: boolean valid_domain (string DOMAIN)

     Returns true if the domain name DOMAIN has a corresponding A record or if it has any MX records, that is if it can be possible to send mail to it.

          func valid_domain(s) returns n
          do
              if (resolve($1) = "0" and not hasmx($1))
                  return 0
              fi
              return 1
          done

 -- Sample Function: boolean match_dnsbl (string ADDRESS,
     string ZONE, string RANGE)

     This function looks up the ADDRESS in the DNS blacklist zone ZONE and checks if the return matches given RANGE of IP addresses.

     It is intended as a replacement for the Sendmail macros dnsbl and enhdnsbl.

     Arguments:

     address
          IP address of the SMTP server to be tested.

     zone
          FQDN of the DNSbl zone to test against.

     range
          The range of IP addresses in CIDR notation or the word ANY, which stands for 127.0.0.0/8.

     The function returns true if dns lookup for ADDRESS in the zone DNSBL yields an IP that falls within the range, specified by CIDR.  Otherwise, it returns false.  If any of ADDRESS or CIDR is invalid, match_dnsbl returns false.

          func match_dnsbl(s,s,s) returns n
          do
              if $3 = 'ANY'
                  set rbl_ip '127.0.0.0/8'
              else
                  set rbl_ip $3
                  if not $3 matches '^([0-9]{1,3}\.){3}[0-9]{1,3}$'
                      return 0
                  fi
              fi

              if not (     $1 matches '^([0-9]{1,3}\.){3}[0-9]{1,3}$'
                       and $1 != $3 )
                  return 0
              fi

              if $1 matches '^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$'
                  set revip "\4.\3.\2.\1." $2 "."
                  if match_cidr (resolve %revip, %rbl_ip)
                      return 1
                  else
                      return 0
                  fi
              fi
              # never reached
          done

 -- Sample Function: boolean match_rhsbl (string EMAIL,
     string ZONE, string RANGE)

     This function checks if the IP address, corresponding to the domain part of EMAIL is listed in the RHS DNS blacklist zone ZONE, and if so, whether its record matches the given range of IP addresses RANGE.

     It is intended as a replacement for the Sendmail macro rhsbl by Derek J. Balling.

     Arguments:

     email
          E-mail address, whose domain name should be tested (usually, it is $f)

     zone
          Domain name of the RHS DNS blacklist zone.

     range
          The range of IP addresses in CIDR notation.

          func match_rhsbl(s,s,s) returns n
          do
              if not (     $1 matches '.+$'
                       and $3 matches '^([0-9]{1,3}\.){3}[0-9]{1,3}$' )
                  return 0
              fi

              if match_cidr (resolve (domainpart $1) "." $2, $3)
                  return 1
              fi
              return 0
          done

Expressions
===========

Expressions are language constructs, that evaluate to a value, that can subsequently be echoed, tested in a conditional statement, assigned to a variable or passed to a function.

Constant Expressions
--------------------

Literals and numbers are constant expressions.  They evaluate to string and numeric types.

Function Calls
--------------

A function call is an expression.  Its type is the return type of the function.

Concatenation
-------------

Any two adjacent expressions are converted to string type and concatenated, producing a new string.  Thus, if $f is smith, and $client_addr is 10.10.1.1, then:

     $f "-" $client_addr => "smith-10.10.1.1"

This feature is often used in filter scripts to generate key values for database lookup functions.

Arithmetic Operations
---------------------

The filter script language offers the common arithmetic operators: +, -, * and /.  All of them follow usual precedence rules and work as you would expect them to.

Relational Expressions
----------------------

Relational expressions are:

Table: Relational Expressions

Expression   Result
X < Y   True if X is less than Y.
X <= Y   True if X is less than or equal to Y.
X > Y   True if X is greater than Y.
X >= Y   True if X is greater than or equal to Y.
X = Y   True if X is equal to Y.
X != Y   True if X is not equal to Y.

The relational expressions apply to string as well as to numbers. When a relational operation applies to strings, case-sensitive comparison is used, e.g.:

     "String" = "string" => False
     "String" < "string" => True

Special Comparisons
-------------------

In addition to the traditional relational operators, described above, mailfromd provides two operators for regular expression matching:

Table: Regular Expression Matching

Expression   Result
X matches Y   True if the string X matches the
regexp denoted by Y.
X fnmatches Y   True if the string X matches the
globbing pattern denoted by Y.

The type of the regular expression used by matches operator is controlled by #pragma regex (see pragma regex).  For example:

     $f => "gray@gnu.org.ua"
     $f matches '.*@gnu\.org\.ua' => true
     $f matches '.*@GNU\.ORG\.UA' => false
     #pragma regex +icase
     $f matches '.*@GNU\.ORG\.UA' => true

The fnmatches operator compares its left-hand operand with a globbing pattern (glob(7)) given as its right-hand side operand.  For example:

     $f => "gray@gnu.org.ua"
     $f fnmatches "*ua" => true
     $f fnmatches "*org" => false
     $f fnmatches "*org*" => true

Both operators have a special form, for MX pattern matching. The expression:

       X mx matches Y

is evaluated as follows: first, the expression X is analyzed, if it is an email address, its domain part is selected.  If it is not, the value is used as is.  Then the list of MXs for this domain is looked up.  Each of MX names is then compared with the regular expression Y.  If any of the names matches, the expression returns true.  Otherwise, its result is false.

Similarly, the expression:

       X mx fnmatches Y

returns true only if any of the MXs for (domain or email) X match the globbing pattern Y.

Both mx matches and mx fnmatches can signal the following exceptions: temp_failure, mf_not_found, failure.

Probably not_found should not be signaled?

The value of any parenthesized subexpression ocurring within the right-hand side argument to matches or mx matches can be referenced using the notation \D, where D is the ordinal number of the subexpression (subexpressions are numbered from left to right, starting at 1).  This notation is allowed in the program text as well as within double-quoted strings and here-documents, for example:

     if $f matches '.*@\(.*\)\.gnu\.org\.ua'
       set message "Your host name is \1;"
     fi

Remember that the grouping symbols are \( and \) for basic regular expressions, and ( and ) for extended regular expressions.  Also make sure you properly escape all special characters (backslashes in particular) in double-quoted strings, or use single-quoted strings to avoid having to do so (see singe-vs-double, for the comparison of the two forms).

Boolean Expressions
-------------------

A boolean expression is a combination of relational or matching expressions using the boolean operators and, or and not, and, eventually, parentheses to control nesting:

table: Boolean Operators

Expression   Result
X and Y   True only if both X and
Y are true.
X or Y   True if any of X or Y
is true.
not X   True if X is false.

Binary boolean expressions are computed using shortcut evaluation:

X and Y
     If X => false, the result is false and Y is not evaluated.

X or Y
     If X => true, the result is true and Y is not evaluated.

Operator Precedence
-------------------

Operator precedence is an abstract value associated with each language operator, that determines the order in which operators are executed when they appear together within a single expression. Operators with higher precedence are executed first.  For example, * has a higher precedence than +, therefore the expression a + b * c is evaluated in the following order: first b is multiplied by c, then a is added to the product.

When operators of equal precedence are used together they are evaluated from left to right.  In other words, all mailfromd operators are left-associative.

The precedences of the mailfromd operators where selected so as to match that used in most programming languages.  The following table lists all operators in order of decreasing precedence:

(...)
     Grouping

$ %
     Sendmail macros and mailfromd variables

String Concatenation?
     There is no special operator, the operands are simply written side by side (*note Concatenation::).

+ - not
     Unary plus, minus, boolean not.

* /
     Multiplication, division

+ -
     Addition, subtraction

< <= >= >
     Relational operators

= != matches fnmatches
     Equality and special comparison

and
     Logical and.

or
     Logical or

Type Casting
------------

When two operands on each side of a binary expression have different type, mailfromd evaluator has to coerce them to a common type.  This is known as implicit type casting.  The rules for implicit type casting are:

 1. Both arguments to an arithmetical operation are cast to numeric type.

 2. Both arguments to the concatenation operation are cast to string.

 3. Both arguments to `match' or `fnmatch' function are cast to string.

 4. The argument of the unary negation (arithmetical or boolean) is cast to numeric.

 5. Otherwise the right-hand side argument is cast to the type of the left-hand side argument.

There is no special syntactic sugar for explicit type casting. However you can use the latter rule to make sure an expression is of desired type: to cast an expression to string, concatenate an empty string to it, e.g.:

       %var ""

Similarly, to cast an expression to numeric, add zero to it:

       %var + 0

Statements
==========

Statements are language constructs, that, unlike expressions, do not return any value.  Statements execute some actions, such as assigning a value to a variable, or serve to control the execution flow in the program.

Action Statements
-----------------

An action statement instructs mailfromd to perform a certain action over the message being processed.  There are two kinds of actions: return actions and header manipulation actions.

Return actions tell Sendmail to return given response code to the remote party.  There are five such actions:

accept
     Return an accept reply.  The remote party will continue transmitting its message.

reject [CODE] [EXCODE] [MESSAGE]
     Return a reject reply.  The remote party will have to cancel transmitting its message.  The three arguments are optional, their usage is described below.

tempfail [CODE] [EXCODE] [MESSAGE]
     Return a temporary failure reply. The remote party can retry to send its message later.  The three arguments are optional, their usage is described below.

discard
     Instructs Sendmail to accept the message and silently discard it without delivering it to any recipient.

continue
     Stops the current handler and instructs Sendmail to continue processing of the message.

Two actions, reject and tempfail can take up to three optional parameters.  The first argument is a three-digit RFC-2821 reply code.  It must begin with 5 for reject and with 4 for tempfail.  If two arguments are supplied, the second argument must be either an extended reply code (RFC 1893/2034) or a textual string to be returned along with the SMTP reply.  Finally, if all three arguments are supplied, then the second one must be an extended reply code and the third one must give the textual string.  The following examples illustrate the possible ways of using the reject statement:

     reject
     reject 503
     reject 503 5.0.0
     reject 503 "Need HELO command"
     reject 503 5.0.0 "Need HELO command"

Header manipulation actions allow you to add, delete or modify message RFC-2822 headers.

add NAME STRING
     Add the header NAME with the value STRING. E.g.:

          add "X-Seen-By" "Mailfromd VERSION"

     (notice argument quoting)

replace NAME STRING
     The same as add, but if the header NAME already exists, it will be removed first, for example:

          replace "X-Last-Processor" "Mailfromd VERSION"

delete NAME
     Delete the header named NAME:

          delete "X-Envelope-Date"

Variable Assignments
--------------------

An assignment is a special statement that assigns a value to the variable.  It has the following syntax:

     set NAME VALUE

where NAME is the variable name and VALUE is the value to be assigned to it.

Assignment statements can appear in any part of a filter program. If an assignment occurs outside of function or handler definition, the VALUE must be a literal value (see Literals).  If it occurs within a function or handler definition, VALUE can be any valid mailfromd expression (see Expressions).  In this case, the expression will be evaluated and its value will be assigned to the variable.  For example:

     set delay 150

     prog envfrom
     do
       set delay %delay * 2
       ...
     done

The next statement
------------------

The next statement has no effect.  It should be used when no statement is needed but the language syntax requires one:

     on poll $f do
     when success:
         next
     when not_found or failure:
         reject 550
     done

Conditional Statements
======================

Conditional expressions, or conditionals for short, test some conditions and alter the control flow depending on the result.  There are two kinds of conditional statements: if-else branches and switch statements.

The syntax of an if-else branching construct is:

       if CONDITION THEN-BODY [else ELSE-BODY] fi

The CONDITION is an expression that governs the control flow within the statement.  Both THEN-BODY and ELSE-BODY are lists of mailfromd statements.  If the CONDITION is true, THEN-BODY is executed, if it is false, ELSE-BODY is executed.  The else part of the statement is optional.  The condition is considered false if it evaluates to zero, otherwise it is considered true.  For example:

     if $f = ""
       accept
     else
       reject
     fi

This will accept the message if the value of the Sendmail macro $f is an empty string, and reject it otherwise.  Both THEN-BODY and ELSE-BODY can be compound statements including other if statements.  The nesting level of conditional statements is not limited.

To facilitate writing complex conditional statements, the elif keyword can be used to introduce alternative conditions, for example:

     if $f = ""
       accept
     elif $f = "root"
       echo "Mail from root!"
     else
       reject
     fi

Another type of branching instruction is switch statement:

     switch CONDITION
     do
       case X1 [or X2 ...]:
         STMT1
       case Y1 [or Y2 ...]:
         STMT2
         .
         .
         .
       [default:
         STMT]
     done

Here, X1, X2, Y1, Y2 are literal expressions; STMT1, STMT2 and STMT are arbitrary mailfromd statements (possibly compound); CONDITION is the controlling expression. The vertical dotted row represent another eventual case branches.

This statement is executed as follows: the CONDITION expression is evaluated and if its value equals X1 or X2 (or any other X from the first case), then the STMT1 is executed.  Otherwise, if the CONDITION evaluates to Y1 or Y2 (or any other Y from the second case), then the STMT2 is executed.  Other case branches are tried in turn, if none of them matches, the STMT is executed.

There can be as many case branches as you wish.  The default branch is optional.  There can be at most one default branch.  Consider the following example:

     switch %x
     do
      case 1 or 3:
        add "X-Branch" "1"
        accept
      case 2 or 4 or 6:
        add "X-Branch" "2"
      default:
        reject
     done

If the value of mailfromd variable x is 2 or 3, it will accept the message immediately, and add a X-Branch: 1 header to it.  If x equals 2 or 4 or 6, this code will add X-Branch: 2 header to the message and will continue processing it.  Otherwise, it will reject the message.

The CONDITION in the switch statement can be both numeric and string expression.  The type of the condition governs the type of comparisons used in case branches: for numeric types, numeric equality will be used, whereas for string types, string equality is used.

Exceptional Conditions
======================

When the running program encounters a condition it is not able to handle, it signals an exception.  To illustrate the concept, let's consider the execution of the following code fragment:

       if hasmx domainpart $f
         accept
       fi

The function hasmx (see builtin hasmx) tests whether the domain name given as its argument has any MX records.  It should return a boolean value.  However, when querying the Domain Name System, it may fail to get a definite result.  For example, the DNS server can be down or temporary unavailable.  In other words, hasmx can be in a situation when, instead of returning yes or no, it has to return don't know.  That is exactly when it will signal an exception.

Exceptions are identified by exception types, a special symbols associated with them.  In when used in mailfromd program exception types must be prefixed with an ampersand, e.g. &temp_failure.  The following table summarizes all the exception types implemented by mailfromd version VERSION:

Table: Exception Types

failure
     A general failure has occurred.  In particular, this exception is signaled by DNS lookup functions when any permanent failure occurs. This exception can be signaled by any DNS-related function (hasmx, poll, etc.) or operation (mx matches).

temp_failure
     A temporary failure has occurred.  This can be signaled by DNS-related functions or operations.

ston_conv
     String-to-number conversion failed.  This can be signaled when a string is used in numeric context which cannot be converted to the numeric data type.  For example:

           set x "10a"
           if %x / 2
             ...

     The if condition will signal ston_conv, since 10a cannot be converted to a number.

divzero
     Division by zero.

regcomp
     Regular expression cannot be compiled.  This can happen when a regular expression (a right-hand argument of a matches operator) is built at the runtime and the produced string is an invalid regex.

invip
     Invalid IP address.  This is signaled by match_cidr function when its first argument is not a valid IP address.

invcidr
     Invalid CIDR notation.  This is signaled by match_cidr function when its second argument is not a valid CIDR.

invtime
     Invalid time interval specification.  It is signaled by interval function if its argument is not a valid time interval (see time interval specification).

dbfailure
     General database failure.  For example, the database cannot be opened.  This exception can be signaled by any function that queries any DBM database.

url
     The supplied URL is invalid. See Interfaces to Third-Party Programs.

range
     The supplied argument is outside the allowed range.  This is signalled, for example, by substring function (see substring).

In addition to these, two symbols are defined that are not exception types in the strict sense of the world, but are provided to make writing filter scripts more convenient.  These are success, meaning successful return from a function, and not_found, meaning that the required entity (e.g. domain name or email address) was not found.  See figure-poll-wrapper, for an illustration on how these can be used.

Normally when an exception is signalled, the program execution is terminated and the MTA is returned a tempfail status.  Additional information regarding the exception is then output to the logging channel (see Logging and Debugging).  However, user can intercept any exception by installing his own exception-handling routines.

An exception-handling routine is introduced by catch statement, which has the following syntax:

     catch EXCEPTION-LIST
     do
        HANDLER-BODY
     done

where EXCEPTION-LIST is the list of exception types, separated by the word or.  A special form * is allowed as well. It means catch all exceptions.  The HANDLER-BODY is the list of statements comprising the handler body.  For example, the code below installs a handler for exceptions failure and temp_failure:

     catch &failure or &temp_failure
     do
       ...
     done

The catch statement can appear anywhere inside a function or a handler, but it cannot appear outside of them.  Each exception redefined by a catch remains in force until either another catch appears further in the same function or handler that redefines it or an end of the containing block is encountered, whichever occurs first.  Upon exit from a function or milter handler, all exceptions are restored to the state they had when it has been entered.

Upon entry to a catch code, it receives a single argument specifying the exception that triggered its execution.  The argument can be referenced in HANDLER-BODY using the usual $1 notation.  A catch defined within a function must return from it by executing return statement.  If it does not do that explicitly, it will return 1.  A catch defined within a milter handler must end execution with any of the following actions: accept, continue, discard, reject, tempfail.  By default, it ends with a continue.

The following example shows an envfrom handler that catches eventual exceptions, signaled by hasmx.  The handler accepts the message if the sender domain has any MX records, rejects them if it does not, and continues execution if an exception occurs.  In the latter case, a log message is also issued:

     prog envfrom
     do
       catch &fail or &temp_failure
       do
         echo "Caught exception " $1
         continue
       done

       if hasmx domainpart $f
         accept
       else
         reject
       fi
     done

The following example defines the function safe_hasmx that returns true if the domain part of its argument has any MX records, and false if it does not or an exception occurs:

     func safe_hasmx (s) returns n
     do
       catch *
       do
         return 0
       done
       return hasmx domainpart $1
     done

You will find more examples of exception handlers in the following section.

Sender Verification Tests
=========================

The filter script language provides a wide variety of functions for sender address verification or polling, for short.  These functions, which were described in Polling functions, can be used to implement any sender verification method.  The additional data that can be needed is normally supplied by two global variables: ehlo_domain, keeping the default domain for the EHLO command, and mailfrom_address, which stores the sender address for probe messages (see predefined variables).  Both variables can either be set directly, using set statement, or via pragmatic option directives #pragma option ehlo and #pragma option mailfrom (see option).

For example, a simplest way to implement standard polling would be:

     prog envfrom
     do
       if stdpoll($1, %ehlo_domain, %mailfrom_address) == 0
         accept
       else
         reject 550 5.1.0 "Sender validity not confirmed"
       fi
     done

However, this does not take into account exceptions that stdpoll can signal.  To handle them, one will have to use catch, for example thus:

     prog envfrom
     do
       catch &failure or &temp_failure
       do
         switch $1
         do
           case &failure:
             reject 550 5.1.0 "Sender validity not confirmed"
           case &temp_failure:
             tempfail 450 4.1.0 "Try again later"
         done
       done

       if stdpoll($1, %ehlo_domain, %mailfrom_address) == 0
         accept
       else
         reject 550 5.1.0 "Sender validity not confirmed"
       fi
     done

If polls are used often, one can define a wrapper function, and use it instead.  The following example illustrates this approach:

Figure: Building Poll Wrappers

     function poll_wrapper(s) returns n
     do
       catch &failure or &temp_failure
       do
         return $1
       done
       return stdpoll($1, %ehlo_domain, %mailfrom_address)
     done

     prog envfrom
     do
       switch poll_wrapper($f)
       do
       case &success:
         accept
       case &not_found or &failure:
         reject 550 5.1.0 "Sender validity not confirmed"
       case &temp_failure:
         tempfail 450 4.1.0 "Try again later"
       done
     done

Notice the way envfrom handles success and not_found, which are not exceptions in the strict sense of the word.

The above paradigm is so common that mailfromd provides a special language construct to simplify it: the on statement. Instead of manually writing the wrapper function and using it as a switch condition, you can rewrite the above example as:

Figure: Standard poll example

     prog envfrom
     do
       on stdpoll($1, %ehlo_domain, %mailfrom_address)
       do
         when success:
           accept
         when not_found or failure:
           reject 550 5.1.0 "Sender validity not confirmed"
         when temp_failure:
           tempfail 450 4.1.0 "Try again later"
       done
     done

As you see the statement is pretty similar to switch.  The major syntactic differences are:

 1. Use of the keyword when to introduce conditional branches;

 2. The ampersand before exception names is optional;

General syntax of the on statement is:

     on CONDITION
     do
       when X1 [or X2 ...]:
         STMT1
       when Y1 [or Y2 ...]:
         STMT2
         .
         .
         .
     done

The CONDITION is either a function call or a special poll statement (see below).  The values used in when branches are normally symbolic exception names (with or without & prefix).

When the compiler processes the on statement it does the following:

 1. Builds a unique wrapper function, similar to that described in figure-poll-wrapper;  The name of the function is constructed from the CONDITION function name and an unsigned number, called exception mask, that is unique for each combination of exceptions used in when branches;  To avoid name clashes with the user-defined functions, the wrapper name begins and ends with $ which normally is not allowed in the identifiers;

 2. Translates the on body to the corresponding switch statement;

The special form of the CONDITION is poll keyword, whose syntax is:

     poll [for] EMAIL
          [host HOST]
          [from DOMAIN]
          [as EMAIL]

The order of particular keywords in the poll statement is arbitrary, for example as EMAIL can appear before EMAIL as well as after it.

The simplest form, poll EMAIL, performs the standard sender verification of email address EMAIL.  It is translated to the following function call:

       stdpoll(EMAIL, %ehlo_domain, %mailfrom_address)

The construct poll EMAIL host HOST, runs the strict sender verification of address EMAIL on the given host. It is translated to the following call:

       strictpoll(HOST, EMAIL, %ehlo_domain, %mailfrom_address)

Other keywords of the poll statement modify these two basic forms.  The as keyword introduces the email address to be used in the SMTP MAIL FROM command, instead of %mailfrom_address.  The from keyword sets the domain name to be used in EHLO command.  So, for example the following construct:

       poll EMAIL host HOST from DOMAIN as ADDR

is translated to

       strictpoll(HOST, EMAIL, DOMAIN, ADDR)

To summarize the above, the code described in figure-stdpoll can be written as:

     prog envfrom
     do
       on poll $f do
       when success:
         accept
       when not_found or failure:
         reject 550 5.1.0 "Sender validity not confirmed"
       when temp_failure:
         tempfail 450 4.1.0 "Try again later"
       done
     done

Example of Configuration File
=============================

This section will show a working example of the filter script file. For the ease of illustration, it is divided in several sections. Each section is prefaced with a comment explaining its function.

The first part defines the configuration settings for this host:

     #pragma option relay /etc/mail/sendmail.cw
     #pragma option relay /etc/mail/relay-domains
     #pragma option timeout 33
     #pragma option retry 3
     #pragma option mailfrom ""
     #pragma option ehlo "gnu.org.ua"
     #pragma regex +extended +icase

     #pragma database cache negative-expire-interval 1 day
     #pragma database cache positive-expire-interval 2 weeks

Next we define a envfrom handler.  In the first two rules, it accepts all mails coming from the null address and from the machines in which we relay:

     prog envfrom
     do
        if $f = ""
             accept
        elif relayed hostname $client_addr
             accept
        elif hostname $client_addr = $client_addr
             reject 550 5.7.7 "IP address does not resolve"

Next rule rejects all messages coming from hosts with dynamic IP addresses.  A regular expression used to catch such hosts is not 100% fail-proof, but it tries to cover most existing host naming patterns:

        elif hostname $client_addr matches
              ".*(adsl|sdsl|hdsl|ldsl|xdsl|dialin|dialup|\
     ppp|dhcp|dynamic|[-.]cpe[-.]).*"
             reject 550 5.7.1 "Use your SMTP relay"

Messages coming from the machines whose host name contains something similar to an IP are subject to strict checking:

        elif hostname $client_addr matches
        ".*[0-9]{1,3}[-.][0-9]{1,3}[-.][0-9]{1,3}[-.][0-9]{1,3}.*"
             on poll host $client_addr for $f do
             when success:
                     next
             when not_found or failure:
                     reject 550 5.1.0 "Sender validity not confirmed"
             when temp_failure:
                     tempfail
             done

If the sender domain is relayed by any of the yahoo.com or nameserver.com MXs, no checks are performed.  We will greylist this message in envrcpt handler:

        elif $f mx fnmatches "*.yahoo.com"
             or $f mx fnmatches "*.namaeserver.com"
          next

Finally, if the message does not meet any of the above conditions, it is verified by the standard procedure:

        else
             on poll $f do
             when success:
                     next
             when not_found or failure:
                     reject 550 5.1.0 "Sender validity not confirmed"
             when temp_failure:
                     tempfail
             done
        fi

At the end of the handler we check if the sender-client pair does not exceed allowed mail sending rate:

        if rate($f "-" $client_addr, interval("1 hour 30 minutes")) > 100
             tempfail 450 4.7.0 "Mail sending rate exceeded. Try again later"
        fi
     done

Next part defines the envrcpt handler.  Its primary purpose is to greylist messages from some domains that could not be checked otherwise:

     prog envrcpt
     do
        set gltime 300
        if $f mx fnmatches "*.yahoo.com"
           or $f mx fnmatches "*.namaeserver.com"
           and not dbmap("/var/run/whitelist.db", $client_addr)
           if greylist($client_addr "-" $f "-" $rcpt_addr, %gltime)
              if %greylist_seconds_left = %gltime
                  tempfail 450 4.7.0
                     "You are greylisted for " %gltime " seconds"
              else
                  tempfail 450 4.7.0
                     "Still greylisted for "
                     %greylist_seconds_left " seconds"
              fi
           fi
        fi
     done

Reserved Words
==============

For your reference, here is an alphabetical list of all reserved words:

  * accept

  * add

  * and

  * case

  * catch

  * continue

  * default

  * delete

  * discard

  * do

  * done

  * echo

  * elif

  * else

  * fi

  * fnmatches

  * fnmatches

  * func

  * if

  * matches

  * matches

  * next

  * not

  * on

  * or

  * prog

  * rate

  * reject

  * replace

  * return

  * returns

  * set

  * switch

  * tempfail

  * when

Several keywords are context-dependent: mx is a keyword if it appears before matches or fnmatches.  Following strings are keywords in on context:

  * as

  * for

  * from

  * host

  * poll

Configuring mailfromd
*********************

In the simplest case you will be able to start up mailfromd without any additional command line options: the default values provided when compiling the package and the default configuration file are enough.

If you need to alter any configuration settings, there are three ways of doing so.  First of all, and this method should always be preferred, you can use various #pragma directives in the configuration file.  These were described in Comments.

We advise to use this method because with it all the program settings are kept in a single file, which simplifies the maintenance and ensures they remain valid across program restarts.

If you wish to temporarily override the configuration settings, use the command line.  It is the second configuration method.

Finally, there are several configuration options that are handled by libmailutils itself, and that cannot currently be configured in mailfromd.rc.  We recommend to store these settings in the mailutils system-wide configuration file (The use of mailutils configuration file is covered in see Mailutils Configuration File (GNU Mailutils Manual).). This approach is described in Local Account Verification.

In this chapter we will discuss mailfromd command line options and the ways of starting and shutting down the program.

Command Line Options.
=====================

Operation Modifiers
-------------------

     compact

--compact
     Compact database (see compaction).  By default, cache database is compacted.  To specify another database, use --format option (see --format).  Full database name can be given in the command line, if it differs from the one specified in the configuration file.

     Use with the option --all (see --all) to compact all databases.

     This option implies --stderr (see --stderr).

     daemon

--daemon
     Run in daemon mode (default).

     delete

--delete
     Delete given entries from the database (see deleting from databases).  By default, cache database is assumed.  To specify another database, use --format option (see --format).

     This option implies --stderr (see --stderr).

     expire

--expire
     Delete all expired entries from the database (see Database Maintenance).  By default, cache database is assumed.  To specify another database, use --format option (see --format).  Full database name can be given in the command line, if it differs from the one specified in the configuration file.

     Use with the option --all (see --all) to expire all databases.

     This option implies --stderr (see --stderr).

     file

-f FILENAME
--file=FILENAME
     Set the name of the database to operate upon (for --compact, --delete, --expire, and --list options). Useful if, for some reason, you need to operate on a database whose filename does not match the one mailfromd is configured to use.

     list

--list
     List the database.  By default, cache database is assumed.  To list another database, use --format option (see --format).

     This option implies --stderr (see --stderr).

     See Basic Database Operations.

     show-defaults

--show-defaults
     Show compilation defaults. See Databases.

     test

-t[STATE]
--test[=STATE]
     Run in test mode.  See Testing Configuration.  Default STATE is envfrom.  This option implies --stderr (see --stderr).

General Settings
----------------

     all

--all
     When used with --compact or --expire option, applies the action to all available databases. See compact cronjob.

     config-file

-c FILE
--config-file=FILE
     Read configuration from FILE, instead of the precompiled SYSCONFDIR/mailfromd.rc.

     domain

-D STRING
--domain=STRING
     Set default SMTP domain. Overrides #pragma option ehlo (see pragma ehlo).

     ehlo
--ehlo=STRING
     Same as --domain.

     expire-interval

-e INTERVAL
--expire-interval=INTERVAL
     Set expiration intervals for all databases to the specified interval. See time interval specification, for the description of INTERVAL format.  The option overrides #pragma option expire-interval (see pragma expire-interval), which you are advised to use instead.

     foreground

--foreground
     Stay in foreground.  When given this option, mailfromd will not disconnect itself from the controlling terminal and become daemon.

     format

-f DBFORMAT
--format=DBFORMAT
     Use database of the given format, instead of the default cache.  See Basic Database Operations.

     group

-g NAME
--group=NAME

     Retain the group NAME when switching to user privileges.  See Starting and Stopping.

     This option overrides #pragma option group (see pragma group).

     ignore-failed-reads

--ignore-failed-reads
     Ignore records that cannot be retrieved while compacting the database.  Without this option, mailfromd will abort the compaction if any such error is encountered.

     lock-retry-count

--lock-retry-count=NUMBER
     Set maximum number of attempts to acquire the lock on the database. See the description of #pragma option lock-retry-count, pragma lock-retry-count, for more information about the database locking.

     lock-retry-timeout

--lock-retry-timeout=INTERVAL
     Set the time span between the two locking attempts.  See the description of #pragma option lock-retry-count, pragma lock-retry-count, for more information about the database locking.

     This option overrides the value set by #pragma option lock-retry-timeout (see pragma lock-retry-timeout.

     See time interval specification, for the description of valid INTERVAL formats.

     mailfrom
--mailfrom=EMAIL
     Set postmaster email address.  Overrides #pragma option ehlo, which you are advised to use instead (see pragma ehlo).  The default is null return address ("").

     port

-p STRING
--port=STRING
     Set communication socket.  Overrides #pragma option port, which you are advised to use instead (see pragma port).

     postmaster-email

--postmaster-email=EMAIL
     See --mailfrom.

     pidfile

--pidfile=FILE
     Set pidfile name.  Overrides #pragma option pidfile, which you are advised to use instead (see pragma pidfile).

     predict

--predict=RATE-LIMIT
     Used with --list enables printing of the estimated times of sending along with the rate database dump.  Implies --format=rate.  See estimated time of sending.

     relayed-domain-file

--relayed-domain-file=FILE
     Read relayed domains from FILE.  Overrides #pragma option relay, which you are advised to use instead (see option relay). See Avoiding Verification Loops, and the description of relayed function (see relayed) for more information.

     remove

-r
--remove
     Force removing local socket file, if it already exists.  Unless this option is specified, mailfromd will refuse to start up if the file exists.

     source

-S
--source
     Set source address for TCP connections.  Overrides #pragma option source, which you are advised to use instead (see pragma source).

     time-format

--time-format=FORMAT
     Set format to be used for timestamps in listings, produced by --list.  The FORMAT is any valid strftime format string, see Time and Date Formats for the detailed description. The default FORMAT is %c (see %c time format).  To analyze mailfromd --list output using text tools, such as awk or grep, the following format might be useful: %s (see %s time format).  Another format I find useful is %Y-%m-%d_%H:%M:%S.

     user

-u NAME
--user NAME
     Switch to this user privileges after startup. Overrides #pragma
     option user, which you are advised to use instead (see pragma user).  Default user is DEFAULT-USER.

Timeout Control
---------------

See time interval specification, for the format of INTERVAL value.

     milter-timeout

--milter-timeout=INTERVAL
     Set MTA connection timeout.  Overrides #pragma option milter-timeout, which you are advised to use instead (see pragma milter-timeout.

     timeout

--timeout=NUMBER
     Set I/O operation timeout (seconds).  Overrides #pragma option timeout, which you are advised to use instead (see pragma io-timeout).

Logging and Debugging Options
-----------------------------

     debug

-d LEVEL
--debug=LEVEL
     Set debugging level.  Overrides #pragma option debug.  See Logging and Debugging.

     dump-code

--dump-code
     Parse and compile the configuration file and dump the disassembly listing of the produced code to the terminal.  See Logging and Debugging.

     dump-grammar-trace

--dump-grammar-trace
     Enable debugging the configuration file parser.  While parsing the file, the detailed dump of the parser states and tokens seen will be output.

     dump-lex-trace

--dump-lex-trace
     Enable debugging the lexical analyzer.  While parsing the configuration file, the detailed dump of the lexer states and matched rules will be output.

     dump-macros

--dump-macros
     Show Sendmail macros used in the configuration file.  The macro names are displayed as comma-separated lists, grouped by handler names. See Sendmail Configuration, for the detailed description of this option and its usage.

     dump-tree

--dump-tree
     Parse and compile the configuration file and dump the parse tree in a printable form to the terminal.

     lint

--lint
-l
     Check configuration file syntax and exit.  If the file is OK, return 0 to the shell, otherwise print appropriate messages to stderr and exit with code 78 (configuration error).

     See Testing Configuration.

     log-facility

--log-facility=FACILITY
     Output logs to syslog FACILITY.

     log-tag

--log-tag=STRING
-L STRING
     Tag syslog entries with the given STRING, instead of the program name.

     stderr

-s
--stderr
     Log to stderr (by default logging goes to syslog). See Logging and Debugging.

     This option is turned on implicitly by any of the following options: --compact, --delete, --expire, --test, and --list.

     source-info

--source-info
     Include C source information into the debugging messages.  You would not need this option, unless you are developing and debugging mailfromd.

     syntax-check

--syntax-check
     Synonym for --lint.

     trace

--trace
     Enable action tracing.  With this option mailfromd will log all executed actions.  See Logging and Debugging.

     trace-program

--trace-program
     Enable program instruction tracing.  With this option mailfromd will log execution of every instruction in the compiled filter program.  This option is useful for debugging mailfromd, but is not advised to use otherwise, since it is very time-costly.

     transcript

-X
--transcript
     Enable transcript of the SMTP sessions to the log channel.  See Logging and Debugging.

     syslog

--syslog
     Output logs to syslog.  This is the default, unless any of the following options is used: --compact, --delete, --expire, --test, and --list.

Informational Options
---------------------

-?
--help
     Give a short help summary.

--license
     Print license and exit.

--usage
     Give a short usage message.

-V
--version
     Print program version.

Starting and Stopping
=====================

Right after startup, when mailfromd has done the operations that requires root privileges, it switches to the privileges of the user it is configured to run as (see default user privileges) or the one given in its configuration file (see pragma user).  During this process it will drop all supplementary groups and switch to the principal group of that user.

Such limited privileges of the daemon can cause difficulties if your filter script needs to access some files (e.g. Sendmail databases) that are not accessible to that user and group.  For example, the following fragment using dbmap function:

     if dbmap("/etc/mail/aliases", $f)
       ...
     fi

will normally fail, because /etc/mail/aliases.db is readable only to the root and members of the group smmsp.

In such situations you need to instruct mailfromd to retain the privileges of one or several supplementary groups when switching to the user privileges.  This is done using #pragma option group directive in the configuration file (see pragma group).  In example above, you need to use the following directive:

     #pragma option group smmsp

(The same effect can be achieved with --group command line option: mailfromd --group=smmsp).

To stop a running instance of mailfromd use one of the following signals: SIGHUP, SIGTERM, SIGINT. All three signals have the same effect: the program cancels handling any pending requests, uninitializes the communication socket (if it is a UNIX socket, the program unlinks it) and exits.

The PID of the master instance of mailfromd is kept on the pidfile, which is named mailfromd.pid and is located in the program state directory.  Assuming the default location of the latter, the following command will stop the running instance of the daemon:

     kill -TERM `head -n1 /usr/local/var/mailfromd/mailfromd.pid`

The default pidfile location is shown in the output of mailfromd --show-defaults (see Databases), and can be changed at run time using #pragma option pidfile statement (see pragma pidfile).

To facilitate the use of mailfromd, it is shipped with a shell script that can be used to launch it on system start up and shut it down when the system goes down.  The script, called rc.mailfromd, is located in the directory /etc of the distribution.  It takes a single argument, specifyng the action that should be taken:

start
     Start the program.

stop
     Shut down the program

restart
     Shut down the program and start it again.

status
     Display program status.  It displays the PID of the master process and its command line, for example:

          $ /etc/rc.d/rc.mailfromd status
          mailformd appears to be running at 26030
          26030 /usr/local/sbin/mailfromd --remove --user mail --group smmsp
          --group mail --log-facility local4 --transcript --trace -d1

     If the second line is not displayed, this most probably mean that there is a stale pidfile, i.e. the one left though the program is not running.

     An empty rc.mailfromd status output means that mailfromd is not running.

configtest [FILE]
     Check the configuration file syntax, report any errors found and exit.  If FILE is given it is checked instead of the default one.

macros [-c] [FILE]
     Parse the configuration file (or FILE, if it is given, extract the names of Sendmail macros it uses and generate corresponding export statements usable in the Sendmail configuration file.  By default, mc statements are generated.  If -c (--cf) is given, the statements for sendmail.cf are output.  See the next chapter for the detailed description of this mode.

You can pass any additional arguments to mailfromd by editing ARGS variable near line 22.

The script is not installed by default.  You will have to copy it to the directory where your system start-up scripts reside and ensure it is called during the system start up and shut down.  The exact instructions on how to do so depend on the operating system you use and are beyond the scope of this manual.

Configuring Sendmail to use mailfromd
*************************************

This chapter assumes you are familiar with Sendmail configuration in general and with Milter configuration directives in particular.  It concentrates only on issues, specific for mailfromd.

To prepare Sendmail to communicate with mailfromd you need first to set up the milter port. This is done with INPUT_MAIL_FILTER statement in your Sendmail file.  Use the same value you have used in #pragma option port statement of mailfromd.rc file (see pragma port):

     INPUT_MAIL_FILTER(`mailfrom', `S=unix:/usr/local/var/mailfromd')

If you prefer to fiddle directly with sendmail.cf file, use this statement instead:

     Xmailfrom, S=unix:/usr/local/var/mailfromd

Then, you need to make sure all Sendmail macros used in your mailfromd.rc file are properly exported.  The simplest way to do so is using rc.mailfromd script, introduced in the previous chapter.  Run it with macros command line argument and copy its output to your sendmail.mc configuration file:

     $ rc.mailfromd macros

If you prefer to work with sendmail.cf directly, use -c (--cf) command line option:

     $ rc.mailfromd macros -c

Finally, if you use other mailfromd configuration file than that already installed (for example, you are preparing a new configuration while the old one is still being used in production environment), give its name in the command line:

     $ rc.mailfromd macros newconfig.rc
     # or:
     $ rc.mailfromd macros -c newconfig.rc

If you use this method, you can skip the rest of this chapter. However, if you are a daring sort of person and prefer to do everything manually, follow the instructions below.

First of all you need to build a list of macros used by handlers in your mailfromd.rc file.  You can obtain it running mailfromd --dump-macros.  This will display all macros used in your handlers, grouped by handler name, for example:

     envfrom i, f, {client_addr}
     envrcpt f, {client_addr}, {rcpt_addr}

Now, modify confMILTER_MACROS_HANDLER macros in your mc file.  Here, HANDLER means the uppercase name of the mailfromd handler you want to export macros to, i.e. the first word on each line of the above mailfromd --dump-macros output.  Notice, that in addition to these macros, you should also export the macro i for the very first handler (rc.mailfromd macros takes care of it automatically, but you preferred to do everything yourself...)  It is necessary in order for mailfromd to include Message-ID in its log messages (see Message-ID).

For example, given the above macros listing, which correspods to our sample configuration (see Configuration File Example), the sendmail.mc snippet will contain:

     define(`confMILTER_MACROS_ENVFROM',dnl
     confMILTER_MACROS_ENVFROM `, i, f, {client_addr}')
     define(`confMILTER_MACROS_ENVRCPT',dnl
     confMILTER_MACROS_ENVRCPT `, f, {client_addr}, {rcpt_addr}')

Special attention should be paid to s macro (HELO domain name).  In Sendmail versions up to 8.13.7 (at least) it is available only to helo handler.  If you wish to make it available elsewhere you will need to use the method described in HELO Domain

Now if you are a really daring person and prefer to do everything manually and to hack your sendmail.cf file directly, you certainly don't need any advices.  Nonetheless, here's how the two statements above could look in this case:

     O Milter.macros.envfrom=i, {auth_type}, {auth_authen}, \
        {auth_ssf}, {auth_author}, {mail_mailer}, {mail_host}, \
        {mail_addr} ,{mail_addr}, {client_addr}, f
     O Milter.macros.envrcpt={rcpt_mailer}, {rcpt_host}, {rcpt_addr} ,i,
     f, {client_addr}

How to Report a Bug
*******************

Email your bug reports and suggestions to bug-mailfromd@gnu.org.ua.

As the purpose of bug reporting is to improve software, please be sure to include maximum information when reporting a bug.  The information needed is:

  * Version of the package you are using.

  * Compilation options used when configuring the package.

  * Run-time configuration (mailfromd.rc file and the command line options used).

  * Conditions under which the bug appears.

     [file gacopyz.texi not included]

     [file strftime.texi not included]

     [file fdl.texi not included]

Concept Index
*************

This is a general index of all issues discussed in this manual