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:Channel Mode API
Introduction
Using the "Extended Channel Modes" API, you can create your own channel modes. We support 2 types: with parameter (eg: +X 123
), and without parameter - paramless (eg: +X
). The current API does not allow you to create list modes or prefixes. If you want to have some list mode then you can often use the Dev:Extended Bans API instead.
The API is relatively simple for modes without a parameter (paramless). It is more complex for channel modes with parameters. But we still do our best to make it easy.
The channel mode API only provides setting/unsetting the channel mode. To make your channel mode actually do something useful you need to add Hooks or other objects.
Example
We highly recommend to read the #Creating a channel mode section, BUT it may also be a good idea to have the source of an actual channel mode on your (other) screen while reading this manual.
All UnrealIRCd channel modes that use the extended channel modes API are in src/modules/chanmodes. Good examples are:
- For a simple paramless channel mode, see src/modules/chanmodes/regonly.c
- Channel modes with parameters are also in src/modules/chanmodes: such as history.c, link.c and floodprot.c. Note, however, that these are quite complex!
Creating a channel mode
The first step, in MOD_INIT
, is to declare the CmodeInfo
struct and its members. Then call the CmodeAdd()
function like this:
Cmode_t EXTCMODE_DUMMY = 0L; MOD_INIT() { CmodeInfo cmodereq; memset(&cmodereq, 0, sizeof(cmodereq)); cmodereq.letter = 'X'; cmodereq.paracount = 0; /* for the is_ok function, choose 1 of 2 below: */ cmodereq.is_ok = extcmode_default_requirechop; /* for paramless modes that simply require +o/+a/+q */ // cmodereq.is_ok = modeX_is_ok; /* or.. your own function */ CmodeAdd(modinfo->handle, cmodereq, &EXTCMODE_DUMMY);
So basically you pass: 1) the module handle, 2) a CmodeInfo struct, and 3) a pointer to a long int that will be set to the integer value of the channel mode.
The CmodeInfo struct contains two regular variables which you may set:
- letter specifies the channel mode letter.
- local specifies if the channel mode is local (i.e. remote servers will never know this channel mode is set or unset). This is rarely used.
Other than that, the CmodeInfo struct contains many function pointers. In the example of above we only set the is_ok function. There are 3 categories of functions:
- Required functions for both paramless and parameter modes
- Optional functions
- Required functions for parameter modes
They are explained below:
Required functions for both paramless and parameter modes
is_ok
Access and parameter checking.
SHORTCUT: If you are writing a paramless channel mode and you simply want to require +o/+a/+q for setting and unsetting the mode, then you can save yourself from writing your own is_ok function. In that case simply use extcmode_default_requirechop as commented out in the example in previous section.
Declaration of the is_ok function:
int (*is_ok)(Client *client, Channel *channel, char mode, const char *para, int type, int what);
Variable | Type | Description |
---|---|---|
client | Client * | Client who issues the MODE change |
channel | Channel * | Channel to which the MODE change applies |
mode | char | The mode character (eg: 'X'). Yes, your function is only called for your own modes but this may come in handy. |
para | const char * | Parameter to the channel mode (will be NULL for paramless modes) |
type | int | Check type, one of EXCHK_*. Explained later. |
what | int | This is set to MODE_ADD (the mode is added) or MODE_DEL (the mode is removed) |
The is_ok function is called for a number of cases:
- If type is EXCHK_ACCESS then the function is called to check if the user may set/unset the mode. You must only check access and NOT send any error message(s). If access is granted you should return EX_ALLOW. If you want to disallow a set/unset then return EX_DENY. Normally IRC Operators with override capabilities may still override a deny, if you also want to prevent IRCOp's from (un)setting then you can return EX_ALWAYS_DENY.
- When type is EXCHK_ACCESS_ERR it means your function is called (again) to check access but this time you may send an error message to the user (in fact, you are expected to), indicating why setting or unsetting is not permitted.
- Finally, when type is EXCHK_PARAM then you are supposed to check the correctness of the parameter being passed. If it's not good (eg: you expect a value between 1 and 20 and you recieve 'lalala' or '30') then you return EX_DENY. Otherwise you return EX_ALLOW. You should also send an error message to the user telling them what's wrong.
Example:
int modeX_is_ok(Client *client, Channel *channel, char mode, char *para, int type, int what) { if ((type == EXCHK_ACCESS) || (type == EXCHK_ACCESS_ERR)) { if (!is_chan_op(client, channel)) { if (type == EXCHK_ACCESS_ERR) sendnumeric(client, ERR_CHANOPRIVSNEEDED, channel->chname); return EX_DENY; } return EX_ALLOW; } else if (type == EXCHK_PARAM) { int v = atoi(para); if ((v < 1) || (v > 20)) { sendnotice(client, "ChanMode +X: ERROR: Expect a value between 1 and 20"); return EX_DENY; } return EX_ALLOW; } return EX_ALLOW; /* Falltrough... normally never reached */ }
Optional functions
conv_param
Convert input parameter to 'correct' parameter. For example +X a123 could be converted to +X 123.
Generally is_ok should be used for checking and simply reject invalid user input there. However in some cases you may want to transform user input. That's what this function is for: transform the string if necessary. You SHOULD NOT send any errors here.
If you don't need this, then simply don't register the function.
Declaration:
const char * (*conv_param)(const char *para, Client *client);
Variable | Type | Description |
---|---|---|
para | const char * | Parameter to the channel mode |
client | Client * | Client who issued the MODE change NOTE: This can be NULL, for example when setting mode via configuration file default channel modes. |
Example:
/* Instead of rejecting <1 and >20 values in +X <value>, the author of this module chose to transform these to 1 and 20 respectively. */ const char *modeX_conv_param(char *param, Client *client) { static char convbuf[32]; int t; t = atoi(param); if (t < 1) t=1; if (t > 20) t=20; ircsnprintf(convbuf, sizeof(convbuf), "%d", t); return convbuf; }
Parameter modes
Parameter modes are more complex because they need to store and retrieve data, duplicate and free data and have to deal with server synchronization.
These parameters (settings) are stored in a struct you define yourself.
Example:
/* Example of mode +X module that accepts a number */ /* This is how you should declare your own custom struct. You normally put this quite high in the source, before all your MOD_TEST / MOD_INIT / etc functions */ typedef struct { int t; // change this or add more variables, whatever suits you. } aModeX;
In addition to an is_ok function described earlier, ALL the functions in the next few sections are required!
put_param
Store a parameter (or more accurately, a setting) in memory for the channel.
Declaration:
void *put_param(void *data, const char *para);
Variable | Type | Description |
---|---|---|
data | void * | Your channel mode setting |
para | const char * | Parameter being set |
Regarding the data parameter:
- If the parameter data is NULL, then you need to allocate your stuct. This happens if the mode is set on the channel and it previously was not set.
- If data is not NULL, then use the existing struct. This happens if the mode was already set on the channel (eg: +X 5) but was changed to a new value (eg: +X 10).
Here is an example, which makes it easy to understand:
void *modeX_put_param(void *Xpara, const char *para) { aModeX *r = (aModeX *)Xpara; /* Allocate a structure if it does not already exist */ if (!r) r = safe_alloc(sizeof(aModeX)); /* Now, set the value (new or overwrite existing) */ r->t = atoi(para); return (void *)r; }
get_param
Retrieve a parameter/setting of the channel from memory. Or, in other words: convert a setting from your struct to a readable string.
Declaration:
const char *get_param(void *data);
Variable | Type | Description |
---|---|---|
data | void * | Your channel mode setting |
Example:
/* Example of mode +X module that accepts a number */ /* This is how you should declare your own custom struct. You normally put this quite high in the source, before all your MOD_TEST / MOD_INIT / etc functions */ typedef struct { int t; // change this or add more variables, whatever suits you. } aModeX; const char *modeX_get_param(void *Xpara) { aModeX *r = (aModeX *)Xpara; static char buf[32]; if (!r) return NULL; ircsnprintf(buf, sizeof(buf), "%d", r->t); return buf; }
free_param
Free parameter/setting from memory.
Declaration:
void free_param(void *data);
Variable | Type | Description |
---|---|---|
data | void * | Your channel mode setting |
Example:
void modeX_free_param(void *para) { safe_free(para); }
dup_struct
Duplicate your param/settings struct. Yes, this function is mandatory for parameter modes. It is only called from a few places but it is required.
Declaration:
void *dup_struct(void *data);
Variable | Type | Description |
---|---|---|
data | void * | Your struct in which the setting is stored |
Example:
typedef struct { int t; // change this or add more variables, whatever suits you. } aModeX; [..] void *modeX_dup_struct(void *src) { aModeX *dst = safe_alloc(sizeof(aModeX)); memcpy(dst, src, sizeof(aModeX)); dst->next = dst->prev = NULL; /* paranoid */ return (void *)dst; }
sjoin_check
When a server links in and all channels are synchronized, their timestamp is compared. If one of them has a lower creation timestamp then their setting is preserved. However it is possible for both sides to have the same creation timestamp, in fact this is quite normal if users were connected on both sides and the servers split and then later on reconnect. In that case the channels from both sides are considered equal but we still have to end up with one single sets of mode. So if one side has +X 100 set and the other sides has +X 200 then we have to agree on a value here, either 100 or 200. In other words, we have to assign a 'winner'. This function decides that.
Note: In general, as a principle throughout UnrealIRCd we use 'highest value wins'. So if one side has +l 5 and the other has +l 20 then the winner will be +l 20.
Declaration:
int sjoin_check(Channel *channel, void *our, void *their);
Variable | Type | Description |
---|---|---|
channel | Channel * | The channel. Note: this can be NULL. It is normally not used as you use our and their. |
our | void * | Our channel setting |
their | void * | Their channel setting |
You must return a value of EXSJ_*:
- EXSJ_SAME: Paramaters are the same
- EXSJ_WEWON: We won, our setting will be used on both sides
- EXSJ_THEYWON: We lost, their setting will be used on our side as well
- EXSJ_MERGE: There's no winner/loser, we have 'merged' the differences and came up with a value different than both. We have changed the settings in our, UnrealIRCd should use these settings on both this side and theirs. Actually EXSJ_MERGE is pretty rare, it's currently only used by channel mode +f.
Example:
/* aModeX is our custom struct (see previous examples) */ int modeX_sjoin_check(Channel *channel, aModeX *our, aModeX *their) { if (our->t > their->t) return EXSJ_WEWON; else if (their->t > our->t) return EXSJ_THEYWON; else return EXSJ_SAME; }
More information
UnrealIRCd ships with quite a lot of channel modes that you can use for examples and inspiration, they are all in src/modules/chanmodes/
And of course, you can have a look at third party modules, some of which are channel mode modules, for some examples too.