API documentation¶

If you want to develop modules or plugins for spmfilter, you need to understand how spmfilter is processing an email.
The processing chain is relatively simple, at first the engine is loaded, either smtpd or pipe, afterwards begins the processing of the registered modules.
The return code of the module decides further processing. This makes it possible, to control whether following modules should be started or not. If the return code of all modules is 0, the email will be delivered to the final destination.
Every module can return one of the following codes:
| code | description |
|---|---|
| -1 | Error in processing, spmfilter will send 4xx Error to local MTA |
| 0 | All ok, the next plugin will be started. |
| 1 | Further processing will be stopped, no other plugin will be startet. spmfilter sends a 250 code. E-Mail is not going to be delivered to nexthop! |
| 2 | Further processing will be stopped, no other plugin will be startet. spmfilter sends a 250 code. E-Mail will be delivered to nexthop |
If the return value of the plugin is greater or equal than 400, then the value is used as smtp response code, as defined in spmfilter.conf.
If for example an error occurred within a plugin, then all the previous plugins are not automatically re-processed. Spmfilter uses so-called "state-files" to store the current state of the processing chain. Each plugin, which was successfully executed, will be held here, in case of an error they won't be processed again. Each "state file" has an unique name, which is computed from the message ID. In this way spmfilter is able to check the previous processing state. The "state file" will be removed, if the last plugin was processed successfully.
Since each plugin is a dynamic shared object, it must provide a specific entry point, which is called by spmfilter. The entry point in the plugin is called immediately after spmfilter loads the plugin. This entry point is only called once during the execution. In calling the plugin, the first argument is a SMFSession_T object that is passed to the plugin, which holds all session informations.
The call is:
1 int load(SMFSession_T *session)
Compiling¶
To compile a spmfilter module, you need to tell the compiler where to find the spmfilter header files and libraries. This is done with the pkg-config utility. The following interactive shell session demonstrates how pkg-config is used (the actual output on your system may be different):
$ pkg-config --cflags spmfilter
-I/usr/include/spmfilter
$ pkg-config --libs spmfilter
-L/usr/lib/spmfilter -lsmf
Sessions¶

All session data is stored in a SMFSession_T object by spmfilter, whereas the email content is stored on disk instead, but connection informations and message headers are hold in memory. If you need to modify an email in the current session, you have to use the session functions.
If a header of a session object has been modified, the session will be marked as "dirty" - that means the header will be flushed to disk before the final delivery is initialized, to keep the message in sync with the modified data. In contrast to the session functions, the message functions are used to generate new messages which are hold in memory only.
Please note that not all session variables in each configuration are available. For example, all SMTP related data is not available in the pipe engine.
Generally available data:
- spool file
- message recipients (extracted from message)
- message sender (extracted from message)
- message size
- message headers
Additional with smtp engine available:
- transmitted smtp helo/ehlo name
- transmitted smtp xforward address (only if MTA is configured to do so)
- envelope sender
- envelope recipients
Messages and MIME parts¶
In contrast to the session functions, the message functions can be used for the generation of new messages and MIME-handling. To create
a new message, you need a SMFMessageEnvelope_T object, which holds all envelope informations. The envelope is needed to deliver your message to an external destination.
The creation of a new MIME message is fairly simple:
1 SMFMessageEnvelope_T *envelope;
2 SMFMessage_T *message;
3 SMFMimePart_T *mime_part;
4 SMFDataWrapper_T *wrapper;
5
6 char recipient[] = "recipient@example.com";
7 char sender[] = "me@example.com";
8 char nexthop[] = "127.0.0.1:2525"
9 char content[] = "This is my email content";
10
11 // create a message envelope
12 envelope = smf_message_envelope_new();
13 envelope = smf_message_envelope_add_rcpt(envelope,recipient);
14 envelope->from = sender;
15 envelope->nexthop = nexthop;
16
17 // create the message
18 message = smf_message_new();
19 smf_message_set_sender(message, sender);
20 smf_message_add_recipient(message,SMF_RECIPIENT_TYPE_TO,NULL,recipient);
21 smf_message_set_subject(message,"Message subject");
22
23 // attach a new mime part to message
24 mime_part = smf_mime_part_new(NULL,NULL);
25 smf_mime_part_set_disposition(mime_part,SMF_DISPOSITION_INLINE);
26 smf_mime_part_set_encoding(mime_part, SMF_CONTENT_ENCODING_DEFAULT);
27
28 // add content to mime part
29 wrapper = smf_mime_data_wrapper_new(content,SMF_CONTENT_ENCODING_DEFAULT);
30 smf_mime_set_content(mime_part,wrapper);
31
32 smf_message_set_mime_part(message,mime_part);
33
34 envelope->message = message;
Settings¶
If you need additional configuration for your plugin, then you can use spmfilter.conf. Just add a new group, and you can get your configuration by using the settings functions. The syntax of spmfilter.conf is described in detail in the Desktop Entry Specification , here is a quick summary: the config file consist of groups of key-value pairs, interspersed with comments.
# this is a comment
[myplugin]
Name=Key File Example\tthis value shows\nescaping
Numbers=2;20;-200;0
Booleans=true;false;true;true
Lines beginning with a '#' and blank lines are considered comments. Groups are started by a header line containing the group name enclosed in '[' and ']', and ended implicitly by the start of the next group or the end of the file. Each key-value pair must be contained in a group.
Key-value pairs generally have the form key=value. Space before and after the '=' character are ignored. Newline, tab, carriage return and backslash characters in value are escaped as \n, \t, \r, and \\, respectively.
The config file can store strings, integers, booleans and lists of these. Lists are separated by a separator character, typically ';' or ','. To use the list separator character in a value in a list, it has to be escaped by prefixing it with a backslash.
Note that in contrast to the Desktop Entry Specification, groups may contain the same key multiple times; the last entry wins. The config file may also contain multiple groups with the same name; they are merged together. Another difference is that keys and group names in key files are not restricted to ASCII characters.
Lookups¶

