Next: Preprocessor, Previous: Modules, Up: MFL [Contents][Index]
Native mailfromd modules described above rely on the functions
provided by the mailfromd
binary. For more sophisticated
tasks you might need to use C functions, either for efficiency reasons
or to make use of some third-party library. This is possible using
special kind of modules called mfmod.
An mfmod consists of two major parts: a dynamically loaded library that provides its main functionality and a small interface mailfromd module. The convention is that for the module x the library is named mfmod_x.so19, and the interface module file is x.mfl.
At the time of this writing, three mfmods exist:
Provides support for Perl-compatible regular expressions. It also contains a special function for scanning an email message for a match to a regular expression. See Mfmod_pcre in Mfmod_pcre.
Functions for searching in LDAP directory. See Mfmod_ldap in Mfmod_ldap.
Openmetrics support for mailfromd
. See mfmod_openmetrics in mfmod_openmetrics reference.
The subsections below describe the internal structure of an mfmod in detail.
• Loadable Library | ||
• Interface Module | ||
• mfmodnew | Creating a Mfmod Structure |
Next: Interface Module, Up: mfmod [Contents][Index]
External functions in the loadable library must be declared as
int funcname(long count, MFMOD_PARAM *param, MFMOD_PARAM *retval);
The MFMOD_PARAM
type is declared in the header file
mailfromd/mfmod.h, which must be included at the start of the
source code.
This type is defined as follows:
typedef struct mfmod_param { mfmod_data_type type; union { char *string; long number; mu_message_t message; }; } MFMOD_PARAM;
The type
fields defines the type of the data represented by the
object. Its possible values are:
String data.
Numeric data.
A mailutils
message object (mu_message_t
).
The actual data are accessed as string
, number
, or
message
, depending on the value of type
.
The first parameter in the external function declaration, count,
is the number of arguments passed to that function. Actual arguments are
passed in the MFMOD_PARAM
array param. The function should
never modify its elements. If the function returns a value to MFL, it
must pass it in the retval parameter. For example, the
following code returns the numeric value ‘1’:
retval->type = mfmod_number; retval->number = 1;
To return a string value, allocate it using malloc
,
calloc
or a similar function, like this:
retval->type = mfmod_string; retval->string = strdup("text");
If a message is returned, it should be created using mailutils message
creation primitives. Mailutils
will call
mu_message_destroy
on it, when it is no longer used.
The return value (in the C sense) of the function is used to determine
whether it succeeded or not. Zero means success. Returning -1 causes
a runtime exception e_failure
with a generic error text
indicating the names of the module and function that caused the
exception. Any other non-zero value is treated as a
mailfromd
exception code (see Exceptions). In this case
an additional textual explanation of the error can be supplied in the
retval
variable, whose type must then be set to mfmod_string
.
This explanation string must be allocated using malloc
.
To facilitate error handling, the following functions are provided (declared in the mailfromd/mfmod.h header file):
Raises exception ecode with the error message formatted from the
variadic arguments using printf
-style format string fmt.
Example use:
if (error_condition) return mfmod_error(retval, "error %s occurred", error_text);
Reports argument type mismatch error (e_inval
with
appropriately formatted error text). Arguments are:
The two arguments passed to the interface function.
0-based index of the erroneous argument in param.
Expected data type of param[n]
.
You will seldom need to use this function directly. Instead, use the
ASSERT_ARGTYPE
macro described below.
Returns the MFL name of the mfmod data type type.
The following convenience macros are provided for checking the number of argument and their types and returning error if necessary:
Assert that the number of arguments (count) equals the expected
number (expcount). If it does not, return the e_inval
exception with a descriptive error text.
retval and count are corresponding arguments from the calling function.
Check if the data type of the nth parameter
(i.e. param[n]
) is exptype and return the
e_inval
exception if it does not.
As an example, suppose you want to write an interface to the system
crypt
function. The loadable library source,
mfmod_crypt.c, will look as follows:
#include <stdlib.h> #include <unistd.h> #include <string.h> #include <mailfromd/mfmod.h> #include <mailfromd/exceptions.h> /* * Arguments: * param[0] - key string to hash. * param[1] - salt value. */ int cryptval(long count, MFMOD_PARAM *param, MFMOD_PARAM *retval) { char *hash; /* Check if input arguments are correct: */ ASSERT_ARGCOUNT(retval, count, 2); ASSERT_ARGTYPE(param, retval, 0, mfmod_string); ASSERT_ARGTYPE(param, retval, 1, mfmod_string); /* Hash the key string. */ hash = crypt(param[0].string, param[1].string); /* Return string to MFL */ retval->type = mfmod_string; retval->string = strdup(hash); /* Throw exception if out of memory */ if (retval->string == NULL) return -1; return 0; }
The exact way of building a loadable library from this source file depends on the operating system. For example, on GNU/Linux you would do:
cc -shared -fPIC -DPIC -omfmod_crypt.so -lcrypt mfmod_crypt.c
The preferred and portable way of doing so is via libtool
(see Shared library support for GNU in Libtool).
Mailfromd
provides a special command mfmodnew
that
creates infrastructure necessary for building loadable modules.
See mfmodnew.
Next: mfmodnew, Previous: Loadable Library, Up: mfmod [Contents][Index]
The interface module is responsible for loading the library, and providing MFL wrappers over external functions defined in it.
For the first task, the dlopen
function is provided. It takes
a single argument, the file name of the library to load. This can
be an absolute pathname, in which case it is used as is, or a relative
file name, which will be searched in the library search path
(see mfmod-path). On success, the function returns the
library handle, which will be used in subsequent calls to
identify that library. On error, a runtime exception is signalled.
It is common to call the dlopen
function in the startup
section of the interface module (see startup/shutdown), so that
the library gets loaded at the program startup. For example:
static number libh prog startup do set libh dlopen("mfmod_crypt.so") done
The function dlcall
is provided to call a function from the
already loaded library. It is a variadic function with three
mandatory parameters:
dlopen
.
The type string argument declares data types of the variable
arguments. It contains a single letter for each additional argument
passed to dlcall
. The valid letters are:
The argument is of string type.
The argument is of numeric type.
The argument is of message type.
For example, the following will call the cryptval
function
defined in the previous section (supposing key
and salt
are two string MFL variables):
set x dlcall(libh, "cryptval", "ss", key, salt)
The last letter in type string can be ‘+’ or ‘*’. Both mean that any number of arguments are allowed (all of the type given by the penultimate type letter). The difference between the two is that ‘+’ allows for one or more arguments, while ‘*’ allows for zero or more arguments. For example, ‘n+’ means one or more numeric arguments, and ‘n*’ means zero or more such arguments. Both are intended to be used in variadic functions, e.g.:
func pringstddev(number ...) returns number do return dlcall(libh, "stddev", "n*", $@) done
The dlcall
function returns the value returned by the
library function it invoked. If the library function returns
no meaningful value, it is recommended to use the void
type cast around the dlcall
invocation (see void type cast). E.g.:
func out(string text) do void(dlcall(libh, "output", "s", text)) done
Without void
type cast, the definition above will produce the
following warning when compiled:
return from dlcall is ignored
Previous: Interface Module, Up: mfmod [Contents][Index]
The mfmodnew
provides a convenient start for writing a new
mfmod. Given a name of the planned module, this command creates
directory mfmod_name and populates it with the files
necessary for building the new module using GNU autotools, as well as
boilerplate files for the loadable library and interface module.
Let’s see how to use it to create the crypt
module outlined in
previous subsections.
First, invoke the command:
$ mfmodnew crypt mfmodnew: setting up new module in mfmod_crypt
Let’s change to the new directory and see the files in it:
$ cd mfmod_crypt $ ls Makefile.am configure.ac crypt.mfl mfmod_crypt.c
Now, open the mfmod_crypt.c file and add to it the definition
of the cryptval
function (see Loadable Library). Then, add
the interface function definition from Interface Module to file
crypt.mfl
.
The last thing to do is to edit configure.ac. The
crypt
function requires the libcrypt library, so the
following line should be added to the ‘Checks for libraries.’
section.
AC_CHECK_LIB([crypt], [crypt])
Now, run autoreconf
, as follows:
$ autoreconf -f -i -s
It will bootstrap the autotools infrastructure, importing additional files as necessary. Once done, you can build the project:
$ ./configure $ make
Notice, that if the autoreconf
stage ends abnormally with a
diagnostics like:
configure.ac:21: error: possibly undefined macro: AC_MFMOD
that means that autoconf
was unable to find the file
mfmod.m4, which provides that macro. That’s because the
directory where this file is installed is not searched by
autoreconf
. To fix this, supply the name of that directory
using the -I option. E.g. assuming mfmod.m4 is
installed in /usr/local/share:
$ autoreconf -fis -I /usr/local/share/aclocal
• mfmodnew invocation |
The mfmodnew
is invoked as:
mfmodnew [options] modname [dir]
where modname is the name of the new module and dir is the directory where to store the module infrastructure files. Normally you would omit dir altogether: in this case the utility will use mfmod_modname as the directory name.
Options are:
Search for template files in dir, instead of the default location.
Supply the author’s email. The email is passed as argument to
the AC_INIT
macro in configure.ac. By default it
is constructed as ‘username@hostname’. If it is
incorrect, you can either edit configure.ac afterwards, or
just supply the correct one using this option.
Suppress informative messages.
Display a short command line usage help.