Mailfromd Manual (split by section):   Section:   Chapter:FastBack: MFL   Up: MFL   FastForward: Library   Contents: Table of ContentsIndex: Concept Index

4.13 Functions

A function is a named mailfromd subroutine, which takes zero or more parameters and optionally returns a certain value. Depending on the return value, functions can be subdivided into string functions and number functions. A function may have mandatory and optional parameters. When invoked, the function must be supplied exactly as many actual arguments as the number of its mandatory parameters.

Functions are invoked using the following syntax:

  name (args)

where name is the function name and args is a comma-separated list of expressions. For example, the following are valid function calls:

  foo(10)
  interval("1 hour")
  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 the corresponding formal parameters.

There are two major groups of functions: built-in functions, that are implemented in the mailfromd binary, and user-defined functions, that are written in MFL. The invocation syntax is the same for both groups.

Mailfromd is shipped with a rich set of library functions. These are described in Library. In addition to these you can define your own functions.

Function definitions can appear anywhere between the handler declarations in a filter program, the only requirement being that the function definition occur before the place where the function is invoked.

The syntax of a function definition is:

[qualifier] func name (param-decl) returns data-type
do
  function-body
done

where name is the name of the function to define, param-decl is a comma-separated list of parameter declarations. The syntax of the latter is the same as that of variable declarations (see Variable declarations), i.e.:

type name

declares the parameter name having the type type. The type is string or number.

Optional qualifier declares the scope of visibility for that function (see scope of visibility). It is similar to that of variables, except that functions cannot be local (i.e. you cannot declare function within another function).

The public qualifier declares a function that may be referred to from any module, whereas the static qualifier declares a function that may be called only from the current module (see Modules). The default scope is ‘public’, unless specified otherwise in the module declaration (see module structure).

For example, the following declares a function ‘sum’, that takes two numeric arguments and returns a numeric value:

func sum(number x, number y) returns number

Similarly, the following is a declaration of a static function:

static func sum(number x, number y) returns number

Parameters are referenced in the function-body by their name, the same way as other variables. Similarly, the value of a parameter can be altered using set statement.

A function can be declared to take a certain number of optional arguments. In a function declaration, optional abstract arguments must be placed after the mandatory ones, and must be separated from them with a semicolon. The following example is a definition of function foo, which takes two mandatory and two optional arguments:

func foo(string msg, string email; number x, string pfx)

Mandatory parameters are: msg and email. Optional parameters are: x and pfx. The actual number of arguments supplied to the function is returned by a special construct $#. In addition, the special construct @arg evaluates to the ordinal number of variable arg in the list of formal parameters (the first argument has number ‘0’). These two constructs can be used to verify whether an argument is supplied to the function.

