Zum Inhalt springen

IRCServices-Modulprogrammierung

Modul-Programmierung – (Coding)

In diesem Bereich findet ihr einige Hilfen und Tutorials, um Euren eigenen IRC-Services – Module schreiben zu können. Wer zuvor schon C oder eine ähnliche Sprache wie Java, C#, C++, Python oder gar perl gelernt hat, wird mit diesen Hilfen sicher zurechtkommen.
Andere die ernsthaft Module Schreiben möchten, sollten sich mit einigen C-Büchern beschäftigen.
Denn die hier gegebenen Hilfen Zeigen nur eine Call-Back-Verfahren, damit die Services miteinander Kommunizieren können. Die Grundlagen sind vorerst nicht zu finden.
Demnächst werden noch einige Workshops folgen.
****
Dieser Bereich ist momentan noch in englischer Sprache.
Der unten aufgeführte Scripting-Text wurde der IRCSERVICES-FAQ von IRCServices.za.net entnommen.
Autor : Andrew Church

Bei der Übersetzung steht der Bereich „Überblick aller Services Funktionen“ im Vordergrund, ich bitte dies zu verstehen ;).
***

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 führt ein neues „Modul“-System ein, mit dem Funktionen nach Belieben zu Services hinzugefügt und daraus entfernt werden können, ohne dass Änderungen am Kerncode der Services vorgenommen werden müssen.

6-1-1. Overview

Dienstmodule basieren auf Standard-Dynamic-Link-Bibliotheken und verwenden tatsächlich dasselbe System wie diese. Beim Start laden die Dienste ein bestimmtes Modul in den Speicher und führen seine Initialisierungsfunktion aus. Diese Funktion sollte alle erforderlichen Datenstrukturen einrichten und alle erforderlichen Rückruffunktionen registrieren (siehe unten). Beim Herunterfahren wird seine Bereinigungsfunktion aufgerufen, die alle verwendeten Ressourcen freigeben und andere notwendige Bereinigungsfunktionen ausführen sollte oder einen Fehler zurückgeben sollte, wenn sich das Modul aus irgendeinem Grund nicht selbst entladen kann.

Die Modulinteraktion mit Diensten erfolgt hauptsächlich über die Verwendung von Rückrufen. Hierbei handelt es sich um Funktionen, die das Modul bei Diensten registriert und aufgerufen werden soll, wenn ein bestimmtes Ereignis eintritt, beispielsweise wenn eine Nachricht vom Remote-Server eintrifft oder wenn eine bestimmte Zeitspanne vergeht. Normalerweise registriert ein Modul Rückrufe für Ereignisse, an denen es interessiert ist, in seiner Initialisierungsroutine und entfernt sie, wenn das Modul entladen wird.

Module werden mithilfe der von „modules.c“ im Services-Kern bereitgestellten Funktionen manipuliert. Sie lauten wie folgt:

Modul *load_module(const char *modulename)
Lädt das angegebene Modul und gibt einen Zeiger auf eine Datenstruktur (Module *) für das Modul zurück. Der Inhalt dieser Datenstruktur ist privat und der Zeiger sollte nur als „Handle“ betrachtet werden, der ein bestimmtes Modul angibt. Wenn das Modul nicht geladen werden konnte, wird NULL zurückgegeben. Einzelheiten zum Modulladevorgang finden Sie in Abschnitt 6-1-2 unten.

int unload_module(Module *module)
Entlädt das angegebene Modul. Gibt einen Wert ungleich Null zurück, wenn das Modul erfolgreich entladen wurde, null, wenn nicht. Einzelheiten zum Entladevorgang des Moduls finden Sie in Abschnitt 6-1-2 unten.

Module *find_module(const char *modulename)
Gibt ein Handle für das Modul zurück, dessen Name durch „modulename“ angegeben ist. Wenn kein solches Modul geladen wurde, wird NULL zurückgegeben.

void use_module(Module *module)
Erhöht den „use count“ für das angegebene Modul. Ein Modul, dessen Nutzungsanzahl ungleich Null ist, darf nicht entladen werden. Diese Funktion wird normalerweise von Modulen aufgerufen, deren ordnungsgemäße Funktion von anderen Modulen abhängig ist. Einzelheiten zu Abhängigkeiten zwischen Modulen finden Sie in Abschnitt 6-1-3.

void unuse_module(Module *module)
Das Gegenteil von use_module(): dekrementiert den „use count“ für das angegebene Modul.

void *get_module_symbol(Module *module, const char *symname)
Sucht den Wert des Symbols „symname“ im angegebenen Modul und gibt seinen Wert zurück; Wenn das Symbol nicht gefunden werden kann, wird NULL zurückgegeben. Wenn das Symbol nicht im angegebenen Modul definiert ist, sondern von einem anderen Modul definiert wird, ist der Rückgabewert dieser Funktion undefiniert. Beachten Sie, dass der „Wert“ einer Variablen, wie er von dieser Funktion zurückgegeben wird, die Adresse der Variablen ist, nicht der Wert, den sie tatsächlich enthält; also, wenn ein Modul deklariert

int intvar;

Dann sollte für den Zugriff Code ähnlich dem folgenden verwendet werden:

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)
Gibt den Namen des angegebenen Moduls zurück. Dies ist normalerweise der Pfad, der zum Laden des Moduls verwendet wird (wird an „load_module()“ übergeben), kann jedoch anders sein, wenn das Modul ein Modulnamensymbol definiert. Wird für den Parameter „module“ NULL übergeben, wird der String „core“ zurückgegeben.

MODULE_NAME
Dieses Präprozessormakro ist eine Abkürzung für get_module_name(module) (wobei „module“ eine Variable ist, die auf das Handle des aktuellen Moduls zeigt). Es kann wie jede andere String-Konstante oder jedes andere Makro verwendet werden, mit der Ausnahme, dass es nicht in literale String-Konstanten eingefügt werden kann. mit anderen Worten: „Modulname:“ MODULE_NAME wird nicht kompiliert.

int register_callback(Module *module, const char *name)
Registriert eine neue Rückrufliste. Das aufrufende Modul sollte seine eigene Modulinformationsstruktur übergeben (wie sie an init_module() übergeben oder von find_module() abgerufen wird). Gibt einen ID-Wert zurück, der eine nicht negative Ganzzahl ist.

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.