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:Hook API
This article explains what "hooks" are and how they can help a module programmer to get notified on certain "events" such as joins and connects. Then we dive into some more background information on different types of hooks and priorities. Ultimately, we have a walkthrough from finding a hook to actually using it.
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 100 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 is RunHook
:
RunHook(HOOKTYPE_XYZ, arg1, arg2);
Example:
RunHook(HOOKTYPE_LOCAL_CHANMODE, client, channel, mtags, modebuf, parabuf, sendts, samode, &should_destroy);
Sometimes modules want to have the option to 'stop processing', for this we have RunHookReturnInt
:
RunHookReturnInt(hooktype, retchk, arg1, arg2);
The retchk defines when to stop processing, example:
RunHookReturn(HOOKTYPE_SASL_RESULT, !=0, target, 0);
How to have your module "hook in"
If you found the hook you are looking for (explained further down including with examples) then you should use our HookAddXXX functions to make it so your function will be called by the hook system. Your HookAddXXX function goes in MOD_INIT, 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):
HookAdd(module, hooktype, priority, func)
For hooks returning nothing (void):
HookAddVoid(module, hooktype, priority, func)
For hooks returning a string (char *):
HookAddPChar(module, hooktype, priority, func)
Example usage:
int MOD_INIT() { HookAdd(modinfo->handle, HOOKTYPE_LOCAL_CONNECT, 0, mymod_connect); HookAddPChar(modinfo->handle, HOOKTYPE_PRE_USERMSG, 0, mymod_usermsg);
A note on priority
You may have noticed the priority argument, which we set to 0 in the above example. You can use 0 if it doesn't matter to you if your hook is called before or after hooks from other modules. Use a negative value such as -100 to be called BEFORE the rest, or a positive value such as 100 to be at the end of the hook callback list.
What is NOT safe to do in a hook
It is not safe to kill the user in a hook. That is, if your hook is called for client 'client' then do not use exit_client(client....). Instead you should use dead_link(client, "reason here").
The reason for this is that the caller of the hook does not expect the user object to disappear.
The only exception to this rule is the PRE_LOCAL_CONNECT hook. You can safely return exit_client() there.
Available hook types
The hooktypes and function prototypes are documented in the Hook API in Doxygen. This is auto-generated documentation based on the source code.
Just CTRL+F in the documentation there to see if you can find the hook you need. See next step for an example
Example session finding and using a hook
Say, we want to get notified when a new user connects and we want to send a silly notice to all IRCOps when that happens.
NOTE: This assumes you already understand the basic skeleton layout of a module. If not, see Dev:Module.
Step 1: find the hook we need
Go to Hook API in Doxygen. We see several hooks that deal with connecting of clients: HOOKTYPE_LOCAL_CONNECT
and HOOKTYPE_REMOTE_CONNECT
. Let's start with the first one, which says on the webpage:
#define HOOKTYPE_LOCAL_CONNECT 2 See hooktype_local_connect()
So we click on the hooktype_local_connect() link there and we see the function prototype:
int hooktype_local_connect (Client * client)
It also shows us more information about this hook, what it does, and what the parameters and return type should be:
Called when a local user connects (function prototype for HOOKTYPE_LOCAL_CONNECT). Parameters client The client Returns The return value is ignored (use return 0)
That sounds like what we need!
Step 2: add the function to our module
We have found the the hook we are looking for. We found the "function prototype" and we can use it in our module.
Let's say our module is called mymod. Then near the top of the module, after the #include unrealircd.h
and the MOD_HEADER
block, we add a forward declaration of the function prototype:
int mymod_connect(Client *client);
And then at the bottom of the module we add the actual function:
int mymod_connect(Client *client) { sendto_ops("Hey we got a new connect: %s!!!", client->name); return 0; }
As you can see, the function definition and parameters of Step 2 matches the one we have found in Step 1. Only the name is different, we call it mymod_connect
, you are free to use any name here. In the function we return the value we looked up in Step 1, it told us to use return 0
here.
Now, before this all works, you still need to do step 3...
Step 3: make UnrealIRCd use this hook
We must tell UnrealIRCd to actually use this hook. So you go into your module, the MOD_INIT() section and add an HookAdd:
MOD_INIT() { HookAdd(modinfo->handle, HOOKTYPE_LOCAL_CONNECT, 0, mymod_connect); return 0; }
Most likely you already have a MOD_INIT() and it already contains code, so it is just a matter of adding that single HookAdd line there.
Step 4: compile and test
Now you can compile the module. So make sure your .c file is put in src/modules/third
and then run make install
from your main unrealircd-x.y.z directory.
Add your module to the unrealircd.conf like loadmodule "third/mymod";
and /REHASH.
To see the module in action you must be oper online and then when a new user locally connects you should see the notice.
Step 5: local and remote
Some hooks have a "REMOTE" and "LOCAL" function, often the protypes are very similar. In our example we added a hook callback for HOOKTYPE_LOCAL_CONNECT
but there also exists a HOOKTYPE_REMOTE_CONNECT
. Again, let's look at the doxygen docs and click on hooktype_remote_connect(). It tells us:
int hooktype_remote_connect( Client *client ) Called when a remote user connects (function prototype for HOOKTYPE_REMOTE_CONNECT). Parameters client The client Returns The return value is ignored (use return 0)
As you can see, the function prototype is exactly the same as for hooktype_local_connect: both functions have 1 parameter Client *client
and they both tell you to use return 0
.
So, to get notified of remote connects, we don't have to create a new function, but we can use our existing function. We simply add 1 more line in MOD_INIT():
HookAdd(modinfo->handle, HOOKTYPE_REMOTE_CONNECT, 0, mymod_connect);
Save your .c file. Run 'make install' again and REHASH the IRCd. Now your module will get notified of remote connects as well.
You can use this same trick for about ~10 hooks to get both remote and local notification via the same module.
Step 6: more hooks
If you need to get notified about a different event, for example a user exiting or a nick change or anything, then simply repeat steps 1, 2, 3 and 4 for every hook that you want to be notified of.