9.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++, Rust and the Go language (cgo).

Native plugins are loaded into the smtpd process from the configuration file’s plugins[] section.

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

Plugins may also be autoloaded if added to the /opt/halon/plugins folder and symlinked from /opt/halon/plugins/autoload/smtpd and /opt/halon/plugins/autoload/hsh folders (may needs to be created).

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_.

9.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;
}
bool Halon_early_init(HalonInitContext *hic)

This function is called by the MTA upon startup. If this function returns false then the MTA will not start. It is called before Halon_init(), and before any suspends or policies are loaded (so that it can create queue lists using HalonMTA_queue_list_add()). For all other use-cases Halon_init() should be used.

HALON_EXPORT
bool Halon_early_init(HalonInitContext* hic)
{
        return true;
}
bool Halon_privileged_init(HalonInitContext *hic)

This function is called by the MTA upon startup. If this function returns false then the MTA will not start. It is called before Halon_early_init() and should be used to eg. bind privileged socket etc. For all other use-cases Halon_early_init() or Halon_init() should be used.

HALON_EXPORT
bool Halon_privileged_init(HalonInitContext* hic)
{
        return true;
}
void Halon_ready(HalonReadyContext *hrc)

This function is called by the MTA just before entering ready state. All internal processes should have been started.

HALON_EXPORT
void Halon_ready(HalonReadyContext* cfg)
{
}
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. This callback is called right before the process is terminated.

HALON_EXPORT
void Halon_cleanup()
{
}
void Halon_early_cleanup()

This function is called early in the cleanup by the MTA upon shutdown. It’s called as one of the first things in the shut down process. For example stopping a custom message injector.

HALON_EXPORT
void Halon_early_cleanup()
{
}

9.2.1.1. Command plugin

bool Halon_command_execute(HalonCommandExecuteContext *hcec, size_t argc, const char *argv[], size_t argvl[], 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_command_execute(HalonCommandExecuteContext* hcec, size_t argc, const char* argv[], size_t argvl[], char** out, size_t* outlen)
{
        if (argc > 0 && strcmp(argv[0], "hello") == 0)
        {
                *out = strdup("world");
                return true;
        }
        return false;
}
bool Halon_plugin_command(const char *in, size_t inlen, char **out, size_t *outlen)

Deprecated in 5.8 by Halon_command_execute()

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.

9.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 Halon.

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.

The lifetime of the hhc, args and ret argument pointers are only valid during the call of the myfunc function.

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

9.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. If the message is requeued the concurrency and rate is not incremented.

bool Halon_queue_pickup_acquire2(HalonQueueContext *hqc)

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_queue_getinfo() function and get HALONMTA_INFO_MESSAGE and then use the HalonMTA_message_getinfo() function. If you need to pass data to the pre or post-delivery hook use the HalonMTA_queue_getinfo() function and get HALONMTA_INFO_RETURN properties. 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. If the message is requeued the concurrency and rate is not incremented.

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.

void Halon_queue_pickup_release2(HalonQueueContext *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_queue_getinfo() function and get HALONMTA_INFO_MESSAGE and then use the HalonMTA_message_getinfo() function. If you need to pass data to the post-delivery hook use the HalonMTA_queue_getinfo() function and get HALONMTA_INFO_RETURN properties. This function must be non-blocking.

9.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);
}

9.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);
}
bool HalonMTA_config_to_json(const HalonConfig *value, char **out, size_t *outlen)

Convert a HalonConfig to a JSON string. If the conversion fails, false is returned and out contains the error message (if any; otherwise null). The memory returned in out must be freed using free();

char* json;
size_t jsonlen;
if (HalonMTA_config_to_json(cfg, &json, &jsonlen))
        printf("%s\n", json);
free(json);
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_FILE

  • HALONMTA_HSL_TYPE_FUNCTION

  • HALONMTA_HSL_TYPE_MAILMESSAGE

  • HALONMTA_HSL_TYPE_X509

  • HALONMTA_HSL_TYPE_PRIVATEKEY

  • 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

HALONMTA_HSL_TYPE_FILE

