Tip of the day: If you still have users on plaintext port 6667, consider enabling Strict Transport Security to gently move users to SSL/TLS on port 6697.

Dev:Module Storage

From UnrealIRCd documentation wiki
Jump to navigation Jump to search

Introduction

The ModData system allows you attach custom metadata to objects such as clients, channels, channel members and channel memberships.

ModData vs modes

When you want a user to be able to set a setting on/off on itself, you normally use user modes. Similarly, to configure channel settings there are channel modes. However, sometimes you want to store specific data for a user or channel that are not changeable settings and shouldn't really be shown to a user/channel MODE. Think of things like counters or other properties of a user/channel. This is where ModData is for. Other benefits of the ModData system is that the data format is up to you. You can store binary data, even kilobytes of data per client, it's entirely up to you.

Examples

Examples of current ModData usage in UnrealIRCd are:

  • webirc (client): if a user is a webirc user or not.
  • certfp (client): the TLS client certificate fingerprint of a user.
  • jointhrottle (client): the /JOIN history of a client so the jointhrottle module knows if the user needs to be throttled or not.
  • websocket (client): internally used to store websocket data
  • blacklist (client): internally used when processing blacklists
  • floodprot (channel membership): for all counters and timers to handle channel mode +f events

Registering custom data

Register the custom data by calling the following from MOD_INIT (similar like registering channel modes).

ModDataInfo *example_md = NULL;

MOD_INIT()
{
ModDataInfo mreq;

    memset(&mreq, 0, sizeof(mreq));
    mreq.type = MODDATATYPE_CLIENT;
    mreq.name = "example";
    mreq.sync = 1;
    mreq.free = example_free;
    mreq.serialize = example_serialize;
    mreq.unserialize = example_unserialize;
    example_md = ModDataAdd(modinfo->handle, mreq);

See the description of each field below:

type

You select where to attach your custom data to, by setting type to any of:

  • MODDATATYPE_CLIENT: attach data to clients (these clients may be persons, servers, etc)
  • MODDATATYPE_CHANNEL: attach data to channels
  • MODDATATYPE_MEMBER: attach data to Member structures
  • MODDATATYPE_MEMBERSHIP: attach data to Membership structures.

Detail: MODDATATYPE_MEMBER and MODDATATYPE_MEMBERSHIP are rarely used. In case you wonder, the difference between Member and Membership is that Member is found by looking at the channel and iterating through the member list (users that are a member of the channel), while Membership is found by looking at the user and then iterating through the channel list (channels the user is a member of). Depending on the type of access you use either MODDATATYPE_MEMBER, MODDATATYPE_MEMBERSHIP or sometimes both.

name

You give your module data entry an unique name (within each MODDATATYPE_*'s namespace).

sync

The sync field decides whether you want to sync all ModData of the clients & channels upon server-linking. Similarly, it will instruct some functions to also synchronize it when a property is changed.

Most modules only need the metadata locally, but some do enable this sync field. For example the "certfp" has sync=1 so other servers can also see the "certfp" of a user.

free/serialize/unserialize

Finally, the following functions are registered:

  • free: called when the object is freed
  • serialize[*]: called when the data needs to be converted to a string
  • unserialize[*]: called when a string needs to be converted back to data

If you have sync set to 0 and you do not use the moddata_xxx_set()/moddata_xxx_get() functions then you don't need to provide a serialize and unserialize function.

The functions are described in next section. Be sure to also read the section after that, on Acquiring and storing moddata as well.

Data functions

Free

This function is called when the ModData for this user/client/member needs to be freed and nullified. Example code if you use simple strings:

void example_free(ModData *m)
{
    if (m->str)
        MyFree(m->str);
    m->str = NULL;
}

Serialize

This function is called when you want to fetch a ModData and convert it to a string. Usage and example (using strings):

char *example_serialize(ModData *m)
{
    if (!m->str)
        return NULL;
    return m->str;
}

Unserialize

This function is called when there's data to store in your ModData. You receive a string and you convert it to your own (binary) data if necessary. Example using strings:

void example_unserialize(char *str, ModData *m)
{
    if (m->str)
        MyFree(m->str);
    m->str = strdup(str);
}

Acquiring and storing moddata for a client

UnrealIRCd offers 2 ways to access ModData, direct and indirect:

Direct access

This method is very fast and easy to use (especially if you store integers)

moddata_client(acptr, example_md).something = xyz;

Where:

  • acptr: The client you want to change a property of
  • example_md: Is a global variable in the module, returned by ModDataAdd() - see earlier on this page

moddata_client() gives you a ModData union with the following members (pick one instead of something):

  • i: to store an integer
  • l: to store a long integer
  • str: to store a string
  • ptr: to store a pointer (void *), allowing any other type of storage

So, to store an integer for example you can do:

moddata_client(acptr, example_md).i = 1234;

And, to store a string:

moddata_client(acptr, example_md).str = strdup("1234"); // note: better free() first if necessary ;)

Finally, if you have some custom structure:

SomeCustomType *custom = moddata_client(acptr, example_md).ptr;
custom.one = 1234;
if (custom.two)
    MyFree(custom.two);
custom.two = strdup("lalala");

Indirect access (serializing/unserializing data)

We also offer a more 'high level' way to store and retrieve data. It uses the serialize/unserialize functions behind the scenes. It can be used if you want to get/set mod data from other modules or if you don't like the low level way for some reason. Technically, the method is a bit slower as it involves lookups for the variable name and the serialize/unserialize string conversions. However, since the C language is very fast the difference in speed may not be noticeable.

To set:

moddata_client_set(acptr, "example", "1234"); /* SET */

This will call your unserialize function to store the actual data.

To get:

char *value = moddata_client_get(acptr, "example"); /* GET */

This will call your serialize function to acquire a serialized representation of the data. Note that value may be NULL at this point if there was no value set for "example".

See also

  • src/modules/certfp.c: certificate fingerprint, using the indirect method, a client object with a simple string
  • src/modules/jointhrottle.c: using the direct method, a client object with a struct
  • src/modules/m_mdex.c: example module to get/set, not compiled by default