Tip of the day: The Security article gives hands-on tips on how to deal with drone attacks, flooding, spammers, (D)DoS and more.

Dev:Configuration API

From UnrealIRCd documentation wiki
Jump to navigation Jump to search

Introduction

Modules can make full use of UnrealIRCd's config parser, i.e. custom configuration blocks or set::something variables can be created and their parameters can be acquired. You are then responsible for storing this information in variables (eg: some_option = 1) or allocate structs for it (eg: if your module adds multiple xyz { } blocks).

We will start with an introduction to the ConfigEntry * type, then quickly move to the testing and running configuration functions with example code and finally explain the various Configuration API functions you can (and should) use, for example to communicate errors or to make parsing easier.

Structures

In the "config test" and "config run" functions you will, each time, receive some structures which you have to deal with. They are explained below:

ConfigEntry

In your function you receive an ConfigEntry *ce. Below we explain what the members of these structs are. We will also use the following (unusual) configuration block to illustrate:

set {
    this-is-a-variable 1234 { fantastic; superb; };
}
Member Type Description Example value
ce->name char * Variable name "this-is-a-variable"
ce->value char * Variable data "1234"
ce->items ConfigEntry * Pointer to a child item. (If any, otherwise it will return NULL) ce->items->name will return "fantastic"
ce->next ConfigEntry * Pointer to the next item. (If any, otherwise it will return NULL) ce->next will return NULL,

ce->items->next->name will return "superb"

ce->file ConfigFile * Pointer to the configuration file item.

Any file item other than filename (see next) is hardly ever used.

-
ce->file->filename char * Pointer to the filename containing the configuration block or variable. "unrealircd.conf"
ce->line_number int Number of line for variable / configuration block. 2

Don't worry if this doesn't make full sense yet. In the sections below you will see example code.

Flow

Testing configuration

To add custom blocks to the configuration file and acquire their parameters, you must add the HOOKTYPE_CONFIGTEST hook in MOD_TEST of your module. (This feature will never be called if you hook it from MOD_INIT or MOD_LOAD.) An integer will be returned to indicate if the configuration file may be loaded or if there would be some fatal errors, in which the boot or /REHASH will fail.

HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, mymod_config_test);

Example code:

int mymod_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
{
    int errors = 0;
    ConfigEntry *cep, *cep2;

    /* We filter on CONFIG_SET here, which means we will only get set::something.
     * You can also filter on CONFIG_MAIN which is to be used if you introduce your
     * own something { } block outside of a set { } block.
     */
    if (type != CONFIG_SET)
        return 0;

    /* We are only interrested in set::mymod... */
    if (!ce || !ce->name || strcmp(ce->name, "mymod"))
        return 0; // return 0 to indicate: we don't know / we don't handle this!

    for (cep = ce->items; cep; cep = cep->next)
    {
        if (!cep->name)
        {
            config_error("%s:%i: blank set::mymod item",
                cep->file->filename, cep->line_number);
            errors++;
            continue;
        }
        else if (!strcmp(cep->name, "option1"))
        {
            /* set::mymod::option1 */
            // do some useful check here...
            option1_specified = 1;
        }
        else if (!strcmp(cep->name, "option2"))
        {
            /* set::mymod::option2 */
            // do some useful check here...
            option2_specified = 1;
        }
        else {
            config_error("%s:%i: unknown directive set::mymod::%s",
                cep->file->filename, cep->line_number, cep->name);
            errors++;
        }
    }
    *errs = errors;
    return errors ? -1 : 1; /* we return 1 to say: yup this belongs to us, it's handled/good, and return -1 to indicate we encountered some error */
}

Checking for missing configuration items

NOTE: This step is not required if your module always provides default values, in which case you simply don't use this hook.

The HOOKTYPE_CONFIGPOSTTEST hook is called after all HOOKTYPE_CONFIGTEST hooks have been called. If you require a specific (mandatory) configuration block or variable then this allows you, with the help of HOOKTYPE_CONFIGTEST earlier, to see if some option was never specified.