&FILE*

n/a

HALONMTA_HSL_TYPE_X509

&X509*

n/a

HALONMTA_HSL_TYPE_PRIVATEKEY

&EVP_PKEY*

n/a

char* string;
size_t stringl;
HalonMTA_hsl_value_get(value, HALONMTA_HSL_TYPE_STRING, &string, &stringl);

Note

When using a FILE* from HALONMTA_HSL_TYPE_FILE, always call fseek to the intended location before use.

HalonHSLValue *HalonMTA_hsl_object_property_get(HalonHSLValue *object, const char *name)

Get a reference to a property of an object by name. This can be used do retrive function object etc. If the properties doesn’t exist NULL is returned.

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;
}
size_t HalonMTA_hsl_value_array_length(const HalonHSLValue *args)

Get the length of the HalonHSLValue array.

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

Comment

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_TYPE_EXCEPTION

char*

size_t (optional)

HALONMTA_HSL_TYPE_X509

X509*

n/a

OpenSSL’s X509_free is called on destruction, but not X509_up_ref on initialization

HALONMTA_HSL_TYPE_PRIVATEKEY

EVP_PKEY*

n/a

OpenSSL’s EVP_PKEY_free is called on destruction, but not EVP_PKEY_up_ref on initialization

HALONMTA_HSL_TYPE_THIS

HalonHSLContext*

n/a

For HSL objects, return a reference to $this

HALONMTA_HSL_TYPE_COPY

HalonHSLValue*

n/a

Copy a HSL value

HALONMTA_HSL_TYPE_FILE

char*

size_t (optional)

HALONMTA_HSL_TYPE_MAILMESSAGE

char*

size_t (optional)

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.

bool HalonMTA_hsl_value_to_json(const HalonHSLValue *value, char **out, size_t *outlen)

Convert a HalonHSLValue to a JSON string. If the conversion fails, false is returned and out contains the error message (if any; otherwise null). The memory returned in out must be freed using free();

char* json;
size_t jsonlen;
if (HalonMTA_hsl_value_to_json(HalonMTA_hsl_argument_get(args, 0), &json, &jsonlen))
        printf("%s\n", json);
free(json);
bool HalonMTA_hsl_value_from_json(HalonHSLValue *value, const char *json, char **out, size_t *outlen)

Convert a JSON string to a HalonHSLValue. If the conversion fails, false is returned and out contains the error message (if any; otherwise null). The memory returned in out must be freed using free();

char* err;
size_t errlen;
if (!HalonMTA_hsl_value_from_json(ret, json, &err, &errlen))
{
        if (err) printf("Error: %s\n", json);
        free(err);
}

9.2.2.1. Objects

The C API allows you to create HSL object. An object is a collection of functions and a custom pointer.

HalonHSLObject *HalonMTA_hsl_object_new()

Return a pointer to a HSL object. This object must be freed using HalonMTA_hsl_object_delete() after copied to one or more HSL values using HalonMTA_hsl_value_set().

HalonHSLObject* hho = HalonMTA_hsl_object_new();
HalonMTA_hsl_value_set(ret, HALONMTA_HSL_TYPE_OBJECT, hho, 0);
HalonMTA_hsl_object_delete(hho);
bool HalonMTA_hsl_object_type_set(HalonHSLObject *hho, const char *type)

Set the class name. Used to identify the object.

HalonHSLObject* hho = HalonMTA_hsl_object_new();
HalonMTA_hsl_object_type_set(hho, "MyObject");
HalonMTA_hsl_object_delete(hho);
void HalonMTA_hsl_object_ptr_set(HalonHSLObject *hho, void *ptr, void (*free)(void*));

Set the internal ptr of the object. The ptr may be changed when reusing the same object with HalonMTA_hsl_value_set(). The free function can be used to free the ptr when the HSL object is destroyed.

