10.2. Native plugins

There are multiple types of native plugins that can be built working with Halon script (HSL), queue, policies, suspensions and delivery. They should be written as C-compatible libraries (.so). It is possible to build plugins in any language able to provide a C ABI interface. Examples of such languages are C, C++ and the GO language (cgo).

Native plugins are loaded into the Halon MTA from the configuration file’s plugins[] section.

HSL plugins may be developed and tested using the hsh program (see the --plugin option).

All functions, defines and types (opaque pointers) used when building the plugin is defined in the HalonMTA.h header file (installed in /opt/halon/include).

There is a distinction between functions that should be implemented by the library and functions that are provided by the caller (MTA). Our naming convention is that all functions that should be provided by the library starts with Halon_ and all functions that are provided by the MTA HalonMTA_.

10.2.1. Exported by plugin

The following functions may be applicable to all plugins.

int Halon_version()

This is the only required function. It must return the value of HALONMTA_PLUGIN_VERSION from the HalonMTA.h header file.

HALON_EXPORT
int Halon_version()
{
        return HALONMTA_PLUGIN_VERSION;
}
bool Halon_init(HalonInitContext *hic)

This function is called by the MTA upon startup. If this function returns false then the MTA will not start. It should be used for initialization on the plugin. The hic argument holds a reference to the init context (which can be used to access the configuration) of the plugin, and it is only valid for the duration of the Halon_init call. Use the HalonMTA_init_getinfo() functions with the hic argument.

HALON_EXPORT
bool Halon_init(HalonInitContext* hic)
{
        return true;
}
void Halon_config_reload(HalonConfig *hc)

This function is called by the MTA upon configuration reloads (smtpd-app.yaml). It can be used to reload the configuration of the plugin. The hc argument holds a reference to the configuration of the plugin, and it is only valid for the duration of the Halon_config_reload call. Use the HalonMTA_config_object_get() family of functions to work with the configuration.

HALON_EXPORT
void Halon_config_reload(HalonConfig* cfg)
{
}
void Halon_cleanup()

This function is called by the MTA upon shutdown. It should be used for cleaning before unloading the plugin. For example closing connections and joining any threads created by the plugin.

HALON_EXPORT
void Halon_cleanup()
{
}

10.2.1.1. Command plugin

bool Halon_plugin_command(const char *in, size_t inlen, char **out, size_t *outlen)

This function is called by the MTA when a plugin command request (over protobuf) is directed to this plugin (based on its id). If this function returns false, then out is considered an error message. Otherwise it’s arbitrary successful data (that can be encoded in any format). The out data memory will be owned by the MTA.

HALON_EXPORT
bool Halon_plugin_command(const char* in, size_t inlen, char** out, size_t* outlen)
{
        if (strcmp(in, "hello") == 0)
        {
                *out = strdup("world");
                return true;
        }
        return false;
}

10.2.1.2. HSL plugin

The following functions are applicable to HSL plugins only (not delivery plugins). HSL plugins are preferred over FFI when writing a new library, specific to the Halon MTA.

bool Halon_hsl_register(HalonHSLRegisterContext *hhrc)

This method is called by the MTA and tooling (hsh and hsl-lint) to register all the HSL functions provided by the plugin. The hhrc argument should be used when calling the HalonMTA_hsl_register_function() function. On error false is returned.

HALON_EXPORT
bool Halon_hsl_register(HalonHSLRegisterContext* hhrc)
{
        HalonMTA_hsl_register_function(hhrc, "myfunc", myfunc);
        return true;
}
void myfunc(HalonHSLContext *hhc, HalonHSLArguments *args, HalonHSLValue *ret)

This is the signature of the function that will be called from HSL. It takes three arguments:

  • hhc is a pointer to the current execution context. This can be used to control execution of the plugin.

  • args is the list of function arguments when calling the function from HSL.

  • ret a reference to the return value (which can be modified). Its default value is None.

void myfunc(HalonHSLContext* hhc, HalonHSLArguments* args, HalonHSLValue* ret)
{
        double n = 123.456;
        HalonMTA_hsl_value_set(ret, HALONMTA_HSL_TYPE_NUMBER, &n, 0);
}

10.2.1.3. Queue plugin

The queue plugin functions are called when a message is picked up from the active queue in order to be able to acquire or account for delivery slots, and also when a delivery is done and the slots should be released (eg. when a estimated concurrency for a destination should be increased or decreased).

