6. Adding to and modifying Services
6-1. Writing modules
6-1-1. Overview
6-1-2. Module loading and
unloading
6-1-3. Inter-module dependencies
6-1-4. Callbacks
6-1-5. Using external functions
and variables
6-1-6. Special types of modules
6-2. Callbacks provided by Services and standard modules
6-2-1. Services core callbacks
6-2-2. OperServ callbacks
6-2-3. NickServ callbacks
6-2-4. ChanServ callbacks
6-2-5. MemoServ callbacks
6-2-6. StatServ callbacks
6-2-7. HTTP server callbacks
6-3. Submitting additions or changes to Services
6-4. Coding guidelines for Services
Table of Contents
6-1. Writing modules
IRC Services 5.0 introduces a new "module" system allowing features
to be added to and removed from Services at will with no changes
to the core Services code.
6-1-1. Overview
Services modules are based on, and in fact use the same system
as, standard dynamic-link libraries. Upon startup
, Services will load a given module into memory and execute its
initialization function. This function should set up any data structures
necessary and register all callback functions needed (see below).
At shutdown
, its cleanup function will be called, which should free all resources
in use and perform other necessary cleanup functions, or return
an error if the module cannot unload itself for some reason.
Module interaction with Services takes place primarily through
the use of callbacks. These are functions which the module registers
with Services to be called when a particular event occurs, such
as a message arriving from the remote server or a certain amount
of time passing. Typically, a module will register callbacks for
events it is interested in in its initialization routine, and remove
them when the module is unloaded.
Modules are manipulated using the functions provided by modules.c
in the Services core. They are as followed:
- Module *load_module(const
char *modulename)
-
Loads the given module and returns a pointer to a data structure
(Module *) for the module. The contents of this data
structure are private, and the pointer should only be considered
as a "handle" that indicates a particular module. If the module
could not be loaded, returns NULL. See section
6-1-2 below for details on the module loading process.
- int unload_module(Module
*module)
-
Unloads the given module. Returns nonzero if the module was
successfully unloaded, zero if not. See section
6-1-2 below for details on the module unloading process.
- Module *find_module(const
char *modulename)
-
Returns a handle for the module whose name is given by "modulename".
If no such module has been loaded, returns NULL.
- void use_module(Module
*module)
-
Increments the "use count" for the given module. A module whose
use count is nonzero is not permitted to be unloaded. This function
is typically called by modules which depend on other modules
to function properly; see section 6-1-3 for
details on inter-module dependencies.
- void unuse_module(Module
*module)
-
The opposite of use_module(): decrements the "use
count" for the given module.
- void *get_module_symbol(Module
*module, const char *symname)
-
Looks up the value of the symbol "symname" in the
given module, and returns its value; if the symbol cannot be
found, returns NULL. If the symbol is not defined in
the given module but is defined by another module, the return
value of this function is undefined. Note that the "value" of
a variable as returned by this function is the address
of the variable, not the value it actually contains; thus, if
a module declares
int intvar;
then code similar to the following should be used to access
it:
int *p_intvar = get_module_symbol(mod, "intvar");
if (p_intvar)
printf("Value of `intvar' is %d\n",
*p_intvar);
else
printf("Unable to resolve symbol `intvar'\n");
- const char *get_module_name(Module
*module)
-
Returns the name of the given module. This is usully the path
used to load the module (passed to load_module()),
but may be different if the module defines a module_name
symbol. If NULL is passed for the "module"
parameter, returns the string "core".
- MODULE_NAME
-
This preprocessor macro is a shortcut for get_module_name(module)
(where `module' is a variable pointing to the current module's
handle). It can be used just like any other string constant
or macro, except that it cannot be inserted into literal string
constants; in other words, "Module name: " MODULE_NAME
will not compile.
- int register_callback(Module
*module, const char *name)
-
Registers a new callback list. The calling module should pass
its own module information structure (as passed to init_module()
or obtained from find_module()). Returns an ID value, which
is a non-negative integer.
- int call_callback(Module
*module, int id)
int call_callback_1(Module *module, int id, void *arg1)
int call_callback_2(Module *module, int id, void *arg1, void *arg2)
int call_callback_3(Module *module, int id, void *arg1, void *arg2,
void *arg3)
int call_callback_4(Module *module, int id, void *arg1, void *arg2,
void *arg3, void *arg4)
int call_callback_5(Module *module, int id, void *arg1, void *arg2,
void *arg3, void *arg4, void *arg5)
-
Calls each function in the given callback list, optionally
passing additional arguments to the callback function. If a
function in the list returns a nonzero value, that value is
returned immediately to the caller without calling any additional
callback functions. If all callback functions return zero (or
there are no functions to call), returns 0; returns -1 on error
(the given callback ID does not exist for the given module).
- int unregister_callback(Module
*module, int id)
-
Deletes a callback list. Returns nonzero on success, zero on
failure (given callback does not exist).
- int add_callback_pri(Module
*module, const char *name, callback_t callback, int priority)
-
Adds the given callback function to the given list with the
given priority (a higher priority value means the function is
called sooner). The priority must be between CBPRI_MIN and CBPRI_MAX
inclusive. Returns nonzero on success, zero on failure (given
callback does not exist or priority out of range). An error
will not be returned if the same function is registered more
than once, but this behavior is not supported and may change
in the future.
- int add_callback(Module
*module, const char *name, callback_t callback)
-
Adds the given callback function to the given list with priority
zero. This is a shortcut for add_callback_pri(module,name,callback,0),
and the two forms are exactly equivalent.
- int remove_callback(Module
*module, const char *name, callback_t callback)
-
Removes the given callback function from the given list. Returns
nonzero on success, zero on failure (given callback does not
exist, given function is not on callback list).
6-1-2. Module loading and unloading
Modules are loaded through the load_module()
function in modules.c. load_module() takes one
parameter, the name of the module to load, and performs the following
steps (assuming dynamically-compiled modules on a system with the
dlXXX() family of functions):
-
Calls dlopen() for the module (the filename is the
module name passed to load_module() with ".so"
appended). If dlopen() fails, load_module()
returns NULL.
-
Creates a module information structure (Module *)
for the module and initializes it.
-
Checks the module's version code against the version code of
the Services core; if the two do not match or a version number
is not present in the module, load_module() frees the
module information structure, calls dlclose() on the
module and returns NULL. The version code is declared
as
int32 module_version = MODULE_VERSION_CODE;
and must be exported by every module.
-
Parses the section of the modules.conf file for the
module (if any), using the configuration directives in module_config[].
module_config[] must be defined as an array of ConfigDirective
elements, terminated by an element with name == NULL.
If any errors occur during parsing, load_module() frees
the module information structure, calls dlclose() on
the module and returns NULL.
-
Calls the module's init_module() function, passing
the allocated module information structure. This function should
return nonzero on success and zero on failure. If the function
returns failure, load_module() frees the module information
structure, calls dlclose() on the module and returns
NULL.
-
Calls the load module
callback, passing the module pointer to each function in the
callback list.
-
Links the module information structure into the global module
list and returns it.
The unload_module()
function takes a pointer to the module structure (handle) for the
module to unload, and performs the following steps:
-
Calls the module's exit_module() function. This function
takes a single parameter, `int shutdown', which is
0 for a regular module unload and 1 if the program is shutting
down. The function should return nonzero on success and zero
on failure; if it returns failure, unload_module()
returns zero (failure).
-
Calls the unload module
callback, passing the module pointer to each function in the
callback list.
-
Calls dlclose() for the module.
-
Unlinks the module information structure from the global list
and returns nonzero (success).
6-1-3. Inter-module dependencies
Some modules may require functions from other modules to work correctly;
for example, a module adding a new command to NickServ would obviously
require NickServ itself to be loaded first. Such modules can use
the find_module() function
from modules.c to check whether the module has been loaded.
An example might look like this:
int init_module(Module *me)
{
if (!find_module("nickserv/main"))
return 0;
...
return 1;
}
Modules should not attempt to load other modules themselves,
as the administrator may have deliberately chosen not to include
such modules. Instead, the module should fail to load, optionally
writing a log message explaining the problem.
6-1-4. Callbacks
modules.c provides an interface for registering and calling
callback functions. Callbacks are referred to by a combination of
module and callback name or ID (callbacks in the Services core use
a module pointer of NULL).
The type of a callback function is defined by callback_t:
int (*)(...). The parameters passed to the function depend
on the particular callback. If a callback function desires to terminate
processing of the callback list, it should return a nonzero value;
returning zero will allow the next function in the list to be called.
Any pointer parameters should be considered "const" (unmodifiable)
unless the function processes the event and returns nonzero.
To add a callback function to a callback list registered by another
module (or a callback list defined by the Services core), use the
add_callback() function
(or add_callback_pri()
if you want to use a priority other than zero; priorities are used
to determine the order in which the functions are called, as described
below). To remove a function previously added to a callback list,
use the remove_callback()
function. It is legal to attempt to remove a callback function which
was not added in the first place; remove_callback() will
return failure in this case.
To create your own callback list, use the register_callback()
function; to delete it, call the unregister_callback()
function, passing in the callback ID returned from register_callback().
To call a list of callback functions, use the call_callback_X()
set of functions, where X is the number of parameters
passed to each callback function (the _X is omitted
if no parameters are passed). This syntax is used to help module
creators double-check that the right number of parameters are being
passed to callback functions, since such functions cannot be checked
against prototypes.
Callback functions are called in descending order of priority (so,
for example, a priority-1 function is called before a priority-0
one); functions with the same priority are called in the same order
they were added to the callback list.
See section 6-2 below for a list of available
callbacks.
6-1-5. Using external functions and variables
Modules can access functions and variables defined in the Services
core or other modules just as in a normal program, by #include'ing
the appropriate header file (or explicitly declaring the symbols
"extern", though using a header file is preferable because
it ensures the symbols are declared the same way in all source files).
The symbol references will be resolved to addresses when the program
is loaded.
However, since an undefined symbol will cause the module to fail
to load even before init_module() is called, modules should
avoid directly referencing symbols from other modules when possible,
instead using the get_module_symbol()
function to retrieve the symbol's value at runtime. If the symbol
is undefined, the module can generate an appropriate error message
and return failure from init_module() or take other appropriate
action.
On the other hand, since looking up a function at runtime can have
an adverse effect on performance, modules may choose to directly
call certain low-level functions in other modules that are assumed
to be loaded (for example, a module dealing with nicknames might
call first_nickinfo() and next_nickinfo() directly
for improved performance). As mentioned earlier, if the functions
are undefined, the module will fail to load, rendering a check in
init_module() unnecessary.
Another thing to keep in mind, whichever method is used, is that
modules can only reference symbols in modules loaded earlier. If
your module depends on symbols from another, make certain you document
that so that people who use the module put it in the right place
in ircservices.conf.
In certain cases, circular references may arise--a case where module
A depends on a symbol from module B, while module B depends on a
symbol from module A. In such a case, at least one of the modules
must use get_module_symbol() to access the other module's
symbol, and must do it after the module has been loaded (i.e. not
failing in init_module()), so that the other module has
a chance to load as well. This can be accomplished using the load
module callback in the Services core, which allows the
module to take action when subsequent modules are loaded.
Finally, modules which export symbols to be used with other modules
must explicitly declare them to be exported, using the EXPORT_VAR(),
EXPORT_ARRAY(), or EXPORT_FUNC() macros in modules.h;
for example:
EXPORT_VAR(int,variable)
int variable = 123;
EXPORT_ARRAY(char,string)
char string[] = "Hello world";
EXPORT_FUNC(function)
int function(int x) { return x*x+x; }
These macros do not have any actual effect on compilation, but
they are used by a script which prepares the modules for static
linking. Without these declarations, modules will be unable to access
any symbols in the module when compiled statically. Note, however,
that the init_module() and exit_module() functions
and the module_version and module_config variables
are automatically exported, and should not be explicitly listed
this way (in fact, listing them will cause an error when compiling
statically).
6-1-6. Special types of modules
While callbacks are used for most inter-module interactions, to
improve performance, some types of modules are integrated more tightly
with Services. These include protocol modules, database modules,
and mail modules.
Protocol modules
Protocol modules allow Services to communicate with different types
of IRC servers. Services calls several routines in these modules
directly; each protocol module must set the following function pointers
to point to appropriate routines:
- void (*send_nick)(const char *nick, const char *user,
const char *host, const char *server, const char *name, const
char *modes)
-
Sends an appropriate combination of NICK/USER/MODE messages
(or their equivalents) to introduce a new nickname onto the
network.
- void (*send_nickchange)(const char *nick, const char
*newnick)
-
Sends a NICK message to change an existing user's nickname.
- void (*send_namechange)(const char *nick, const char
*newname)
-
Sends a message to change an existing user's "real name", if
possible. (If the protocol does not support it, this can be
a do-nothing routine, but it must be defined.)
- void (*send_server)(void)
-
Sends the initial server registration message (typically a
SERVER message) for Services itself.
- void (*send_server_remote)(const char *server, const
char *reason)
-
Sends a server registration message for a "remote" server (used
for jupes).
- void (*wallops)(const char *source, const char *fmt,
...)
-
Sends a message to "all IRC operators" (the definition of which
may vary between protocols). This is implemented as a WALLOPS
for RFC1459, but as a GLOBOPS for DALnet-compatible servers,
for example. This function takes printf()-style parameters.
- void (*notice_all)(const char *source, const char *fmt,
...)
-
Sends a notice to all users on the IRC network, typically using
NOTICE $* or equivalent (protocols which do not support $* by
itself may require a common domain name to be set in the configuration
file, as the RFC1459 and most other current protocols do). This
function takes printf()-style parameters.
- void (*send_channel_cmd)(const char *source, const char
*fmt, ...)
-
Sends a command which modifies channel status. This is used
where "source" is a nickname introduced by Services
(such as "ChanServ"); some protocols do not allow arbitrary
clients, even those from Services, to change channel commands,
so those protocols would need to change "source" to
"ServerName" or take other measures to get the command
recognized. If possible, the command should be sent on as is,
for example:
va_list args;
va_start(args, fmt);
vsend_cmd(source, fmt, args);
va_end(args);
This command takes printf()-style parameters.
- void (*send_nickchange_remote)(const char *nick, const
char *newnick)
-
Forcibly changes a remote client's nickname. The client's current
nickname is given by "nick", and the nickname to change
to by "newnick". Note: This routine must
not be called if the PF_CHANGENICK flag is not
set in protocol_features (see below).
The two strings "pseudoclient_modes" and "enforcer_modes"
should also be set to the user modes which pseudoclients and enforcers,
respectively, should receive.
Furthermore, the module must set the following four variables (defined
in send.c) to appropriate values:
- const char *protocol_name
-
The name of the protocol (e.g. RFC1459, Dreamforge, etc.)
- const char *protocol_version
-
A string which identifies what versions of the protocol are
supported. If the protocol does not have "versions", the variable
should be set to the empty string ("").
- uint32 protocol_features
-
A bitmask indicating what features (PF_* constants
defined in services.h) are supported by the protocol;
if none of the listed features are supported, the value should
be 0.
- int protocol_nickmax
-
The actual maximum length of a nickname, not including the
trailing null byte used when storing the string internally;
for example, RFC1459-compliant servers would use a value of
9 here. The module should check that NICKMAX (defined
in defs.h) is strictly greater than this value, and
refuse to load if it is less or equal. (Remember that NICKMAX
includes the trailing null byte, so it must be at least 1 byte
greater than protocol_nickmax, which does not include
the trailing null.)
For examples of how protocol modules should be designed, see the
Unreal module (modules/protocol/unreal.c), which is fairly
well documented.
Database modules
Database modules provide the means for Services to store non-volatile
data (data which will be saved between runs), such as the nickname
and channel databases. These modules must provide the functions
to open, read, write, and close databases for each of the major
Services module groups (OperServ, NickServ, ChanServ, MemoServ,
and StatServ).
Details of database modules are still in flux; to be defined later.
Mail modules
Mail modules provide the low-level functionality to actually send
E-mail, and are called by the high-level mail module (mail/main)
when mail is to be sent. When loaded (i.e. in the init_module()
function), mail modules must set the "low_send" symbol
(exported from the mail/main module) to point to a function
with the following prototype:
int low_send(const char *from, const char *fromname, const char
*to,
const char *subject, const char *body)
"from" and "fromname" are the address and name,
respectively, to be used in the From: line; "to"
is the address for the To: line, "subject" is
the text for the Subject: line, and "body" is
the body text. Except for "body", none of the strings will
contain newlines, and the "from" and "to" strings
will be valid E-mail addresses (as determined by valid_email()).
The function should return 0 on success, -1 on failure.
Back to top
6-2. Callbacks provided by Services and standard modules
The callbacks provided by Services are listed below under the names
used to access them (e.g. with add_callback()).
When using Services core callbacks, use a module parameter of NULL.
Note that where the descriptions below refer to "processing" a
message, they refer to a message that is meant specifically for
the callback in question and which is not to be passed on to other
modules or the Services core.
Except as otherwise specified, callbacks which return zero must
leave all arguments unchanged, and callbacks which return nonzero
may modify any arguments which are not specified as const.
6-2-1. Services core callbacks
- channel create
Parameters: Channel *channel, User *user
-
Called when a channel is created (i.e. a user joins a nonexistent
channel). The user that created the channel is also passed to
the callback, but is not yet in the channel's user list. The
callback routine should always return 0.
- channel delete
Parameters: Channel *channel
-
Called when a channel is about to be deleted (i.e. the last
user has left the channel). The callback routine should always
return 0.
- channel JOIN
Parameters: Channel *channel, struct c_userlist *u
-
Called when a user has joined a channel. The callback routine
should always return 0.
- channel JOIN check
Parameters: const char *channel, User *user
-
Called when a user is about to join a channel. The callback
routine should return 1 to refuse the user access to the channel
or 0 to allow the JOIN to proceed normally.
- channel KICK
Parameters: Channel *channel, User *user, const
char *reason
-
Called when a user has been kicked from a channel. The callback
routine should always return 0.
- channel MODE
Parameters: const char *source, Channel *channel,
int modechar, int add, char **av
-
Called for each valid mode character in a MODE message, except
for channel user modes (+o, +v, etc.). "add" is nonzero
if adding modes, zero if removing them. "av" points
to the first argument for the mode; there will always be enough
arguments for the mode character. The callback routine should
return 1 if it processes the mode, 0 otherwise.
- channel mode change
Parameters: Channel *channel
-
Called when a channel's modes have changed. The callback routine
should always return 0.
- channel umode
change
Parameters: Channel *channel, struct c_userlist *user
-
Called when a user's modes on a channel have changed. The callback
routine should always return 0.
- channel PART
Parameters: Channel *channel, User *user, const
char *reason
-
Called when a user parts from a channel. The callback routine
should always return 0.
- channel TOPIC
Parameters: Channel *channel, const char *topic,
const char *setter time_t topic_time
-
Called when a TOPIC command is received. The callback routine
should return 0 to permit the topic change or 1 to refuse it.
- clear channel
Parameters: const char *source, Channel *channel,
int what, const void *param
-
Called whenever the clear_channel() function (in actions.c)
is called, before any other action is taken. The callback routine
should return 1 to terminate clear_channel() processing
or 0 to continue. Note that even if the callback routine returns
1, clear_channel() will still flush out any channel
mode changes, so the routine does not need to do this itself.
See the comments above clear_channel() in actions.c
for the meaning of the parameters.
- command line
Parameters: char *option, char *value
-
Called at program startup for each command-line option. "option"
is the name of the option (the part of the option string after
the initial "-", up to and not including any "="),
and "value" is the text after the "=" (NULL
if no "=" is present). "option" is guaranteed
to be non-NULL. The callback routine should return
0 if it does not recognize the option, 1 if it successfully
processes the option, 2 if there is an error processing the
option, or 3 if no error occurred but Services should terminate
successfully.
- expire
Parameters: none
-
Called whenever the expire timeout (for expiring nicknames,
etc.) runs out, or another action, such as OperServ UPDATE,
explicitly causes expiration to occur. The callback routine
should always return 0.
- introduce_user
Parameters: char *nick
-
Called whenever Services may need to introduce a pseudoclient
(at startup and when receiving a KILL message). The "nick"
parameter is the nickname of the pseudoclient which should be
introduced (if it exists), or NULL when introducing all pseudoclients.
The callback routine should return 1 if nick is not
NULL and the routine introduces a pseudoclient,
0 otherwise. (If nick is NULL, the routine
should introduce any pseudoclients necessary and return 0.)
- load module
Parameters: Module *module, const char *modname
-
Called when a module is loaded, after its init_module()
routine has been called. "modname" is the name of the
module, either specified by the module (char module_name[])
or copied from the module's path. The callback routine should
always return 0. This callback may be used, for example, to
add callback routines to a new module's callbacks when the module
is loaded.
- m_privmsg
Parameters: char *source, char *target, char
*message
-
Called for every PRIVMSG which is not ignored by Services.
The callback routine should return 1 if it processes the message,
0 otherwise.
- m_whois
Parameters: char *source, char *target, char
*target_server
-
Called for every WHOIS command. The callback routine should
return 1 if it processes the message, 0 otherwise.
- receive message
Parameters: char *source, char *cmd, int
ac, char **av
-
Called for every message received from the uplink server. The
callback routine should return 1 if it processes the message,
0 otherwise.
- reconfigure
Parameters: int after_configure
-
Called twice when Services is signalled to re-read its configuration
files. The first call is made before any module configuration
data is changed (but after the main Services configuration settings
have been updated), with after_configure set to 0;
the second call is made after all configuration data has been
successfully read and configuration variables have been updated,
with after_configure set to 1. Callback routines should
always return 0.
- save data
Parameters: none
-
Called whenever the save-data timeout runs out, or another
action, such as OperServ UPDATE, explicitly causes
data saving to occur. The callback routine should always return
0.
- server create
Parameters: Server *server
-
Called whenever a new server joins the network, after the server
record is created. The callback routine should always return
0.
- server delete
Parameters: Server *server, const char *reason
-
Called whenever a server splits from the network, before the
server record is created. The callback routine should always
return 0.
- set topic
Parameters: Channel *c, const char *topic, const
char *setter, time_t t
-
Called twice whenever set_topic() (in actions.c)
is called to set the topic of a channel from Services. The first
call is done before the channel structure is modified, and all
callback routines must return 0 in this case. The second call
is done after the channel structure is modified, but before
c->topic_time is changed; in this call, topic
and setter are both NULL. If the callback
routine returns 1, no further processing is done. This is currently
used to ensure that the topic time is set so that the IRC servers
will acknowledge the change under certain protocols.
- unload module
Parameters: Module *module
-
Called when a module is unloaded, after its exit_module()
routine has been called. The callback routine should always
return 0. Note that it is not necessary to use this callback
to remove callbacks from a module which is being unloaded, since
those callback entries will be removed automatically.
- user check
Parameters: int ac, char **av
-
Called whenever a user joins the network, before the user record
is created. "ac" and "av" are the argument
list to the NICK (or USER) command, as passed to users.c/do_nick().
The callback routine should return 0 to allow the user to connect
or 1 to disallow (in this case the routine should take action
to remove the user from the network, such as sending a KILL
message).
- user create
Parameters: User *user, int ac, char **av
-
Called whenever a user joins the network, after the user record
is created. "ac" and "av" are the argument
list to the NICK (or USER) command, as passed to users.c/do_nick().
The callback routine should always return 0.
- user delete
Parameters: User *user, char *reason, int
is_kill
-
Called whenever a user leaves the network (whether by QUIT
or KILL), before the user record is deleted. "is_kill"
is 1 if the user is quitting because of a KILL, 0 if because
of a QUIT. The callback routine should always return 0.
- user MODE
Parameters: User *user, int modechar, int
add, char **av
-
Called for every mode character given in a user MODE message.
"add" is nonzero when adding modes and zero when subtracting.
"av" points to the first argument for the mode; there
will always be enough arguments for the mode character. The
callback routine should return 1 if it processes the mode, 0
otherwise.
- user nickchange
(after)
Parameters: User *user, const char *oldnick
-
Called whenever a user changes nicks, after the nickname is
actually changed. The callback routine should always return
0.
- user nickchange
(before)
Parameters: User *user, const char *newnick
-
Called whenever a user changes nicks, before the nickname is
actually changed. The callback routine should always return
0.
- user servicestamp
change
Parameters: User *user
-
Called when a user's Services stamp (the servicestamp
field) is altered and the network should be informed of it.
The callback routine should always return 0.
6-2-2. OperServ callbacks
- command
Parameters: User *user, char *command
-
Called for every message (other than a CTCP PING or empty message)
received by OperServ. "command" is the first word in
the message (where words are separated by spaces); the remaining
text can be obtained through repeated calls to strtok()
with a NULL first parameter. The callback routine should
return 1 if it processes the message, 0 otherwise. If the routine
does not process the message, it MUST NOT call strtok().
- expire maskdata
Parameters: uint32 type, MaskData *md
-
Called when a MaskData entry is about to be expired,
just before it is actually deleted. The callback routine should
always return 0.
- HELP
Parameters: User *user, char *param
-
Called when the HELP command is used with a parameter.
The callback should return 1 if it processes the message, 0
otherwise.
- HELP COMMANDS
Parameters: User *user, int which
-
Called when the HELP COMMANDS command is used. The
"which" parameter is 0 when called after the list of
unprivileged (IRC operator) commands, 1 after the Services operator
commands, 2 after the Services administrator commands, and 3
after the Services root commands. The callback routine should
always return 0.
- SET
Parameters: User *u, char *option, char
*param
-
Called when the SET command is used. All parameters
are guaranteed to be valid (non-NULL); "param"
may contain multiple words. The callback routine should return
1 if it processes the given option, 0 otherwise.
- STATS
Parameters: User *user, char *extra
-
Called for every STATS command that is not plain "STATS",
"STATS ALL", or "STATS RESET". "extra"
contains the remainder of the message (after STATS and any following
whitespace). The callback routine should return 1 if it processes
the command, 0 otherwise.
- STATS ALL
Parameters: User *user, const char *s_OperServ
-
Called for every STATS ALL command. "s_OperServ"
is the OperServ pseudoclient nickname; the callback routine
should send messages using this nick. The callback routine should
always return 0.
- cancel_akill
("operserv/akill" module)
Parameters: const char *username, const char *host
-
Called when an autokill is to be removed from the uplink server.
The callback routine should return 1 if it removes the autokill,
0 otherwise.
- send_akill ("operserv/akill"
module)
Parameters: const char *username, const char *host,
time_t expires const char *who, const char
*reason
-
Called when an autokill is to be sent to the uplink server.
The callback routine should return 1 if it sends the autokill,
0 otherwise.
6-2-3. NickServ callbacks
- cancel_user
Parameters: User *u, int old_status, int
old_authstat
-
Called whenever the cancel_user() routine is called
for a user with a registered nick. "old_status" is
the value of the user's nick's "status" field while
the nick was in use (when the callback is called, all temporary
flags will have been cleared); "old_authstat" is likewise
the value before clearing of the nick group's "authstat"
field. The callback routine should always return 0.
- check recognized
Parameters: const User *u
-
Called whenever Services needs to check whether a user is recognized
as being the owner of the nick they are using (e.g. when the
user connects to the network). The User structure is guaranteed
to have valid "ni" and "ngi" pointers. The
callback routine should return:
-
0, to indicate that the user was not recognized;
-
1, to indicate that the user was recognized as the owner
of the nick; or
-
2, to indicate that the user should NOT be recognized as
the owner of the nick, regardless of any subsequent checks.
- collide
Parameters: User *u
-
Called whenever NickServ determines that a user should be "collided".
The callback should return 1 if it handles colliding the user
(for example, by forcibly changing the user's nick), or 0 to
allow normal collide processing to continue.
- command
Parameters: User *user, char *command
-
Called for every message (other than a CTCP PING or empty message)
received by NickServ. See the OperServ
"command" callback for details.
- HELP
Parameters: User *user, char *param
-
Called when the HELP command is used with a parameter.
The callback should return 1 if it processes the messages, 0
otherwise.
- HELP COMMANDS
Parameters: User *user, int which
-
Called when the HELP COMMANDS command is used. The
"which" parameter is 0 when called after the list of
unprivileged commands and 1 after the list of privileged commands
(those shown to IRC operators). The callback routine should
always return 0.
- identified
Parameters: User *u, int old_authstat
-
Called when a user identifies for their nick. "old_authstat"
is the authorization status of the nick before the identify
took place. The callback routine should always return 0.
- IDENTIFY check
Parameters: User *u, char *pass
-
Called when a user attempts to identify for their nick, after
the standard checks (whether the nick exists, etc.) are done
but before the password itself is checked. The callback routine
should return 1 to disallow identification or 0 to allow processing
to continue normally.
- nick delete
Parameters: NickInfo *ni
-
Called when a nick's registration is deleted. The callback
routine should always return 0.
- nickgroup delete
Parameters: NickGroupInfo *ngi, const char *oldnick
-
Called when a nick group is deleted (i.e. when the last nick
for the group is deleted), after the nick
delete callback is called. "oldnick" is the
nickname whose deletion caused this nickgroup to be deleted.
The callback routine should always return 0.
- REGISTER check
Parameters: User *u, char *password, char
*email
-
Called when a user attempts to register their nickname, after
the validity of the parameters has been checked but before checking
whether the nickname exists or the password is strong enough.
"email" may be NULL (if the user did not provide
an E-mail address), but "password" will always be non-NULL.
The callback routine should return 0 to allow the registration
to continue or 1 to disallow it.
- registered
Parameters: User *u, NickInfo *ni, NickGroupInfo
*ngi, int *replied
-
Called when a user registers their nickname, after the data
structures have been set up. The integer pointed to by "replied"
can be set to 1 to disable the standard "your nick has been
registered" reply. The callback need not call put_xxx()
even if it modifies "ni" or "ngi", as the
caller will do so afterwards. The callback routine should always
return 0.
- SET
Parameters: User *u, NickInfo *ni, NickGroupInfo
*ngi, char *option, char *param
-
Called when the SET command is used. All parameters
are guaranteed to be valid (non-NULL), and standard
permission checks will have been performed (so "u"
either is identified for "ni"/"ngi" or is
a Services admin). The callback routine should return 1 if it
processes the given option, 0 otherwise.
- SET EMAIL
Parameters: User *u, NickGroupInfo *ngi
-
Called when the E-mail address associated with a nick group
is set, changed, or unset. "u" is the user making the
change; this may not necessarily be the owner of the nick if,
for example, a Services admin is making the change. The new
address is stored in "ngi->email". The callback routine
should always return 0.
6-2-4. ChanServ callbacks
- check_chan_user_modes
Parameters: const char *source, User *user,
Channel *c, int32 modes
-
Called when checking a user's modes on a channel, before any
standard checks are performed. The mode change is being performed
by "source" (NULL for a channel join) on "user"
on channel "c", and the user's current modes on the
channel are "modes". The callback routine should return
0 to allow normal checking to continue or 1 to abort further
checks.
Note: "source" may be the empty string; this
indicates an internal call to verify a user's current modes.
In this case callback routines should not make any decisions
about modes based on the value of "source".
- check_kick
Parameters: User *user, ChannelInfo *ci, char
**mask_ret, const char **reason_ret
-
Called when checking whether a user should be kicked from a
channel, before any standard checks are performed. The callback
routine may return:
-
0, to allow normal processing to continue.
-
1, to cause the user to be kicked from the channel. In
this case, the callback routine must set "*mask_ret
to a newly smalloc()'d memory area containing the
mask to be banned (create_mask() or sstrdup()
may also be used) and "*reason_ret" to a string
for use as the reason in the kick.
-
2, to abort further processing and allow the user to enter
the channel.
- check_modes
Parameters: Channel *channel, ChannelInfo *ci,
int add, int32 flag
-
Called when checking a channel's modes against its mode lock
for every mode locked either on (add nonzero) or off
(add zero). The callback routine should return 1 if
it processes the mode, 0 if not. If a mode is not processed
by any callback and is further not specially handled by Services
(+k and +l modes), the default action is to simply modify the
channel's current modes to match the mode lock (by calling set_cmode(...,"+X")
or set_cmode(...,"-X") for mode X),
so it is not necessary to add a callback for modes for which
this behavior is sufficient.
- CLEAR
Parameters: User *user, Channel *channel, char
*what
-
Called when a valid CLEAR command is received (i.e.
from a user with permission to use the command on the given
channel). The callback routine should return 1 if it processes
the command, 0 if not.
- command
Parameters: User *user, char *command
-
Called for every message (other than a CTCP PING or empty message)
received by OperServ. See the OperServ
"command" callback for details.
- HELP
Parameters: User *user, char *param
-
Called when the HELP command is used with a parameter.
The callback should return 1 if it processes the message, 0
otherwise.
- HELP COMMANDS
Parameters: User *user, int which
-
Called when the HELP COMMANDS command is used. The
"which" parameter is 0 when called after the list of
unprivileged commands and 1 after the list of privileged commands
(those shown to IRC operators). The callback routine should
always return 0.
- INVITE
Parameters: User *u, Channel *c, ChannelInfo
*ci
-
Called whenever a valid INVITE command is received
(i.e. the user has permission to use INVITE for the
channel). The callback should return 0 to allow normal processing
to continue or 1 to abort further processing.
- SET
Parameters: User *u, ChannelInfo *ci, char
*option, char *param
-
Called when the SET command is used. All parameters
are guaranteed to be valid (non-NULL), and standard
permission checks will have been performed (so `u'
either is identified for "ci" or is a Services admin).
The callback routine should return 1 if it processes the given
option, 0 otherwise.
- SET MLOCK
Parameters: User *u, ChannelInfo *ci, int
modechar, int add, char **av
-
Called for each valid mode character in a SET MLOCK
command. "add" is nonzero if adding modes, zero if
removing them. "av" points to the first argument for
the mode; there will always be enough arguments for the mode
character. The new mode lock (currently being constructed) is
stored in ci->mlock_XXX. The callback routine
should return 1 if it encounters an invalid parameter for a
mode, 0 otherwise.
Also called with "mode" set to zero when all modes
have been processed. In this case, "add" and "av"
are undefined, and the callback routine should return 1 if there
is a problem with the mode lock, 0 otherwise.
- UNBAN
Parameters: User *u, Channel *c, ChannelInfo
*ci
-
Called whenever a valid UNBAN command is received
(i.e. the user has permission to use UNBAN for the
channel). The callback should return 0 to allow normal processing
to continue or 1 to abort further processing.
6-2-5. MemoServ callbacks
- command
Parameters: User *user, char *command
-
Called for every message (other than a CTCP PING or empty message)
received by OperServ. See the OperServ
"command" callback for details.
- HELP
Parameters: User *user, char *param
-
Called when the HELP command is used with a parameter.
The callback should return 1 if it processes the message, 0
otherwise.
- HELP COMMANDS
Parameters: User *user, int which
-
Called when the HELP COMMANDS command is used. The
"which" parameter is 0 when called after the list of
unprivileged commands and 1 after the list of privileged commands
(those shown to IRC operators). The callback routine should
always return 0.
- receive memo
Parameters: int ischan, void *who, const
char *whoname, const User *sender, char *text
-
Called when a user or channel receives a memo. "ischan"
is zero if the recipient is a user, nonzero if a channel, and
"who" is the NickGroupInfo * or ChannelInfo
* for the recipient, respectively; "whoname" is
the name corresponding to "who" (the one given in the
MemoServ SEND command). The callback should return nonzero to
terminate processing or zero to cause the memo to be received
normally.
Callback routines for this callback should use one of the
following two priorities (defined in memoserv.h): MS_RECEIVE_PRI_CHECK
for routines which check whether the memo is allowed to be sent,
or MS_RECEIVE_PRI_DELIVER for actually sending the
memo.
- SET
Parameters: User *u, MemoInfo *mi, char
*option, char *param
-
Called when the SET command is used. All parameters
are guaranteed to be valid (non-NULL), and standard
permission checks will have been performed. "mi" points
to the MemoInfo for the user's nickname. The callback routine
should return 1 if it handles the given option, 0 otherwise.
6-2-6. StatServ callbacks
- command
Parameters: User *user, char *command
-
Called for every message (other than a CTCP PING or empty message)
received by StatServ. See the OperServ
"command" callback for details.
- HELP
Parameters: User *user, char *param
-
Called when the HELP command is used with a parameter.
The callback should return 1 if it processes the message, 0
otherwise.
- HELP COMMANDS
Parameters: User *user, int which
-
Called when the HELP COMMANDS command is used. The
"which" parameter is currently always 0. The callback
routine should always return 0.
6-2-7. HTTP server callbacks
- auth
Parameters: Client *c, int *close_ptr
-
Called for each valid request received by the server. "c"
is the client from which the request was received; the request
data is stored in the client structure (see http.h),
and at a minimum the "method", "url", "version_major",
and "version_minor" fields will be set appropriately.
If the request is denied and the connection should be closed
after the reply is sent, the callback routine should set *close_ptr
to a nonzero value (*close_ptr should not be modified
if the request is not denied). The callback routine should return
HTTP_AUTH_ALLOW to explicitly allow the request to
proceed, HTTP_AUTH_DENY to deny the request (in which
case the module must send an appropriate error message), or
HTTP_AUTH_UNDECIDED to pass the request through to
the next callback; if all authorization callbacks pass the request
through, it is treated as allowed.
- request
Parameters: Client *c, int *close_ptr
-
Called for each valid request received by the server and either
explicitly allowed or passed through by all authorization callbacks.
"c" is the client from which the request was received;
the request data is stored in the client structure (see http.h),
and at a minimum the "method", "url", "version_major",
and "version_minor" fields will be set appropriately.
If the connection should be closed after the reply is sent,
the callback routine should set *close_ptr to a nonzero
value. The callback routine should return 1 if it processes
the request, 0 otherwise.
Back to top
6-3. Submitting additions or changes to Services
If you would like to submit a module you have written for inclusion
in the standard Services distribution, or you have made a modification
to Services itself (or a standard module) that you feel would be
useful to others and would like it included in Services, please
observe the following guidelines:
-
Your code must follow the coding guidelines
given below.
-
Your code should be reasonably well error-checked and robust.
For example, the return value of any function call which can
return an error should be checked, and appropriate action taken
if an error occurs.
-
Your code should compile with no errors or warnings using the
default Services compilation options (gcc -O2 -Wall -Wmissing-prototypes).
-
You should use comments well, but not overuse them; you do
not need to comment every single line of code, but on the other
hand someone looking at your module should not have to decipher
several hundred lines of code just to figure out what it does.
-
All comments and any accompanying documentation should be in
English and use proper grammar, spelling, etc. (Partial sentences
are acceptable for inline comments.)
Any submissions which do not meet the above requirements will be
categorically rejected, although some leeway may be provided in
terms of comment and documentation quality if your native language
is not English.
Furthermore, any submissions of modules, code, documentation, or
any other information (collectively "information") become the sole
property of the author of IRC Services, and by
submitting such information, you agree to transfer any and all rights
you may have in said information to said author. If you do not agree
to the above, or you are not permitted (by law, contract, or otherwise)
to transfer all of your rights in the information to the author(s)
of IRC Services, then you may not submit the information. (If you
cannot comply with this paragraph but would still like to submit
something, please contact the author to discuss your situation.)
Back to top
6-4. Coding guidelines for Services
In general, observe the same style as is used in the rest of Services
and you should have no problems. Specific points follow:
Formatting
-
Lines must not be longer than 79 columns.
-
The basic indentation unit is four columns.
-
Tab characters are eight characters wide. Tabs should be used
in place of sequences of space characters, but this is not required.
-
Only one statement is allowed per line. Statements (including
the empty statement ";") may not be written on the
same line as a control keyword (if, for, or
while).
-
Spaces are placed between binary operators and their operands,
except for the member-reference operators "->" and
"."; spaces are not placed between unary operators
and their operands. However, spaces may be omitted around binary
operators when it would improve readability. Examples:
i = j * 2;
user->channelcount++;
result += (i+59)*60 + j/3600;
-
The construct "!!expression" should not be
used. Use "expression != 0" (for characters
or integers) or "expression != NULL" (for pointers)
instead.
-
Parentheses are required when && and || are
used in the same expression to explicitly indicate order of
evaluation; do not rely on the default order of evaluation.
(Not using parentheses will generate a warning when using gcc
-Wall.)
-
Parentheses following the control statements "if",
"for", and "while", as well as the macros
LIST_FOREACH, LIST_FOREACH_SAFE, and ARRAY_FOREACH,
are preceded by a space. Parentheses following function or macro
calls (other than the above) are not preceded by a space. Examples:
if (flag)
function();
-
Spaces are placed after the comma of each parameter to a function;
however, they may be omitted in function calls which are themselves
parameters to a function, or when including them would make
the line exceed 79 columns in length. Spaces are also placed
after the semicolons in a for statement. Spaces are
not placed after the opening parenthesis or before the closing
parenthesis of a function call or control statement. Examples:
function(param1, param2);
function(param1, strchr(string,'/'), param3);
for (i = 0; i < count; i++) . . .
-
Opening braces of blocks go on the same line as the control
statement (if, for, etc.) associated with
the block; "naked blocks" (those not associated with any control
statement) have the opening brace alone on a line, indented
to the same level as the surrounding code. As an exception,
the opening brace for a function is placed alone on the next
line, without indentation. Closing braces are indented to the
same level as the line containing the opening brace. Examples:
if (flag) {
. . .
}
int function(int param1, char *param2)
{
. . .
}
-
If an if statement is longer than one line, it should
be broken at logical operators (&& or ||);
the operator should be placed at the beginning of the next line,
and should be indented to show which term the operator belongs
with. The closing parenthesis for the if statement
and the subsequent open brace should be alone on a line, aligned
with the first line of the if statement. (Braces are
required in this case.) Conditions for for and while
statements should never span multiple lines, though a for
statement may be broken at the semicolons if necessary. Examples:
if (ok && (strcmp(option, "option-name-1") == 0
||
strcmp(option, "option-name-2") == 0)
) {
. . .
}
if (!init_first()
|| !init_second()
|| !init_third()
|| !init_fourth()
) {
. . .
}
for (exception = first_maskdata(MD_EXCEPTION); exception;
exception = next_maskdata(MD_EXCEPTION)
) {
. . .
}
-
An else followed by an if is considered to
be a single keyword, "else if". The if part
should always be placed on the same line as and immediately
following the else; the else if should never
be split up onto two lines, nor should braces be used around
the if block. The exception to this is when the else
and if refer to two conceptually distinct sets of conditions,
in which case the if block should be enclosed by braces
and indented. Example:
res = check_password( /* ... */ );
if (res == 0) {
. . .
} else if (res == 1) {
/* "else if" on a single line */
. . .
} else {
if (user->ni != NULL) {
/* "if" condition
is different from "else" condition, thus separate */
. . .
}
}
-
Braces are always placed around the body of a control statement
(if, for, etc.). The one exception, aside
from the case of else if mentioned above, is a statement
for which the body, as well as the bodies of any else if
or else statements for an if statement, are
all one statement in length and do not contain any nested control
statements; in this case braces are optional (but recommended
if any of the statements span multiple lines). Examples:
for (i = 0; i < count; i++)
function(i);
while (!done) {
/* Braces required because of the
nested "if" */
if (do_stuff())
quit = 1;
}
if (state == 0) {
a = b;
} else if (state == 1) {
/* Every if/else body gets braces
because this body has two statements */
b += a;
a = 0;
} else {
state = 0;
}
-
Case labels for a switch should be indented half of
a normal indentation unit (two columns) from the line containing
the switch with which they are associated; statements
associated with a case should be indented a full unit from the
line containing the switch (half a unit from the case).
Example:
switch (variable) {
case 123:
. . .
break;
default:
. . .
return -1;
}
-
When a case in a switch block does not contain a break
(or return) statement and deliberately "falls through"
to the next case, a comment to this effect should be made at
the bottom of the case. Example:
switch (state) {
case 0:
. . .
/* fall through */
case 1:
. . .
break;
}
-
Inline comments (comments placed to the right of a line of
code) should be aligned to start on the same column, when this
does not place too much space between the comment and the code
or cause the line to exceed 79 columns in length. Inline comments
after unusually long lines (such as long field declarations
in structures) should be placed alone on the following line,
indented the proper amount.
-
The goto statement should not be used except in error
handling situations where it will help avoid multiple levels
of if nesting or other awkward code. Labels for goto
should be outdented half of an indentation unit from the surrounding
code (i.e., indented two columns less than the surrounding
code).
Identifier naming
-
Global variables and type names should use mixed upper- and
lower-case, with an upper-case letter at the beginning of each
distinct word in the name.
-
Preprocessor constants and macros should use all upper-case
letters, with an underscore between distinct words in the name.
-
Local variables and structure names should use all lower-case
letters, with an underscore between distinct words in the name.
-
Structure names for structures with an associated type name
(i.e., from typedef) should be given the same
name as the type, except with all letters in lowercase and followed
by an underscore. Example:
typedef struct mytype_ MyType;
struct mytype_ {
MyType *next, *prev;
. . .
};
-
Names should be descriptive. For global variables, preprocessor
macros and constants, type names, and structure names, names
should generally consist of one or more full words. For local
variables, short names and abbreviations are permitted as long
as it is clear what the variables are used for. In general,
one-character local variable names should not be used other
than the following:
-
c: character
-
f: file pointer (FILE *)
-
i, j, k: integers (usually counters)
-
n, p, q: integers
-
s: string
-
t: string or temporary variable
-
x, y, z: integers (usually position
variables)
Two-letter variable names are often formed from initials of
type names, such as NickInfo *ni; see the source code
for examples.
Back to top
Table of Contents
| Previous section: Importing and exporting databases
| Top | Next section: Contacting
the author
|