HalonHSLObject* hho = HalonMTA_hsl_object_new();
HalonMTA_hsl_object_type_set(hho, "MyObject");
HalonMTA_hsl_object_ptr_set(hho, strdup("Hello World"), free);
HalonMTA_hsl_value_set(value1, HALONMTA_HSL_TYPE_OBJECT, hho, 0);
HalonMTA_hsl_object_ptr_set(hho, strdup("Hello World"), free);
HalonMTA_hsl_value_set(value2, HALONMTA_HSL_TYPE_OBJECT, hho, 0);
HalonMTA_hsl_object_delete(hho);
void HalonMTA_hsl_object_register_function(HalonHSLObject *hho, const char *name, HalonHSLFunction *hhf);

Register member function on the object, this hhf function should be a regular HSL function myfunc(). When the hhf function is called HalonMTA_hsl_object_ptr_get() may be used to receive the pointer set by HalonMTA_hsl_object_ptr_set() hence making it an instance function of the object.

void *HalonMTA_hsl_object_ptr_get(HalonHSLContext *hhc)

Get the instance pointer set by HalonMTA_hsl_object_ptr_set() from within a HSL function (myfunc()). If not called within a object a null pointer is returned.

void sayhi(HalonHSLContext* hhc, HalonHSLArguments* args, HalonHSLValue* ret)
{
    char* ptr = (char*)HalonMTA_hsl_object_ptr_get(hhc);
    printf("%s\n", ptr);
    HalonMTA_hsl_value_set(ret, HALONMTA_HSL_TYPE_THIS, hhc, 0);
}

// ...
HalonHSLObject* hho = HalonMTA_hsl_object_new();
HalonMTA_hsl_object_type_set(hho, "MyObject");
HalonMTA_hsl_object_ptr_set(hho, strdup("Hello World"), free);
HalonMTA_hsl_object_register_function(hho, "sayhi", &sayhi);
HalonMTA_hsl_value_set(ret, HALONMTA_HSL_TYPE_OBJECT, hho, 0);
HalonMTA_hsl_object_delete(hho);
void HalonMTA_hsl_object_delete(HalonHSLObject *hho)

Free the HSL object created by HalonMTA_hsl_object_new().

9.2.2.2. Callable

The C API allows you to call HSL function by function pointers (often referred to as callback functions).

HalonHSLFunctionCallable *HalonMTA_hsl_function_callable_new(const HalonHSLContext *hhc, const HalonHSLValue *func)

Return a pointer to a HalonHSLFunctionCallable object. This object must be freed using HalonMTA_hsl_function_callable_delete(). If the func argument is not a callable function pointer this function returns null.

HalonHSLFunctionCallable* hhfc = HalonMTA_hsl_function_callable_new(hhc, func);
HalonMTA_hsl_function_callable_delete(hhfc);
HalonHSLValue *HalonMTA_hsl_function_callable_argument_add(HalonHSLFunctionCallable *hhfc)

Add an argument to be used when invoked using HalonMTA_hsl_function_callable_invoke(). The default value of the argument is none. Previously return HalonHSLValue pointers are invalided when the next argument is requested.

auto hhfc = HalonMTA_hsl_function_callable_new(hhc, func);
HalonMTA_hsl_value_set(
        HalonMTA_hsl_function_callable_argument_add(hhfc),
        HALONMTA_HSL_TYPE_STRING,
        "Hello", 0);
HalonMTA_hsl_value_set(
        HalonMTA_hsl_function_callable_argument_add(hhfc),
        HALONMTA_HSL_TYPE_STRING,
        "World", 0);
HalonMTA_hsl_function_callable_delete(hhfc);
bool HalonMTA_hsl_function_callable_invoke(const HalonHSLFunctionCallable *hhfc, HalonHSLValue **ret)

Invoke the HSL function. This function returns a boolean value where

  • true: ret is the return value of the function

  • false: ret is the exception thrown by the function

The lifetime of the ret pointer is bound to the HalonHSLFunctionCallable and is overwritten by each repeated invocation of this function.

auto ctx = HalonMTA_hsl_function_callable_new(hhc, func);
HalonMTA_hsl_value_set(
        HalonMTA_hsl_function_callable_argument_add(ctx),
        HALONMTA_HSL_TYPE_STRING,
        "Hello", 0);