When an actual argument for parameter n is supplied, the number of actual arguments ($#) is greater than the ordinal number of that parameter in the declaration list (@n). Thus, the following construct can be used to check if an optional argument arg is actually supplied:

func foo(string msg, string email; number x, string arg)
do
  if $# > @arg
    …
  fi

The default mailfromd installation provides a special macro for this purpose: see defined. Using it, the example above could be rewritten as:

func foo(string msg, string email; number x, string arg)
do
  if defined(arg)
    …
  fi

Within a function body, optional arguments are referenced exactly the same way as the mandatory ones. Attempt to dereference an optional argument for which no actual parameter was supplied, results in an undefined value, so be sure to check whether a parameter is passed before dereferencing it.

A function can also take variable number of arguments (such functions are called variadic). This is indicated by the use of ellipsis as the last abstract parameter. The statement below defines a function foo taking one mandatory, one optional and any number of additional arguments:

func foo (string a ; string b, ...)

All actual arguments passed in a list of variable arguments are coerced to string data type. To refer to these arguments in the function body, the following construct is used:

$(expr)

where expr is any valid MFL expression, evaluating to a number n. This construct refers to the value of nth actual parameter from the variable argument list. Parameters are numbered from ‘1’, so the first variable parameter is $(1), and the last one is $($# - Nm - No), where Nm and No are numbers of mandatory and optional parameters to the function.

For example, the function below prints all its arguments:

func pargs (string text, ...)
do
  echo "text=%text"
  loop for number i 1,
       while i <= $# - 1,
       set i i + 1
  do
    echo "arg %i=" . $(i)
  done
done

Note the loop limits. The last variable argument has number $# - 1, because the function takes one mandatory argument.

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

As an example of this, consider the following code snippet that defines the function ‘sum’ to return a sum of its two arguments:

func sum(number x, number y) returns number
do
        return x + y
done

The returns part in the function declaration is optional. A declaration lacking it defines a procedure, or void function, i.e. a function that is not supposed to return any value. Such functions cannot be used in expressions, instead they are used as statements (see Statements). The following example shows a function that emits a customized temporary failure notice:

func stdtf()
do
  tempfail 451 4.3.5 "Try again later"
done

A function may have several names. An alternative name (or alias) can be assigned to a function by using alias keyword, placed after param-decl part, for example:

func foo()
alias bar
returns string
do
  …
done

After this declaration, both foo() and bar() will refer to the same function.

The number of function aliases is unlimited. The following fragment declares a function having three names:

func foo()
alias bar
alias baz
returns string
do
  …
done

Although this feature is rarely needed, there are sometimes cases when it may be necessary.

A variable declared within a function becomes a local variable to this function. Its lexical scope ends with the terminating done statement.

Parameters, local variables and global variables are using separate namespaces, so a parameter name can coincide with the name of a global, in which case a parameter is said to shadow the global. All references to its name will refer to the parameter, until the end of its scope is reached, where the global one becomes visible again. Consider the following example:

number x

func foo(string x)
do
  echo "foo: %x"
done

prog envfrom
do
  set x "Global"      
  foo("Local")
  echo x
done

Running mailfromd --test with this configuration will display:

foo: Local
Global

4.13.1 Some Useful Functions

To illustrate the concept of user-defined functions, this subsection shows the definitions of some of the library functions shipped with mailfromd13. These functions are contained in modules installed along with the mailfromd binary. To use any of them in your code, require the appropriate module as described in import, e.g. to use the revip function, do require 'revip'.

Functions and their definitions:

  1. revip

    The function revip (see revip) is implemented as follows:

    func revip(string ip) returns string
    do
      return inet_ntoa(ntohl(inet_aton(ip)))
    done    
    

    Previously it was implemented using regular expressions. Below we include this variant as well, as an illustration for the use of regular expressions:

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

    This function returns at most n last components of the domain name domain (see strip_domain_part).

    #pragma regex push +extended
    
    func strip_domain_part(string domain, number n) returns string
    do
      if n > 0 and
        domain matches '.*((\.[^.]+){' . $2 . '})'
        return substring(\1, 1, -1)
      else
        return domain
      fi
    done
    #pragma regex pop
    
  3. valid_domain

    See valid_domain, for a description of this function. Its definition follows:

    require dns
    
    func valid_domain(string domain) returns number
    do
      return not (resolve(domain) = "0" and not hasmx(domain))
    done
    
  4. match_dnsbl

    The function match_dnsbl (see match_dnsbl) is defined as follows:

    require dns
    require match_cidr
    #pragma regex push +extended
    
    func match_dnsbl(string address, string zone, string range)
        returns number
    do
      string rbl_ip
      if range = 'ANY'
        set rbl_ip '127.0.0.0/8'
      else
        set rbl_ip range
        if not range matches '^([0-9]{1,3}\.){3}[0-9]{1,3}$'
          return 0
        fi
      fi
    
      if not (address matches '^([0-9]{1,3}\.){3}[0-9]{1,3}$'
              and address != range)
        return 0
      fi
    
      if address matches
            '^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$'
        if match_cidr (resolve ("\4.\3.\2.\1", zone), rbl_ip)
          return 1
        else
          return 0
        fi
      fi
      # never reached
    done
    

Footnotes

(13)

Notice that these are intended for educational purposes and do not necessarily coincide with the actual definitions of these functions in Mailfromd version 8.7.

Mailfromd Manual (split by section):   Section:   Chapter:FastBack: MFL   Up: Functions   FastForward: Library   Contents: Table of ContentsIndex: Concept Index