bool Halon_queue_pickup_acquire(HalonQueueMessage *hqm)

This method is called by the MTA when a message is picked up from the active queue for delivery. If you need the message’s pickup parameters, use the HalonMTA_message_getinfo() function. This function must be non-blocking. If this function return false, the message is not to be delivered, instead it will be requeued to run the pre-delivery hooks and the Halon_queue_pickup_release() will not be called.

void Halon_queue_pickup_release(HalonQueueMessage *hqm)

This method is called by the MTA when a message’s delivery attempt is done. If you need the message’s pickup parameters, use the HalonMTA_message_getinfo() function. This function must be non-blocking.

10.2.1.4. Delivery plugin

The deliver plugin system provides an interface for custom, non-blocking, non-SMTP delivery methods. It simply replaces the queue’s regular SMTP/LMTP client with a custom sending routine. In other words, it sits inbetween the active queue pickup and the post-delivery script.

void Halon_deliver(HalonDeliverContext *hdc)

This method is called by the MTA when sending using a plugin for delivery. This function must be non-blocking.

HALON_EXPORT
void Halon_deliver(HalonDeliverContext* hdc)
{
        short int status = 250;
        HalonMTA_deliver_setinfo(hdc, HALONMTA_RESULT_CODE, &status, 0);
        HalonMTA_deliver_setinfo(hdc, HALONMTA_RESULT_REASON, "OK", 0);
        HalonMTA_deliver_done(hdc);
}

10.2.2. Exported by MTA

These are functions provided by the MTA.

bool HalonMTA_init_getinfo(HalonInitContext *hic, int type, const void *inval, size_t inlen, void *outval, size_t *outlen)

Get info about the plugin (from within the Halon_init function). The following types are available. No memory is owned by the caller. For most types inval and inlen should be (NULL and 0). On error false is returned.

Type

inval

inlen

outval

outlen

HALONMTA_INIT_CONFIG

NULL

0

&HalonConfig*

n/a

HALONMTA_INIT_APPCONFIG

NULL

0

&HalonConfig*

n/a

HALON_EXPORT
bool Halon_init(HalonInitContext* hic)
{
        HalonConfig* cfg;
        HalonMTA_init_getinfo(hic, HALONMTA_INIT_APPCONFIG, nullptr, 0, &cfg, nullptr);
}
HalonConfig *HalonMTA_config_array_get(HalonConfig *cfg, size_t index)

Get a pointer reference to the index value of an array. If the cfg argument is not an array or the index is out of bound then NULL is returned. The pointer returned from this function must not be freed.

size_t index = 0;
HalonConfig* val;
while ((val = HalonMTA_config_array_get(cfg, index++)))
        /* work with val */;
HalonConfig *HalonMTA_config_object_get(HalonConfig *cfg, const char *property)

Get a pointer reference to the property value of a map. If the cfg argument is not a map or the property doesn’t exist then NULL is returned. The pointer returned from this function must not be freed.

HalonConfig* value = HalonMTA_config_object_get(cfg, "key"));
const char *HalonMTA_config_string_get(HalonConfig *cfg, size_t *outlen)

Get a pointer reference to the scalar value of cfg. If the cfg argument is not a scalar then NULL is returned. The pointer returned from this function must not be freed.

HalonConfig* value = HalonMTA_config_object_get(cfg, "key"));
if (value)
{
        const char* str = HalonMTA_config_string_get(value, NULL);
        if (str)
                printf("%s\n", str);
}
int HalonMTA_hsl_value_type(const HalonHSLValue *value)

Return the type of the HSL value, the following defines may be returned. For types that cannot be used in HSL plugins HALONMTA_HSL_TYPE_UNSUPPORTED is returned (eg. HSL function or resources).

  • HALONMTA_HSL_TYPE_NONE

  • HALONMTA_HSL_TYPE_BOOLEAN

  • HALONMTA_HSL_TYPE_NUMBER

  • HALONMTA_HSL_TYPE_STRING

  • HALONMTA_HSL_TYPE_ARRAY

  • HALONMTA_HSL_TYPE_FFI_POINTER

  • HALONMTA_HSL_TYPE_UNSUPPORTED

bool HalonMTA_hsl_value_get(const HalonHSLValue *value, int type, void *outval, size_t *outlen)

Return the value of a HSL value. The type argument must match the actual type of value. All memory is owned by the value itself and must not be freed, or used after the plugin execution is done. On error false is returned.