HalonHSLValue* retval;
if (HalonMTA_hsl_function_callable_invoke(ctx, &retval))
        HalonMTA_hsl_value_set(ret, HALONMTA_HSL_TYPE_COPY, retval, 0);
else
        HalonMTA_hsl_value_set(HalonMTA_hsl_throw(hhc), HALONMTA_HSL_TYPE_COPY, retval, 0);
HalonMTA_hsl_function_callable_delete(ctx);

A sample function pointer in HSL.

myfunc(function($phrase) {
        echo "$phrase World!";
});
void HalonMTA_hsl_function_callable_delete(HalonHSLFunctionCallable *hho)

Free the HalonHSLFunctionCallable object created by HalonMTA_hsl_function_callable_new().

9.2.2.3. 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(args, index)))
{
        printf("%zu: type of a %d\n", index, HalonMTA_hsl_value_type(a));
        ++index;
}
size_t HalonMTA_hsl_argument_length(const HalonHSLArguments *args)

Get the length of the HalonHSLArguments list.

HalonHSLValue *HalonMTA_hsl_throw(HalonHSLContext *hhc)

Get a pointer to the exception value that will be thrown when returning from the function. Any HSL type may be thrown. This superseeds any change to the return value.

void example(HalonHSLContext* hhc, HalonHSLArguments* args, HalonHSLValue* ret)
{
        HalonHSLValue* e = HalonMTA_hsl_throw(hhc);
        HalonMTA_hsl_value_set(e, HALONMTA_HSL_TYPE_EXCEPTION, "An error has occurred", 0); // throw an instance of Exception
        return;
}
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_module_register_function(HalonHSLRegisterContext *hhrc, const char *name, HalonHSLFunction *func)

Register a function to the plugins module namespace. 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). If func is null, in order to prevent naming conflicts, function could be defined and referenced with a custom namespace eg. “xyz__myfunc”, while the registered function name would only be “myfunc”. On error false is returned.

HALON_EXPORT
bool Halon_hsl_register(HalonHSLRegisterContext* hhrc)
{
        HalonMTA_hsl_module_register_function(hhrc, "myfunc", myfunc);
        return true;
}

For GO (cgo) both options are available. The later also needs to include #include <dlfcn.h>.

//export myfunc
func myfunc(hhc *C.HalonHSLContext, args *C.HalonHSLArguments, ret *C.HalonHSLValue) {

}

//export Halon_hsl_register
func Halon_hsl_register(hhrc *C.HalonHSLRegisterContext) C.bool {
        C.HalonMTA_hsl_module_register_function(hhrc, C.CString("myfunc"), nil);
        C.HalonMTA_hsl_module_register_function(hhrc, C.CString("myfunc"), (*C.HalonHSLFunction)(unsafe.Pointer(C.dlsym(nil, C.CString("myfunc")))));
}
bool HalonMTA_hsl_module_register_static_function(HalonHSLRegisterContext *hhrc, const char *classname, const char *name, HalonHSLFunction *func)

Register a static class function in the plugins module namespace. Must only be called in Halon_hsl_register(). If must be called after HalonMTA_hsl_module_register_function() so that the class name (function namespace) is created. 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). If func is null, in order to prevent naming conflicts, function could be defined and referenced with a custom namespace eg. “xyz__myfunc”, while the registered function name would only be “myfunc”. On error false is returned.

HALON_EXPORT
bool Halon_hsl_register(HalonHSLRegisterContext* hhrc)
{
        HalonMTA_hsl_module_register_function(hhrc, "MyClass", MyClass);
        HalonMTA_hsl_module_register_static_function(hhrc, "MyClass", "staticfunction", MyClass_staticfunction);
        return true;
}
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). If func is null, in order to prevent naming conflicts, function could be defined and referenced with a custom namespace eg. “xyz__myfunc”, while the registered function name would only be “myfunc”. 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);
}
bool HalonMTA_hsl_register_static_function(HalonHSLRegisterContext *hhrc, const char *classname, const char *name, HalonHSLFunction *func)