Spmfilter implements a small, fast, and easy to use database API with thread-safe connection pooling. The library can connect transparently to multiple database systems, has zero configuration and connections are specified via a standard URL scheme.
Supported are variety of database systems:- MySQL
- PostgreSQL
- SQLite
- Berkeley DB
- LDAP directories.
Spmfilter cares completely around connection management, load balancing and fallback connections. Failed connections will be also reconnected again automatically.
Whether you are using a SQL database or a LDAP directory, all results are delivered as SMFLookupResult_T objects back. This is a kind of singly linked list for all found SQL rows or LDAP entries. Each element is a SMFLookupElement_T object, which is implemented as a HashMap, in which each key represents a SQL column or a LDAP attribute.
The only exception here is Berkeley DB, as this does not require a query language and is based on key/values.
In order to use database lookups, you have to set a backend in spmfilter.conf, this can be sql, ldap or both (see Installation and configuration). If a valid backend is configured, spmfilter will automatically establish the connection.
An complete overview can be found on the page lookup functions.
The following, fictitious Example shows how to use the spmfilter API to query a LDAP-backend:
1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <spmfilter.h>
4 #define THIS_MODULE "test"
5
6 /* START HERE */
7 int load(SMFSession_T *session) {
8 int i;
9 SMFLookupResult_T *ldapresult = NULL;
10
11 if(session->envelope_from->is_local == 1) {
12
13 /* do the ldap query, we'll check if "allowedtosend" is set for the address in LDAP */
14 ldapresult = smf_lookup_query((&(|(email=%s)(alias=%s)", session->envelope_from->addr);
15
16 /* we found something */
17 if(ldapresult != NULL) {
18 if(ldapresult1->len > 0) {
19 for(i=0; i < ldapresult1->len; i++) {
20
21 SMFLookupElement_T *elem = smf_lookup_result_index(ldapresult,i);
22 SMFLdapValue_T *allowedtosend = smf_lookup_element_get(elem,"allowedtosend");
23
24 if(g_ascii_strcasecmp(allowedtosend->data[0], "TRUE") == 0) {
25
26 /* we just log that this sender is allowed to send */
27 TRACE(TRACE_INFO, "sender %s allowed to send", session->envelope_from->addr);
28 }
29 }
30 }
31
32 /* cleanup */
33 smf_lookup_result_free(ldapresult);
34
35 }
36 return 0;
37 }
Overview¶
- Data types