Dev:Hook API

What are hooks?
When you write an UnrealIRCd module you usually want your code to be called after a specific event has been triggered, such as a new user connecting, a user joining a channel, etc. The Hook API allows a module to "hook" into UnrealIRCd's core and modules. It basically informs UnrealIRCd that your function wants be called when XYZ happens.

UnrealIRCd has more than 80 of these hooks, so chances are high that we have one you are looking for.

How hooks are called
Here's some little explanation of how hooks are called "by us" from the core and our own modules. It may not interest you if you just want to "hook in" (for that, skip this and see next section):

When we want to run a hook we have a few functions available. The most basic ones are RunHook/RunHookX: RunHook(HOOKTYPE_XYZ); RunHook1(HOOKTYPE_XYZ, arg1); RunHook2(HOOKTYPE_XYZ, arg1, arg2); RunHook3(HOOKTYPE_XYZ, arg1, arg2, arg3); etc..

Sometimes modules want to have the option to 'stop processing', for this we have: RunHookReturnInt(hooktype,x,retchk); etc... (^^ improve)

How to have your module "hook in"
If you found the hook you are looking for (see next section) then you should use our HookAddXXX functions to make it so your function will be called by the hook system. Your HookAddXXX function generally goes in MOD_INIT or MOD_LOAD, with the exception of two or three hooks which go in MOD_TEST.

We have different HookAddXX functions, the ones you need depend on the return type of the hook.

For hooks returning integers (the majority): HookAddEx(module, hooktype, func)

For hooks returning nothing (void): HookAddVoidEx(module, hooktype, func)

For hooks returning a string (char *): HookAddPCharEx(module, hooktype, func)