Type

outval

outlen

HALONMTA_HSL_TYPE_BOOLEAN

&bool

n/a

HALONMTA_HSL_TYPE_NUMBER

&double

n/a

HALONMTA_HSL_TYPE_STRING

&char*

&size_t (optional)

HALONMTA_HSL_TYPE_FFI_POINTER

&void*

n/a

char* string;
size_t stringl;
HalonMTA_hsl_value_get(value, HALONMTA_HSL_TYPE_STRING, &string, &stringl);
HalonHSLValue *HalonMTA_hsl_value_array_get(const HalonHSLValue *value, size_t index, HalonHSLValue **key)

Get a reference to the value and key item of an array based on the index (starts at zero). If index doesn’t exist return NULL is returned and the key is not affected. In order to lookup a specific key based on a value they array has to be iterated.

size_t index = 0;
HalonHSLValue *k, *v;
while ((v = HalonMTA_hsl_value_array_get(value, index, &k)))
{
        printf("%zu: type of k %d => v %d\n", index, HalonMTA_hsl_value_type(k), HalonMTA_hsl_value_type(v));
        ++index;
}
HalonHSLValue *HalonMTA_hsl_value_array_find(const HalonHSLValue *value, const char *key)

Get a reference to the value of the key. If key doesn’t exist return NULL is returned.

HalonHSLValue *v = HalonMTA_hsl_value_array_find(value, "mykey");
bool HalonMTA_hsl_value_set(HalonHSLValue *value, int type, const void *inval, size_t inlen)

Set a value of value. For strings the value is copied to HSL. As for the FFI pointer type a new FFIPointer object is created which doesn’t own the memory (if ownership is not changed using the HSL FFI implementation). On error false is returned.

Type

inval

inlen

HALONMTA_HSL_TYPE_NONE

n/a

n/a

HALONMTA_HSL_TYPE_BOOLEAN

&bool

n/a

HALONMTA_HSL_TYPE_NUMBER

&double

n/a

HALONMTA_HSL_TYPE_STRING

char*

size_t (optional)

HALONMTA_HSL_TYPE_ARRAY

n/a

n/a

HALONMTA_HSL_TYPE_FFI_POINTER

void*

n/a

HalonMTA_hsl_value_set(value, HALONMTA_HSL_TYPE_STRING, "Hello World", 0);
bool HalonMTA_hsl_value_array_add(HalonHSLValue *value, HalonHSLValue **key, HalonHSLValue **value)

Add a new key and value to an array. If the value isn’t None or an array, then return false. The values are initialized as None and should be set.

HalonHSLValue *k, *v;
HalonMTA_hsl_value_array_add(ret, &k, &v);
HalonMTA_hsl_value_set(k, HALONMTA_HSL_TYPE_STRING, "key", 0);
HalonMTA_hsl_value_set(v, HALONMTA_HSL_TYPE_STRING, "value", 0);

Note

Consecutive calls to HalonMTA_hsl_value_array_add() will invalidate previous return HalonHSLValue pointers, use HalonMTA_config_array_get() by index to receive them again.

10.2.2.1. HSL plugin

HalonHSLValue *HalonMTA_hsl_argument_get(const HalonHSLArguments *args, size_t index)

Get the function call argument based on index (starting at zero). If the index doesn’t exists then NULL is returned.

size_t index = 0;
HalonHSLValue *a;
while ((a = HalonMTA_hsl_argument_get(value, index)))
{
        printf("%zu: type of a %d\n", index, HalonMTA_hsl_value_type(a));
        ++index;
}
void HalonMTA_hsl_suspend(HalonHSLContext *hhc)

This function suspends the current execution of the script (and it doesn’t return until scheduled), allowing other scripts to be executed on the specific thread in a cooperative fashion (M:N threading).

void example(HalonHSLContext* hhc, HalonHSLArguments* args, HalonHSLValue* ret)
{
        std::thread t([hhc] {
                sleep(5);
                HalonMTA_hsl_schedule(hhc);
        });
        HalonMTA_hsl_suspend(hhc);
        // continue here after 5 seconds
        t.join();
}
void HalonMTA_hsl_suspend_return(HalonHSLContext *hhc)