Register a static class function in HSL. Must only be called in Halon_hsl_register(). If must be called after HalonMTA_hsl_register_function() so that the class name (function namespace) is created. 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). If func is null, in order to prevent naming conflicts, function could be defined and referenced with a custom namespace eg. “xyz__myfunc”, while the registered function name would only be “myfunc”. On error false is returned.

HALON_EXPORT
bool Halon_hsl_register(HalonHSLRegisterContext* hhrc)
{
        HalonMTA_hsl_register_function(hhrc, "MyClass", MyClass);
        HalonMTA_hsl_register_static_function(hhrc, "MyClass", "staticfunction", MyClass_staticfunction);
        return true;
}

9.2.2.4. Lists plugin

The list plugin may control the content of lists in the queue policy, queue suspend and queue delivery configurations.

bool HalonMTA_queue_list_add(int type, const char *name);

Add a list, this is usually done in the Halon_early_init() function. The type should be one of the HALONMTA_QUEUE_ defines. The name should be given without the “@” prefix. On error false is returned.

bool HalonMTA_queue_list_delete(int type, const char *name);

Delete a list. On error false is returned.

bool HalonMTA_queue_list_item_add(int type, const char *name, const char *item);

Add an item to a list. In order to apply the change call HalonMTA_queue_list_reload(). On error false is returned.

char *HalonMTA_queue_list_item_get(int type, const char *name, size_t index, size_t *length);

Get an item from a list based on the index (starts at zero). The returned item must be freed by the caller. On error NULL is returned.

bool HalonMTA_queue_list_item_delete(int type, const char *name, const char *item);

Delete an item from a list. In order to apply the change call HalonMTA_queue_list_reload(). On error false is returned.

bool HalonMTA_queue_list_reload(int type, const char *name);

Reload a list making the changes affect the queue policies. On error false is returned.

9.2.2.5. Inject plugin

The inject plugins API allows you to submit messages to the MTA (EOD hook) without using the SMTP or HTTP API/Protocol. In order to import a plain HQF file you may use the import plugin API.

HalonInjectContext *HalonMTA_inject_new(const char *serverid);

This function create a new HalonInjectContext* context to be used when submitting a message. If the function fails (eg. due to missing serverid, disk full etc) then NULL is returned. The type of the serverid (servers[].type) used must be plugin. The pointer (context) must be passed and successfully used with HalonMTA_inject_commit() or HalonMTA_inject_abort() in order to prevent memory and resource leaks.

HalonInjectContext *hic = HalonMTA_inject_new("plugin1");
...
if (!HalonMTA_inject_commit(hic))
        HalonMTA_inject_abort(hic);
void HalonMTA_inject_abort(HalonInjectContext *hic);

This function should be called on all code-paths unless HalonMTA_inject_commit() returns true in order to prevent memory and resource leaks.

bool HalonMTA_inject_sender_set(HalonInjectContext *hic, const char *localpart, const char *domain);

This function sets the sender localpart and domain of the message.

HalonMTA_inject_sender_set(hic, "user", "example.com");
// or
HalonMTA_inject_sender_set(hic, nullptr, nullptr); // empty-sender
bool HalonMTA_inject_recipient_add(HalonInjectContext *hic, const char *localpart, const char *domain, const char *transportid);

This function adds a recipient to the message. If transportid is NULL the servers default will be used.

HalonMTA_inject_recipient_add(hic, "user", "example.org", nullptr);
bool HalonMTA_inject_message_append(HalonInjectContext *hic, const char *data, size_t length);

This function appends content to a message, it may be called multiple times. If length is 0, the length will be determined by the C library strlen() function.

while (!feof(fp))
{
        char buf[8192];
        size_t r = fread(buf, sizeof buf, 1, fp);
        if (r > 0)
                HalonMTA_inject_message_append(hic, buf, r);
}
bool HalonMTA_inject_callback_set(HalonInjectContext *hic, HalonInjectResultFunction *cb, void *userptr);

This function registers a callback function and a user pointer that will be called after the message has be submitted and processed by the EOD hook.

MyInjectContext* mic = new MyInjectContext;
HalonMTA_inject_callback_set(hic, MyInjectCallback, mic);