Example usage: DLLFUNC int MOD_INIT(mymod)(ModuleInfo *modinfo) {   HookAddEx(modinfo->handle, HOOKTYPE_LOCAL_CONNECT, mymod_connect); HookAddPCharEx(modinfo->handle, HOOKTYPE_USERMSG, mymod_usermsg);

HOOKTYPE_CONFIGTEST
This hook gets called when the configuration file 'test' runs. If you add your own configuration block or variable then you need this.

Hook: HookAddEx(modinfo->handle, HOOKTYPE_CONFIGTEST, mymod_config_test);

NOTE: This is one of the only 3 hooks that must go in MOD_TEST (and not in MOD_INIT/MOD_LOAD).

Hook function + example (hmm, this should probably go in it's own wiki page..): DLLFUNC int mymod_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs) { int errors = 0; ConfigEntry *cep, *cep2;

if (type != CONFIG_SET) return 0;

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

for (cep = ce->ce_entries; cep; cep = cep->ce_next) {       if (!cep->ce_varname) {           config_error("%s:%i: blank set::mymod item",                cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; continue; }       else if (!strcmp(cep->ce_varname, "option1")) {           /* set::mymod::option1 */ // do some useful check here... option1_specified = 1; }       else if (!strcmp(cep->ce_varname, "option2")) {           /* set::mymod::option2 */ // do some useful check here... option2_specified = 1; }       else { config_error("%s:%i: unknown directive set::mymod::%s",               cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname); 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 */

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

How to hook (this particular one must go in MOD_TEST): HookAddEx(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, mymod_config_posttest);

Function & example code: DLLFUNC 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++; }

HOOKTYPE_CONFIGRUN
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 hook this one from MOD_INIT: HookAddEx(modinfo->handle, HOOKTYPE_CONFIGRUN, mymod_config_run);

Function syntax & continueing with the example from HOOKTYPE_CONFIGTEST and HOOKTYPE_CONFIGPOSTTEST: DLLFUNC int mymod_config_run(ConfigFile *cf, ConfigEntry *ce, int type) { ConfigEntry *cep;

if (type != CONFIG_SET) return 0;

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

for (cep = ce->ce_entries; cep; cep = cep->ce_next) {       if (!strcmp(cep->ce_varname, "option1")) option1 = config_checkval(cep->ce_vardata, CFG_YESNO); else if (!strcmp(cep->ce_varname, "option2")) option2 = atoi(cep->ce_vardata); }   return 1; // we handled it }

HOOKTYPE_LOCAL_CONNECT
Called when a user connects to OUR server. When this hook is called the user is fully "registered" in IRC terms, that means "USER" and "NICK" have been received and the user is now "online".

Note that if you want to take some sort of reject/kill action on the user (eg: for access control) then you must use HOOKTYPE_PRE_LOCAL_CONNECT instead.

Add the hook from MOD_LOAD: HookAddEx(modinfo->handle, HOOKTYPE_LOCAL_CONNECT, mymod_connect);

Function syntax and example: DLLFUNC int sqlmod_connect(aClient *sptr) {   sendnotice(sptr, "Hi %s, welcome on-board!!", sptr->name); }

HOOKTYPE_PRE_LOCAL_CONNECT
Called after "USER" and "NICK" but before the user is fully registered / online. In contrast to HOOKTYPE_LOCAL_CONNECT this function allows you to reject/kill the user.

This function is normally only used if your module provides some sort of access control functionality. If you just want to take some sort of other action, like send a message or log something, then use HOOKTYPE_LOCAL_CONNECT instead.

Add the hook from MOD_LOAD: HookAddEx(modinfo->handle, HOOKTYPE_PRE_LOCAL_CONNECT, mymod_preconnect);

Function syntax and example: DLLFUNC int sqlmod_preconnect(aClient *sptr) {   if (!strcmp(sptr->name, "lazyuser")) return exit_client(sptr, sptr, sptr, "We do not allow lazy people on this server, sorry");

return 0; }

HOOKTYPE_REMOTE_CONNECT
Called when a user connects to a remote server.

HOOKTYPE_LOCAL_QUIT
Called when a user gets disconnected, either via /QUIT or any other reason including but not limited to: ping timeout, being killed, network errors, etc.

If your module manages it's own per-user struct then this function allows you to free it from here (you may also be interested in HOOKTYPE_UNKUSER_QUIT then). Naturally it can also be used for logging or any other action you think is useful on user disconnection.

Hook from MOD_LOAD: HookAddEx(modinfo->handle, HOOKTYPE_LOCAL_QUIT, mymod_quit);

Function syntax & example: DLLFUNC int mymod_quit(aClient *sptr, char *comment) {   SomeDynamicInfo *e = find_some_dynamic_info(sptr); if (e) free_some_dynamic_info_and_remove_from_list(e);

return 0; }

HOOKTYPE_PRE_LOCAL_QUIT
Similar to HOOKTYPE_LOCAL_QUIT but this one is called a little earlier and is only meant when you want to change the quit reason. An example use case would be a censorship module.

Hook from MOD_LOAD: HookAddPCharEx(modinfo->handle, HOOKTYPE_PRE_LOCAL_QUIT, mymod_prequit);

Function syntax & example: DLLFUNC char *mymod_prequit(aClient *acptr, char *comment) {   if (strstr(comment, "shit")) return NULL; // we could return a string here (which will then be used), or we can return NULL which means: no quit reason

return comment; }

HOOKTYPE_UNKUSER_QUIT
Called when an "unknown" user disconnects. Unknown here means: any client who is not online yet. This hook is for example triggered if you connect to the IRC port and then disconnect without sending any USER or NICK. It could be used for logging or to deallocate any custom made structures if necessary (not too common and HOOKTYPE_FREE_CLIENT is arguably more suitable for that).

The syntax and usage is exactly the same as HOOKTYPE_LOCAL_QUIT. Note, though, that sptr->name may or may not be filled in (as NICK may or may not have been received). Additionally, even if sptr->name contains a nickname it has not yet been fully "approved" and since it's not really online the other servers know nothing about this user.

HOOKTYPE_REMOTE_QUIT
Called when a user disconnects or gets killed on a remote server.

HOOKTYPE_PRE_LOCAL_JOIN
This function allows you, unlike HOOKTYPE_LOCAL_JOIN, to "reject" a local join. Useful if your module provides some sort of channel access restriction.

HOOKTYPE_PRE_LOCAL_KICK
This function allows you, unlike HOOKTYPE_LOCAL_KICK, to "reject" a KICK request from a local client. For example if your module creates some special class of users that may not be kicked.