Hook it from MOD_TEST:

HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, mymod_config_posttest);

Function & example code:

int mymod_config_posttest(int *errs)
{
    int errors = 0;

    if (!option1_specified) { config_error("set::mymod::option1 missing"); errors++; }
    if (!option2_specified) { config_error("set::mymod::option2 missing"); errors++; }
}

Running the configuration

Once the configuration file has passed testing (see previous two hooks) UnrealIRCd will "run" the configuration file. At this point you can no longer "cancel" processing of the configuration file so any options specified are assumed to be valid at this point and you must deal with it.

You add this HOOKTYPE_CONFIGRUN hook from MOD_INIT:

HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, mymod_config_run);

Function syntax & continueing with the example from HOOKTYPE_CONFIGTEST and HOOKTYPE_CONFIGPOSTTEST:

int mymod_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
{
    ConfigEntry *cep;

    /* We filter on CONFIG_SET here, which means we will only get set::something.
     * You can also filter on CONFIG_MAIN which is to be used if you introduce your
     * own something { } block outside of a set { } block.
     */
    if (type != CONFIG_SET)
        return 0;

    /* We are only interrested in set::mymod... */
    if (!ce || !ce->name || strcmp(ce->name, "mymod"))
        return 0; // if it's anything else: we don't care

    for (cep = ce->items; cep; cep = cep->next)
    {
        if (!strcmp(cep->name, "option1"))
            option1 = config_checkval(cep->value, CFG_YESNO);
        else if (!strcmp(cep->name, "option2"))
            option2 = atoi(cep->value);
    }
    return 1; // we handled it
}

Available functions

The UnrealIRCd configuration API exposes a number of functions which you are recommended to use for parsing, displaying errors, etc.

Parsing functions

To convert a string to an integer you normally use atoi() or atol(), however in some cases you may want to use a little bit more advanced parsing...

config_checkval

Allows you to convert time, size and boolean values to a 'long':

long t = config_checkval(cep->value, CFG_TIME);
long s = config_checkval(cep->value, CFG_SIZE);
int bool = config_checkval(cep->value, CFG_YESNO);
Cfg type What for Example string Example result
CFG_TIME Time "7d10h5s" 7*86400 + 10*3600 + 5
CFG_SIZE, Size "1M" 1048576 (or 1000000? did not check..)
CFG_YESNO Boolean "yes", "no", "true", "false", "1", "0" 1 or 0

Logging and error functions

These functions will output the warnings/errors to the ircd.log and send notices to any IRC Operators that are on your server. Therefore, you should use these functions to signal any warnings or errors during configuration parsing.

Note that you should normally only send warnings/errors from the HOOKTYPE_CONFIGTEST and HOOKTYPE_CONFIGPOSTTEST callbacks and never from HOOKTYPE_CONFIGRUN.

config_warn

A configuration warning. Informs the user about something important during parsing. No fatal error, configuration may still be loaded.

Example:

config_warn("Missing value XYZ. Assuming default 123.");

config_error

A fatal error which prevents the configuration file from being loaded. Example:

config_error("%s:%i: set::something should be a value between 1 and 20.",
    cep->file->filename, cep->line_number);

Note that you still need to increases the 'errors' variable (errors++) and set it, as pointed out in the example code earlier.

config_error_unknown

If you provide configuration blocks and you encounter an unknown variable then you can use this.

Example, deny version { hdfssfdhsdhds; } will error on the "hdfssfdhsdhds" which is in cep->name:

config_error_unknown(cep->file->filename,
        cep->line_number, "deny version", cep->name);

config_is_blankorempty

Returns true if a ConfigEntry is blank or empty (has no variable name or no value).

Example configuration block:

me {
     something; // variable name but no value.
};

Example code:

for (cep = ce->items; cep; cep = cep->next)
{
    if (config_is_blankorempty(cep, "me"))
    {
        errors++;
        continue;
    }
}

Of course sometimes you may want to allow a variable without value because it looks nicer, then simply don't use this function.