This function suspends the current execution of the script after the plugin function has returned to the MTA from the plugin (and it doesn’t return until scheduled), allowing other scripts to be executed on the specific thread in a cooperative fashion (M:N threading). This function should be used when not possible to suspend the execution inside the plugin itself. This is the eg. case when building plugins in GO (cgo).

func sleepTask(hhc *C.HalonHSLContext)
{
        time.Sleep(5 * time.Seconds);
        C.HalonMTA_hsl_schedule(hhc);
}

//export sleep
func sleep(hhc *C.HalonHSLContext, args *C.HalonHSLArguments, ret *C.HalonHSLValue)
{
        go sleepTask(hhc);
        C.HalonMTA_hsl_suspend_return(hhc);
}
void HalonMTA_hsl_schedule(HalonHSLContext *hhc)

Reschedule the execution of a suspended execution. The script execution will be requeued onto the assigned threadpool queue. This function is thread-safe and may be called at any time (before or after the HalonMTA_hsl_suspend()).

bool HalonMTA_hsl_register_function(HalonHSLRegisterContext *hhrc, const char *name, HalonHSLFunction *func)

Register a function to HSL. Must only be called in Halon_hsl_register(). If func is null the function is obtained by the MTA using dlsym (this is useful when writing plugins in eg. GO (cgo) which doesn’t easily support C function pointers). On error false is returned.

HALON_EXPORT
bool Halon_hsl_register(HalonHSLRegisterContext* hhrc)
{
        HalonMTA_hsl_register_function(hhrc, "myfunc", myfunc);
        return true;
}
//export Halon_hsl_register
func Halon_hsl_register(hhrc *C.HalonHSLRegisterContext) C.bool {
        C.HalonMTA_hsl_register_function(hhrc, C.CString("myfunc"), nil);
}

10.2.2.2. Queue plugin

bool HalonMTA_message_getinfo(HalonQueueMessage *hqm, int type, const void *inval, size_t inlen, void *outval, size_t *outlen)

Get info about a message. The following types are available. No memory is owned by the caller. For most types inval and inlen should be (NULL and 0), but for tags it’s the number index of the tag and for metadata the string key index. On error false is returned.

Type

inval

inlen

outval

outlen

HALONMTA_MESSAGE_TRANSPORTID

NULL

0

&const char*

&size_t (optional)

HALONMTA_MESSAGE_LOCALIP

NULL

0

&const char*

&size_t (optional)

HALONMTA_MESSAGE_REMOTEIP

NULL

0

&const char*

&size_t (optional)

HALONMTA_MESSAGE_REMOTEMX

NULL

0

&const char*

&size_t (optional)

HALONMTA_MESSAGE_RECIPIENTDOMAIN

NULL

0

&const char*

&size_t (optional)

HALONMTA_MESSAGE_JOBID

NULL

0

&const char*

&size_t (optional)

HALONMTA_MESSAGE_RECIPIENTLOCALPART

NULL

0

&const char*

&size_t (optional)

HALONMTA_MESSAGE_SENDERDOMAIN

NULL

0

&const char*

&size_t (optional)

HALONMTA_MESSAGE_SENDERLOCALPART

NULL

0

&const char*

&size_t (optional)

HALONMTA_MESSAGE_TRANSACTIONID

NULL

0

&const char*

&size_t (optional)

HALONMTA_MESSAGE_QUEUEID

NULL

0

&size_t

n/a

HALONMTA_MESSAGE_TAGS

&size_t

0

&const char*

&size_t (optional)

HALONMTA_MESSAGE_METADATA

char*

size_t (optional)

&const char*

&size_t (optional)

HALONMTA_MESSAGE_RETRYCOUNT

NULL

0

&size_t

n/a

HALONMTA_MESSAGE_CONTEXT

NULL

0

&HalonHSLValue*

n/a

char *HalonMTA_queue_suspend_add(const char *transportid, const char *localip, const char *remoteip, const char *remotemx, const char *recipientdomain, const char *jobid, const char *tag, double ttl)

Add a suspend based on conditions. If any of the conditions (transportid, localip, remoteip, remotemx, recipientdomain or jobid) is NULL it will match any value. The tag and ttl are optional (and may be set to NULL or 0). The ttl is in seconds. The returned id must be freed by the caller. On error NULL is returned.

bool HalonMTA_queue_suspend_update(const char *id, const char *tag, double ttl)

Update a suspensions based the id. The ttl and ttl are optional (and may be set to NULL or 0). The ttl is in seconds. On error false is returned.