When message has executed the EOD hook, the cb function will be called (in the thread of the EOD hook). Use the HalonMTA_inject_result_getinfo() function to get the result.

void MyInjectCallback(HalonInjectResultContext* hirc, void* userptr)
{
        MyInjectContext* mic = (MyInjectContext*)userptr;
        short int code;
        HalonMTA_inject_result_getinfo(hirc, HALONMTA_RESULT_CODE, nullptr, 0, &code, nullptr);
}
bool HalonMTA_inject_getinfo(HalonInjectContext *hic, int type, const void *inval, size_t inlen, void *outval, size_t *outlen);

Get info about the HalonInjectContext. 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_METADATA

n/a

n/a

&HalonHSLValue

n/a

HALONMTA_MESSAGE_TRANSACTIONID

n/a

n/a

&char*

&size_t

bool HalonMTA_inject_commit(HalonInjectContext *hic);

This function submits messages to the MTA. If this function return false HalonMTA_inject_abort() must be called to prevent memory and resource leaks.

HalonInjectContext *hic = HalonMTA_inject_new("plugin1");
...
if (!HalonMTA_inject_commit(hic))
        HalonMTA_inject_abort(hic);
bool HalonMTA_inject_result_getinfo(HalonInjectResultContext *hirc, int type, const void *inval, size_t inlen, void *outval, size_t *outlen);

This function is used with a HalonInjectResultContext pointer obtained in the callback set by HalonMTA_inject_callback_set(). 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_RESULT_CODE

n/a

n/a

&short int

n/a

HALONMTA_RESULT_ENHANCED_CLASS

n/a

n/a

&short int

n/a

HALONMTA_RESULT_ENHANCED_SUBJECT

n/a

n/a

&short int

n/a

HALONMTA_RESULT_ENHANCED_DETAIL

n/a

n/a

&short int

n/a

HALONMTA_RESULT_REASON

n/a

n/a

&char*

&size_t

HALONMTA_RESULT_METADATA

n/a

n/a

&HalonHSLValue

n/a

void MyInjectCallback(HalonInjectResultContext* hirc, void* userptr)
{
        HalonHSLValue *metadata;
        HalonMTA_inject_result_getinfo(hirc, HALONMTA_RESULT_METADATA, nullptr, 0, &metadata, nullptr);
}

9.2.2.6. Import plugin

The import plugins API allows you to import HQF data directly into the queue (pre-delivery hook). In order to inject a plain message/rfc822 you may use the inject plugin API.

HalonImportContext *HalonMTA_import_new(int flags);

This function create a new HalonImportContext* context to be used when importing a message. The pointer (context) must be passed and successfully used with HalonMTA_import_commit() or HalonMTA_import_abort() in order to prevent memory and resource leaks.

HalonImportContext *hic = HalonMTA_import_new(0);
...
if (!HalonMTA_import_commit(hic))
        HalonMTA_import_abort(hic);

Flag

Purpose

HALONMTA_IMPORT_RESET_TRANSACTIONID

Generate a new transaction id

HALONMTA_IMPORT_RESET_TS

Set the timestamp to current time

HALONMTA_IMPORT_RESET_RETRYCOUNT

Reset the retry count to zero

void HalonMTA_import_abort(HalonImportContext *hic);

This function should be called on all code-paths unless HalonMTA_import_commit() returns true in order to prevent memory and resource leaks.

bool HalonMTA_import_append(HalonImportContext *hic, const char *data, size_t length);

This function appends HQF data, it may be called multiple times. Since HQF files are partially in a binary format, lenght must always be specified.

while (!feof(fp))
{
        char buf[8192];
        size_t r = fread(buf, sizeof buf, 1, fp);
        if (r > 0)
                HalonMTA_import_append(hic, buf, r);
}
bool HalonMTA_import_commit(HalonImportContext *hic);

This function imports the HQF data to the MTA. If this function return false HalonMTA_import_abort() must be called to prevent memory and resource leaks.

