Plugins hook into the mail system at 5 main points:
At each of these events, mailfront goes through the list of loaded plugins. For each plugin that has a handler for such an event, mailfront calls that handler. If the handler returns no response, control passes to the next handler, otherwise no further handlers are called. If the returned code was an error, it is passed back to the protocol immediately, which will present it to the client. If the sender or recipient handlers of all the plugins return no response, the address is considered rejected, and it is not passed on to the back end. This is done to prevent the default configuration from being an open relay. Plugins may modify the sender or recipient address, as well as the message body.
A template plugin is included as a starting point for developing new plugins.
A mailfront plugin needs to define exactly one public symbol, "plugin". All other public symbols are ignored. That symbol is to be defined as follows:
struct plugin plugin = { .version = PLUGIN_VERSION, .flags = 0, .init = init, .helo = helo, .reset = reset, .sender = sender, .recipient = recipient, .data_start = data_start, .data_block = data_block, .message_end = message_end, };
All items in this structure except for .version may be omitted if they are not needed. The .version field is a constant set to prevent loading of plugins that were built for an incompatible API. The .flags field controls how certain parts of the plugin are called, and may be zero or more flags values (see below) ored together. The remainder of the fields are hook functions which, if present, are called at the appropriate times in the message handling process.
Note that backend modules have identical structure to plugins described here, except that the single required public symbol is named backend instead of plugin. The backend hook functions are also always the last ones called (with the exception of data_start described below). Protocol modules have an entirely different structure.
All hook functions return a response pointer or NULL. This structure consists of two elements: an unsigned SMTP response code number and an ASCII message. If the plugin returns a NULL response, processing continues to the next plugin in the chain (ie pass-through). If the plugin returns a response and either the response number is greater than or equal to 400 (ie an error) or the hook is short-circuiting (as indicated below), then no further hooks in the chain are called. Response numbers less than 400 are treated as acceptance. If the response was an error, the error is passed back through the protocol, otherwise processing continues to the backend. Protocols that do not use the SMTP numbers (such as QMTP) will translate the number into something appropriate. Error numbers between 400 and 499 inclusive are considered "temporary" errors. All others are considered "permanent" failures (ie reject).
All string parameters are passed as type str* and are modifiable. If their value is changed, all subsequent plugins and the backend will see the modified form, as will the protocol module. See the bglibs str documentation module for functions to use in manipulating these objects.
Be aware that the sender and recipient hooks may be called before the message data is handled (as with the SMTP protocol) or after (as with the QMQP and QMTP protocol). In either case, the reset hook will always be called at least once before the message is started, and the message_end hook is called after the message has been completely transmitted.
The session structure contains all the current session data, including pointers to the protocol module, the backend module, environment variables, temporary message file descriptor, and internal named strings and numbers. Plugins may use these internal named data items to store information for internal use or to pass to other plugins with the following functions. Note that the string and number tables are independent and may contain items with the same names without conflicts. The named strings work like environment variables but are not exposed when subprograms are executed. The numbers work similarly, but the data type is unsigned long instead of a string pointer.
Plugins that need to rewrite the message of the body should do so in the message_end hook. Create a new temporary file descriptor with scratchfile() and write the complete new message to it. Then move the new temporary file over to the existing one with the following sequence:
dup2(tmpfd, fd); close(tmpfd);
Be sure to rewind the original file descriptor with lseek(fd,SEEK_SET,0) before using it, since the file position will normally be at the very end of the data.