bool HalonMTA_queue_suspend_delete(const char *id)

Delete a suspensions based the id. On error false is returned.

char *HalonMTA_queue_policy_add(int fields, const char *transportid, const char *localip, const char *remoteip, const char *remotemx, const char *recipientdomain, const char *jobid, size_t concurrency, size_t messages, double interval, const char *tag, double ttl)

Add a queue policy for the specific fields (the defines should be binary OR’ed). If any of the conditions (transportid, localip, remoteip, remotemx, recipientdomain or jobid) is NULL it will match any value. The concurrency, messages, interval, tag and ttl are optional and may be set to NULL or 0. The interval and ttl is in seconds. The returned id must be freed by the caller. On error NULL is returned.

Fields

HALONMTA_QUEUE_TRANSPORTID

HALONMTA_QUEUE_LOCALIP

HALONMTA_QUEUE_REMOTEIP

HALONMTA_QUEUE_REMOTEMX

HALONMTA_QUEUE_RECIPIENTDOMAIN

HALONMTA_QUEUE_JOBID

bool HalonMTA_queue_policy_update(const char *id, size_t concurrency, size_t messages, double interval, const char *tag, double ttl)

Update a queue policy based on id. The concurrency, messages, interval, tag and ttl are optional and may be set to NULL or 0. The interval and ttl is in seconds. On error false is returned.

bool HalonMTA_queue_policy_delete(const char *id)

Delete a queue policy based the id. On error false is returned.

10.2.2.3. Delivery plugin

bool HalonMTA_deliver_getinfo(HalonDeliverContext *hdc, int type, const void *inval, size_t inlen, void *outval, size_t *outlen)

Get info about the delivery. The following types are available. No memory is owned by the caller. For most types inval and inlen should be (NULL and 0), but for tags it’s the number index of the tag and for metadata the string key index. On error false is returned.

Type

inval

inlen

outval

outlen

HALONMTA_INFO_MESSAGE

NULL

0

&HalonQueueMessage*

n/a

HALONMTA_INFO_FILE

NULL

0

&FILE*

n/a

HALONMTA_INFO_ARGUMENTS

NULL

0

&HalonHSLValue*

n/a

HALONMTA_INFO_RETURN

NULL

0

&HalonHSLValue*

n/a

FILE* fp;
HalonMTA_deliver_getinfo(hdc, HALONMTA_INFO_FILE, NULL, 0, (void*)&fp, NULL);

char* string;
size_t stringl;
HalonMTA_deliver_getinfo(hdc, HALONMTA_INFO_METADATA, "mykey", 0, &string, &stringl);

HalonHSLValue* value;
HalonMTA_deliver_getinfo(hdc, HALONMTA_INFO_RETURN, NULL, 0, &value, NULL);
HalonMTA_hsl_value_set(value, HALONMTA_HSL_TYPE_STRING, "plugin result data", 0);
bool HalonMTA_deliver_setinfo(HalonDeliverContext *hdc, int type, const void *inval, size_t inlen)

Set the result of the delivery status. When the first property of HALONMTA_RESULT_ or HALONMTA_ERROR_ is set. The other properties will have their default values. On error false is returned.

Type

inval

inlen

default

HALONMTA_RESULT_CODE

&short int

n/a

400

HALONMTA_RESULT_ENHANCED_CLASS

&short int

n/a

0

HALONMTA_RESULT_ENHANCED_SUBJECT

&short int

n/a

0

HALONMTA_RESULT_ENHANCED_DETAIL

&short int

n/a

0

HALONMTA_RESULT_REASON

char*

size_t (optional)

“Error”

HALONMTA_ERROR_TEMPORARY

&bool

n/a

true

HALONMTA_ERROR_REASON

char*

size_t (optional)

“Error”

void HalonMTA_deliver_done(HalonDeliverContext *hdc)

This function should be called when the delivery is completed, and after all info is set (using HalonMTA_deliver_setinfo()). This function can be in any thread. The default result if not changed is a temporary error.

HALON_EXPORT
void Halon_deliver(HalonDeliverContext* hdc)
{
        short int status = 250;
        HalonMTA_deliver_setinfo(hdc, HALONMTA_RESULT_CODE, &status, 0);
        HalonMTA_deliver_setinfo(hdc, HALONMTA_RESULT_REASON, "OK", 0);
        HalonMTA_deliver_done(hdc);
}