HalonImportContext *hic = HalonMTA_import_new(0);
...
if (!HalonMTA_import_commit(hic))
        HalonMTA_import_abort(hic);

9.2.2.7. Queue plugin

bool HalonMTA_queue_getinfo(HalonQueueContext *hqc, int type, const void *inval, size_t inlen, void *outval, size_t *outlen)

Get info about a message in queue system. 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_RETURN

NULL

0

&HalonHSLValue*

n/a

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_GROUPING

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_add4(const char *transportid, const char *localip, const char *remoteip, const char *remotemx, const char *recipientdomain, const char *jobid, const char *grouping, const char *tenantid, const char *tag, const char *propv[], size_t propl, double ttl)

Add a suspend based on conditions. If any of the conditions (transportid, localip, remoteip, remotemx, recipientdomain, jobid, grouping or tenantid) 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 propv array should contain even number of items (as indicated by propl), the first string in pairs will be the property name, and the second the property value. The returned id must be freed by the caller. On error NULL is returned.

char *HalonMTA_queue_suspend_add3(const char *transportid, const char *localip, const char *remoteip, const char *remotemx, const char *recipientdomain, const char *jobid, const char *grouping, const char *tenantid, const char *tag, double ttl)

Same as HalonMTA_queue_suspend_add4() but with the propv set to NULL and propl set to 0 .

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

Same as HalonMTA_queue_suspend_add3() but with the tenantid set to NULL.

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)

Same as HalonMTA_queue_suspend_add2() but with the grouping set to NULL.

bool HalonMTA_queue_suspend_update2(const char *id, const char *tag, const char *propv[], size_t propl, 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. The propv array should contain even number of items (as indicated by propl), the first string in pairs will be the property name, and the second the property value. On error false is returned.

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

Same as HalonMTA_queue_suspend_update2() but with the propv set to NULL and propl set to 0 .

bool HalonMTA_queue_suspend_delete(const char *id)

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

char *HalonMTA_queue_policy_add3(int fields, const char *transportid, const char *localip, const char *remoteip, const char *remotemx, const char *recipientdomain, const char *jobid, const char *grouping, size_t concurrency, size_t messages, double interval, const char *tag, const char *propv[], size_t propl, bool stop, 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, jobid or grouping) is NULL it will match any value. The concurrency, messages, interval, tag, propv, propl and ttl are optional and may be set to NULL or 0. The stop indicates if the policy should trigger a stop of policy evaluation (short-circuit). The interval and ttl is in seconds. The propv array should contain even number of items (as indicated by propl), the first string in pairs will be the property name, and the second the property value. 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

HALONMTA_QUEUE_GROUPING

char *HalonMTA_queue_policy_add2(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, const char *propv[], size_t propl, bool stop, double ttl)

Same as HalonMTA_queue_policy_add3() but with the grouping set to NULL.

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)

Same as HalonMTA_queue_policy_add2() but with the propv set to NULL, propl set to 0 and stop argument set to false.

bool HalonMTA_queue_policy_update2(const char *id, size_t concurrency, size_t messages, double interval, const char *tag, const char *propv[], size_t propl, 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. The propv array should contain even number of items (as indicated by propl), the first string in pairs will be the property name, and the second the property value. On error false is returned.

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

Same as HalonMTA_queue_policy_update2() but with the propv set to NULL and propl set to 0 .

bool HalonMTA_queue_policy_delete(const char *id)

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

9.2.2.8. 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”

bool HalonMTA_deliver_trace(HalonDeliverContext *hdc, int flags, const void *inval, size_t inlen)

This function can be used to add trace information (extracted using halonctl queue trace). This function can be used in any thread. Flags may be either 0 (inval logged with timestamp and line wrapps grouped) or HALONMTA_TRACE_BINARY (output the inval binary - as-is). If inlen is zero, the length is determined by the null termination of inval. If no trace is active, this function returns false.

HALON_EXPORT
void Halon_deliver(HalonDeliverContext* hdc)
{
        HalonMTA_deliver_trace(hdc, 0, "Hello", 0);
        HalonMTA_deliver_done(hdc);
}
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 used 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);
}