IRC Services Technical Reference Manual

5. IRC server interface

5-1. Protocol modules: the IRC protocol bridge
5-2. Specifying protocol features
5-3. Sending messages to the network
    5-3-1. Required functionality
    5-3-2. Optional functionality
5-4. Receiving messages from the network
    5-4-1. Required functionality
    5-4-2. Optional functionality
5-5. Other functions of protocol modules
5-6. Specific protocol module details
    5-6-1. protocol/rfc1459
    5-6-2. protocol/ts8
    5-6-3. protocol/dalnet
    5-6-4. protocol/dreamforge
    5-6-5. protocol/bahamut
    5-6-6. protocol/hybrid
    5-6-7. protocol/inspircd
    5-6-8. protocol/monkey
    5-6-9. protocol/ptlink
    5-6-10. protocol/ratbox
    5-6-11. protocol/solidircd
    5-6-12. protocol/trircd
    5-6-13. protocol/undernet-p9
    5-6-14. protocol/unreal
        5-6-14-1. Module prologue
        5-6-14-2. Message receiving
        5-6-14-3. Message sending
        5-6-14-4. Module initialization and cleanup
5-7. Auxiliary source file details
    5-7-1. banexcept.c, banexcept.h
    5-7-2. chanprot.c, chanprot.h
    5-7-3. halfop.c, halfop.h
    5-7-4. invitemask.c, invitemask.h
    5-7-5. sjoin.c, sjoin.h
    5-7-6. svsnick.c, svsnick.h
    5-7-7. token.c, token.h


5-1. Protocol modules: the IRC protocol bridge

While the "official" IRC protocol is defined in the document RFC 1459 [www.ietf.org], modern IRC servers have added many extensions to this base protocol over the years, and the current state of IRC software is such that it is rare to find an IRC server implementation that can communicate with a different implementation. This makes the job of Services considerably more difficult, as it must communicate with servers in order to perform its job, and there is no telling what implementation a particular network administrator will choose.

In order to overcome these differences in protocol, Services makes use of protocol modules, a class of modules which interface between the generic IRC server implemented by the Services core and the specific protocols used by different IRC server implementations. While there are some basic assumptions made by the core code about the protocol—for example, that the protocol uses lines of text terminated by a CR/LF pair—most differences seen in current IRC servers can be handled by protocol modules.

The standard protocol modules distributed in Services are located in the modules/protocol directory, along with auxiliary source and header files that implement functionality common to multiple protocols. This location is not a requirement, however; third-party modules can be located in different directories, provided that they implement the required functionality described in this section.

Note that, in order to simplify the module initialization process, Services requires that a protocol module be loaded before any other modules; this requirement is enforced by the load_module() routine, as discussed in section 4-3-1. A number of pseudoclient modules alter their behavior depending on features of the IRC protocol in use, such as maximum nickname length or support of certain nonstandard IRC messages, and this requirement allows such modules to simply check the protocol information without having to ensure that a protocol module has been loaded (and subsequently watch for protocol module loads and unloads). Implementation note: Although load_module() ensures that a protocol module is loaded first, unload_module() does not prevent protocol modules from being unloaded! Protocol modules should therefore use an exit_module() routine that returns zero on any unload attempt, except during shutdown. The modules included with Services all exhibit this behavior.


5-2. Specifying protocol features

In addition to providing the functionality listed in subsequent sections, protocol modules must inform Services of certain information about the protocol in use. This is done by setting the following global variables (defined in send.c) in the module's module_init() routine:

const char *protocol_name
Specifies the name of the protocol supported by the module. This variable is provided for informational purposes only. (The current core code does not make any use of the variable.)
const char *protocol_version
Specifies the version(s) of the protocol supported by the module, if applicable (the variable may be left unset if there is only one version of the protocol). This variable is provided for informational purposes only.
int32 protocol_features
Specifies particular features supported by the protocol. The value should be a combination (bitwise OR) of zero or more of the following flags: Note that protocol_features is initialized to a value of PF_UNSET to detect whether its value has been changed, so the protocol module should use a direct assignment (rather than an OR-assignment) to set the value.
int protocol_nickmax
Specifies the maximum number of characters (bytes) allowed in a nickname.

send.c hooks into the "load module" callback to check that these variables, as well as the functions listed in section 5-3-1, are appropriately set when a protocol module is loaded, generating a fatal error if not. Implementation note: As mentioned in section 2-5-1, there is nothing to mark a protocol module as being such, so the callback function simply assumes that the first module loaded is a protocol module.

There are also three variables which can be optionally set as needed:

const char *pseudoclient_modes
Specifies the user modes, if any, that should be set on any newly introduced pseudoclient; a leading "+" should not be added. For example, if a protocol includes a user mode specifically for pseudoclients, that mode should be set here. Defaults to the empty string (no modes). Note that mode o (operator privilege) should not be specified even if some pseudoclients need operator privileges to perform their functions; use pseudoclient_oper for that instead (see below).
const char *enforcer_modes
Specifies the user modes, if any, that should be set on pseudoclients used as nickname enforcers. Defaults to the empty string (no modes).
int pseudoclient_oper
Indicates whether pseudoclients which perform actions restricted to IRC operators need the IRC operator user mode (+o) set. Defaults to 1, causing this mode to be set (0 disables +o).

5-3. Sending messages to the network

The bulk of a protocol module consists of routines to send messages to and process messages from the network, handling any peculiarites of the particular protocol in use.

5-3-1. Required functionality

A number of the common message sending operations defined in send.c are defined as function pointers, which the protocol module must set to point to appropriate functions (by default, they point to a placeholder function which generates a fatal error). These function pointers (see section 2-5-1 for descriptions of the functions) are:

Protocol modules must also provide a handler for the "set topic" callback. The callback function should have the following signature:

int set_topic_handler(const char *source, Channel *c, const char *topic, const char *setter, time_t t)

This callback is called twice for each time the topic is set. The first call is made before the Channel structure is changed, and topic, setter, and t are filled in with the new topic text, the nickname to be used as the topic setter, and the timestamp for the topic. The second call is made after the topic and topic_setter fields of the Channel structure have been set to the new values, and the corresponding parameters to the callback (topic and setter) are NULL for this call. Note that the topic_time field is not set by the core, and must be set appropriately by the callback function; this is because some protocols require that the timestamp of a channel topic must be newer or older than the current topic's timestamp for the new topic to be accepted.

5-3-2. Optional functionality

In addition to the functions listed above, the OperServ modules implementing the autokill and S-line functionality (see sections 7-2-2-2 and 7-2-2-3) register message-sending callbacks which they expect the protocol module to hook into if it supports the relevant messages. These callbacks (see Appendix C for details) are:

If any of the callbacks are left unsupported, Services will simply send a KILL or other appropriate message each time the autokill or S-line is triggered. (However, hooking into a "send" callback but not the corresponding "cancel" callback can have undesirable consequences!)


5-4. Receiving messages from the network

While basic message parsing and processing is handled by the Services core, it is up to protocol modules to handle details of the particular protocol as well as additional messages used by the protocol.

In Services, processing of received messages is handled using tables of message names and corresponding processing routines, as described in section 2-5-3. Protocol modules will typically define a message table for messages handled by the module, and call register_messages() to register the message table during module initialization.

5-4-1. Required functionality

The only functionality required in protocol modules is the ability to recognize new clients connecting to the network and clients changing nicknames. As described in section 2-5-3, the NICK and USER messages are not supported by the Services core because of the differences between protocols in handling them; thus, the protocol module must supply its own handler routines for these messages, or whatever other messages may be used in their place.

However, in many protocols there are other messages which must be handled properly to maintain the network state. These, of course, must be processed accordingly as well.

5-4-2. Optional functionality

As described above, modules can add support for additional messages through a message table; the default processing for core-handled messages can be changed the same way, if (for example) a message takes a different set of parameters than the "standard" server assumed by the core code.

There are some types of functionality shared among several servers, such as the SJOIN message used in protocols such as Bahamut and Unreal to update a channel's state with one message, or the "token" systems used by some protocols to reduce the bandwidth and processing required for inter-server messages. These are implemented by separate source files, which can be included in the module's source to implement the particular functionality. These are described in section 5-7.


5-5. Other functions of protocol modules

Aside from handling the sending and receiving of messages particular to the protocol, protocol modules must handle any other aspect of server-to-server communication that is not done in the core. Chief among these is the handling of protocol-specific modes.

Many IRC server implementations add new modes to the basic set; the module must be able to recognize and process these modes appropriately. This can be done using the core mode-handling facility (see section 2-6-4), or by hooking into the MODE message callbacks "user MODE" and "channel MODE" (in the case of modes that take parameters, it is usually necessary to use the latter method in order to store the parameter values in the channel structure). The module may also need to hook into various pseudoclient callbacks; see the relevant parts of section 7, or see the description of the Unreal protocol module, which uses most of these callbacks, in section 5-6-14 below.


5-6. Specific protocol module details

This section describes each of the protocol modules supplied with Services. In addition to the source file for the module itself, some protocol modules make use of auxiliary source files in the same directory; these files are described in detail in section 5-7. For the most part, each subsection only covers details unique to that particular module, but the protocol/unreal module (section 5-6-14), which makes use of almost all protocol-related functionality, is discussed in more detail.

5-6-1. protocol/rfc1459

The protocol/rfc1459 module provides an interface for servers which strictly follow the standard IRC protocol, as defined in RFC 1459. While few if any such servers still remain in operation, this module serves as a reference implementation for Services.

Since the generic IRC server implemented by Services is very similar to the RFC 1459 standard, this module is rather straightforward. In a format shared by the other standard protocol modules as well, the module is divided into four major parts: routines to process received IRC messages, routines to send IRC messages, callback functions, and module-related functions and variables.

The only received messages which need special processing are the NICK and USER messages used for introducing clients. The module ignores the NICK message, assuming that the remote server will take care of checking for collisions with the nicknames of any pseudoclients introduced by Services, and uses the parameters to the USER message to initialize the client's data record. Before calling do_nick() to perform this action, the handler for the USER message, m_user(), sets up a new parameter list with the parameters in the order do_nick() expects them, filling in default values for parameters unavailable in the RFC 1459 protocol. (Technically, the hop count parameter is available from the NICK message, but since it would take considerable extra effort to save this value until the USER message arrived, and since the hop count is not used by Services anyway, this field is simply discarded and a default value of 0 used.)

The message-sending routines are likewise simple, for the most part. The only routine that deserves special mention is the do_notice_all() routine, which implements the notice_all() function of send.c. Since the RFC 1459 protocol does not permit a wildcard target of a NOTICE (or PRIVMSG) to be a simple "*", which would target all clients on the network, the module uses the NetworkDomain configuration setting, if available, to create a more-specific server wildcard mask which will still target all clients on the network. If this setting is not available, the routine iterates through some common top-level domains (.com, .net, .org, and .edu) in an attempt to reach as many clients as possible.

There is only one callback function defined by the module; it hooks into the "set topic" callback, used for setting channel topics (see section 2-6-5). Since RFC 1459 places no restrictions on topic-changing messages from servers, the callback simply sends out a TOPIC message on the first call, ignoring the second.

The module initialization routine sets the protocol-specific variables (protocol_name, protocol_version, and so on—since there are no "versions" associated with RFC 1459, the version string is left empty), installs the handlers for the NICK and USER messages, adds the topic-setting routine to the "set topic" callback, and installs the function pointers for the various message sending operations. Of these, the module cleanup routine removes the callback function and message handlers, but it leaves the protocol variables and message sending function pointers alone; this is a shortcut based on the assumption that the module will never be unloaded at runtime (this assumption is enforced by returning zero to refuse unloading if the shutdown parameter to the cleanup function is false).

5-6-2. protocol/ts8

TS8 was one of the earliest additions made to the IRC protocol, and includes timestamp (often called TS) values with many messages to indicate the time at which certain actions took place. The addition of timestamps allowed, among other things, less disruption of the network during netjoins; in the case of a nickname collision, for example, timestamps made it possible to determine which user was the first claimant on a nickname and kill only the second user, rather than killing both of them as the original protocol called for.

Other than the addition of timestamps into some IRC messages, the protocol/ts8 module, which supports TS8 as used in the ircd-2.8 series of IRC servers (specifically tested with ircd-2.8.21+TS8), is very similar to the protocol/rfc1459 module. The one difference worth noting is in the operation of the "set topic" callback. Under TS8, a server receiving a TOPIC message from another server will give preference to the topic with the older timestamp, ignoring the TOPIC message if it specifies a timestamp newer than that of the topic currently set on the channel; thus, in order to ensure that the topic gets set correctly, the callback function modifies the caller's topic timestamp to be one second earlier than the timestamp of the channel's current topic if a topic is set. This same approach is used in other protocol modules as well to ensure that changes made by Services are given priority over the current network state regardless of timestamps.

5-6-3. protocol/dalnet

The protocol/dalnet module supports the IRC server released by (and used on) the DALnet IRC network, ircd.dal, through version 4.4.13. This server is, incidentally, the server for which Services was originally designed, and as such, the module is nearly as concise as those for the simpler RFC 1459 and TS8 protocols.

The DALnet IRC server introduced several additions to the standard IRC protocol. Most notable, from the point of view of the protocol module, is the addition of the AKILL and RAKILL messages for adding and removing network-wide client bans, and the OperServ AKILL command derives its name from these messages. The module includes callback functions for OperServ's "send_akill" and "cancel_akill" callbacks; it also includes entries in the message table for these messages, but since there is no need to process AKILL messages from the network, the handlers for these are NULL, along with several other messages not recognized by the core message processing code. (These messages are included simply to avoid warnings in the log file when such messages are received.)

Other changes made to the server-to-server protocol in the DALnet server are the unification of the NICK and USER messages into a single NICK message, and the addition of the GLOBOPS message, a WALLOPS-like message that cannot be seen by non-operators (and which is used for the implementation of the wallops() routine in preference to WALLOPS).

In order to ensure that the AKILL-related callbacks mentioned above are added when the appropriate OperServ module (operserv/akill) is loaded, this module also hooks into the "load module" callback. An "unload module" callback is also included for completeness, though it does nothing in this module.

A close look at the module initialization routine, module_init(), will show another difference in the DALnet protocol. The RFC 1459 standard specifies that the three characters [ \ ] are to be interepreted as equivalent to { | } in nicknames and channel names; this is a holdover from the Scandinavian character set which was used by the creators of the IRC protocol. DALnet does away with this holdover, and treats [ \ ] as distinct from { | } when doing such string comparisons. By default, however, Services uses the RFC 1459 rules for comparing nicknames and channel names, so in order to change this behavior, the module_init() routine modifies the global irc_lowertable[] array. There are also two changes made to the valid_chan_table[] array, to accommodate the fact that the DALnet protocol allows channel names to begin with "+" and does not allow colons in channel names.

There is also a mapstring() call in the initialization routine to change the OPER_BOUNCY_MODES message to OPER_BOUNCY_MODES_U_LINE; this latter message includes a specific reference to "U: lines", a type of entry in the DALnet server configuration file that indicates servers (like Services) that are allowed to change channel modes arbitrarily. Failure to set this correctly on all servers can result in the "bouncy modes" phenomenon, where a server reverses mode changes made by Services, causing Services to resubmit those changes in an infinite loop (see section 2-6-3 for details).

One other piece of code not present in the rfc1459 or ts8 modules is the mode initialization code. The DALnet protocol adds two user modes to the standard mode set: +g, indicating whether the client wants to receive GLOBOPS messages, and +h, indicating whether the user is identified as a "help-op". Neither of these have any effect on the operation of Services, but for completeness, they are added to the global mode tables as described in section 2-6-4. The method used for adding the modes (the three arrays new_usermodes[], new_chanmodes[], and new_chanusermodes[], and the local initialization function init_modes(), called from init_module()) is shared by other protocol modules are well.

5-6-4. protocol/dreamforge

The protocol/dreamforge module supports the Dreamforge server protocol. Dreamforge is the name given to versions 4.4.15 and later of the DALnet IRC server, and as such, this protocol is a direct successor to the dalnet protocol.

The major difference between the classic DALnet protocol and the Dreamforge protocol is the addition of features designed to improve the integration of Services-like programs with the network. Chief among these is the "Services timestamp" or "servicestamp", a timestamp-like value associated with each client which is set by Services and retained by all servers as long as the client is connected. This value, or 0 if it has not been set, is sent as part of the NICK message when announcing a new client to the network. Services can set the servicestamp to a unique value, and use that value to distinguish clients with certainty, avoiding problems arising from servers with out-of-sync clocks or clients that connect to the network at the same time. There are also new user and channel modes added to identify registered nicknames and channels, and to prevent clients with unregistered nicknames from joining a channel.

Most of these changes are handled in the NICK message handler, m_nick(), and the callback functions do_user_servicestamp_change(), do_user_mode(), and do_nick_identified(). The do_user_mode() callback function ensures that no other server on the network attempts to change a user's servicestamp (note that this can cause mode floods if two copies of Services are run on the same network!) or registered-nickname status. The function also sets the "Services administrator" (+a) user mode if the user is known to be a Services administrator, and clears the mode otherwise.

In order to check whether a user is a Services administrator, the module has to call the is_services_admin() function in the operserv/main module; however, this module may not be loaded. To avoid having to check for the module and symbol at every location in the code where the function is called, a local helper function, local_is_services_admin(), is defined at the top of the source file, and is_services_admin() is redefined (via #define) to point to this local function. The function itself only checks a cache variable to determine whether is_services_admin() is available; this cache variable is set by the "load module" callback function when the operserv/main module is loaded (and cleared again by the "unload module" if operserv/main is unloaded).

The final new aspect to this module's source code is the use of the svsnick.c auxiliary source file (see section 5-7-6 for details on this particular file), to support the SVSNICK message used to forcibly change a client's nickname. In order to avoid complexities resulting from compiling the source file separately and linking it into the module (in particular, identifier collisions during static linking), the svsnick.c source file is included directly into the module's main source file, dreamforge.c. svsnick.c includes its own initialization and cleanup routines, init_svsnick() and exit_svsnick(), which are called by init_module() and exit_module() respectively.

With respect to server registration, Dreamforge includes a new PROTOCTL message type, used to inform the remote server about the sending server's capabilities; this allows changes and additions to be made to the protocol that can be enabled or disabled depending on whether the remote server supports them. Services does not make use of any such optional features in Dreamforge, however, so the PROTOCOL message is given a NULL handler.

5-6-5. protocol/bahamut

Bahamut is the successor to the Dreamforge IRC server, and is the server currently (2006/8) used on the DALnet IRC network. The protocol/bahamut module handles versions of Bahamut from 1.8.0 onward.

In addition to the SVSNICK feature introduced in Dreamforge, Bahamut includes ban exceptions for channels (clients matching a ban exception mask can join the channel even if they also match a ban mask), supported by the auxiliary source file banexcept.c; invite masks for channels (clients matching an invite mask can join an invite-only channel without having to be explicitly invited), supported by invitemask.c; and a channel-join message for server-to-server communications, SJOIN (which reduces bandwidth use by allowing multiple users and channel modes to be sent in a single message), supported by sjoin.c. With respect to the latter file, bahamut.c defines the BAHAMUT_HACK symbol before including the file; this is to select the Bahamut mode of operation for SJOIN, as described in section 5-7-5.

Bahamut includes a CAPAB message that functions like Dreamforge's PROTOCTL to inform the remote server about supported protocol features. Services checks for one token in the CAPAB message: "NOQUIT", an extension suppressing (on server-to-server links) the client QUIT messages generated when a netsplit occurs. Services always advertises the NOQUIT capability, and sets the PF_NOQUIT protocol flag if the remote server also supports NOQUIT.

One difficulty that can arise when using the Bahamut server is that the server to which Services connects is configured as a "Services hub" (the configuration option "servtype serviceshub"). While this seems logical from the name, this option is intended only for the custom Services program that DALnet uses, and is not compatible with this program, Services for IRC Networks. The DALnet Services program is customized to work specifically with the Bahamut server and the DALnet network, and as such takes certain shortcuts; the "Services hub" option causes the Bahamut server to take advantage of these shortcuts, reducing network bandwidth. In particular, activating this option causes messages to pseudoclients such as NickServ to be sent in an abbreviated form (the NS, CS and similar commands defined under the dummy #ifdef ALLOW_BAHAMUT_SERVICESHUB, referring to a macro not defined anywhere). As Services for IRC Networks cannot assume that these clients have particular nicknames, or even that they exist, the protocol/bahamut module reports an error and aborts the program if any of these abbreviated messages are seen. Implementation note: It might be feasible to support these if the module exported a register_pseudoclient() routine, which took a constant indicating the pseudoclient, like BAHAMUT_NS, and the pseudoclient's nickname and stored the nickname locally for processing the given message. The "Services hub" option also prevents channel topics and client AWAY messages from reaching Services, causing channel topic retention and MemoServ unaway checking to break.

The Bahamut-specific channel mode +j is used to limit the rate at which clients can join a channel. It takes the form +j num1:num2, where num1 and num2 are positive integers that set the exact limits on joining. Internally, these are stored in the joinrate1 and joinrate2 fields of the Channel structure as well as the locked-mode set of a registered channel (see section 7-4-1-1), and values of 0 in these fields indicate that the mode is not set; values of -1 in the locked-mode structure indicate that the mode is locked off.

Unlike RFC 1459, Bahamut does not allow control characters in a channel name. It also does not allow ASCII 160 (0xA0), presumably because this corresponds to an "unbreakable space" in the ISO 8859-1 character set and could be confused with an ordinary space by users. The initialization routine adjusts the valid_chan_table[] array to account for this.

5-6-6. protocol/hybrid

The protocol/hybrid module supports versions 7.0 and later of the ircd-hybrid IRC server. Hybrid has a fairly simple design compared to other servers, and only supports a few features above the standard set.

The Hybrid server does not ordinarily send channel topics during the initial net burst when a server connects, and only allows the channel topic to be set by a TOPIC message from a client currently in the channel. This prevents the ChanServ topic-related functions from working, since ChanServ does not join the channel when setting a topic. There is, however, a module available for Hybrid which restores the synchronization of channel topics on a server link as well as the ability of a server to set topics arbitrarily: the "topic burst" module, m_tburst.so. To simplify processing, the protocol/hybrid module requires that its uplink server support this topic bursting, and will abort the program if that support is missing (as determined by the CAPAB message received on connection).

5-6-7. protocol/inspircd

The protocol/inspircd supports the InspIRCd IRC server. This server shares a number of features with the Unreal server (see section 5-6-14, but was created from scratch, rather than by modifying the source code of an existing IRC server as in the case of most other servers.

The inspircd module uses the banexcept.c, invitemask.c, and svsnick.c auxiliary source files mentioned above, as well as the chanprot.c and halfop.c files, which are used to implement two additional channel user modes supported by InspIRCd: +a (protection) and +h (half-op). A user with +a set cannot be kicked by a channel operator unless that operator also has +a set. Half-op privilege is an extra privilege level between +v (voiced) and +o (channel operator); half-ops can set the channel topic and give +v to other users, but cannot change channel modes or perform other actions that ordinary channel operators can do. InspIRCd also has a condensed channel join message (FJOIN) similar to Bahamut's SJOIN, but its syntax is different enough that a message handler is included in the module itself rather than using the handler in sjoin.c.

In addition to autokills and S-lines, InspIRCd supports autokill exclusions, similar to channels' ban exceptions but applying to network-wide autokill masks. As described in detail in section 7-2-2-2, the operserv/akill module can take advantage of this to implement autokill exclusions without having to send KILL messages for all autokilled users manually.

Another feature of the InspIRCd protocol is the removal of the restriction on sending network-wide messages; as such, the protocol/inspircd module does not require a NetworkDomain configuration setting for global messages to be correctly sent.

5-6-8. protocol/monkey

The protocol/monkey module supports the Chunky Monkey IRCD server. This server is based on an earlier version of Bahamut; the primary Services-visible change is the addition of the half-op (+h) channel user mode. Features like ban exceptions and invite masks, added to Bahamut in later versions, are not present. Other than these differences, this module is essentially the same as the bahamut module.

5-6-9. protocol/ptlink

The protocol/ptlink module supports the PTlink IRCd server, version 6.10.0 and later. This server is based on ircd-hybrid-6, and was originally developed for the PTlink IRC network, from which it takes its name.

In PTlink, autokills are set and removed using the GLINE message. Since this can also be used by IRC operators to set and remove autokills, Services uses a constant string (defined as GLINE_WHO at the top of the source file) as the "nickname" associated with the entry. Services can then check this value when receiving a GLINE message from the network, in order to avoid removing entries set by IRC operators. (However, nicknames are not stored with SGLINE and SQLINE entries, so operator-set entries will be deleted if they do not match a record in Services' databases.) See under the TKL message handler in section 5-6-14, which discusses the Unreal server, for an explanation of why these entries need to be cleared when received from the network.

5-6-10. protocol/ratbox

The protocol/ratbox module supports the ircd-ratbox server. This server is derived from ircd-hybrid, and the ratbox module is likewise very similar to the hybrid module.

Like Hybrid, ircd-ratbox does not normally allow servers like Services to arbitrarily change channel topics, but this behavior can be changed with a similar "topic burst" function. In the case of ircd-ratbox, this functionality is built into the server (rather than being a separate module), and need only be enabled through the appropriate flag ("topicburst" in the connect block for Services). If this flag is not enabled, Services will abort at connection time, as for Hybrid.

5-6-11. protocol/solidircd

The protocol/solidircd module supports the solid-ircd server. This server is based on an earlier version of the Bahamut server, with several additions and changes.

One new feature supported by solid-ircd is encrypted connections. If a client connects to the server via SSL, the client is given user mode +z; a new channel mode, +S, is also available to prevent clients without +z from entering the channel. This presents a problem when +S is mode-locked on: since the channel does not exist when it is empty, the IRC server will not know to stop a -z user from entering the channel, and even if Services then sets +S on the channel, the non-secure user will already be in the channel. In order to work around this, Services hooks into the ChanServ "check_kick" callback, and if a non-secure user tries to join an empty channel that is registered and mode-locked +S, the user is kicked out just as if on the autokick list.

5-6-12. protocol/trircd

The protocol/trircd module supports the tr-ircd server, version 5.5 and later. tr-ircd was originally based on the Bahamut server, but has been rewritten from scratch since version 5.0.

One feature unique to tr-ircd is the availability of a user mode (+L)bindicating the language in which the client desires to receive server messages. For users with registered nicknames, Services sets this to the language selected by the user for their nickname (if that language is also supported by tr-ircd) when the user identifies. Implementation node: It would also be theoretically possible to use the mode's value as a default for sending messages unregistered nicknames, as well as the initial language setting of a newly-registered nickname, but this is difficult to accomplish without adding the concept of a language mode for clients to the Services core. The list of languages supported by tr-ircd, taken from the tr-ircd source code, can be found at the top of the trircd.c module source file (langhash_init[]); the actual values used in the user mode are hashes created from the language name strings, and these hashes are computed and stored in the langhash[] array by the init_langhash() function, called at module initialization time.

tr-ircd also supports "channel linking"; this is the ability to make one channel into an "alias" for another. When this mode (coincidentally also +L) is set for a channel #A with a parameter of #B, for example, a user who attempts to join channel #A will be sent to channel #B instead. As a side effect of this, the IRC server keeps track of channels which have +L set, and does not delete them even after the last client leaves. Since Services treats a channel with no users as nonexistent, the trircd module must hook into the low-level "receive message" callback to watch for +L or -L messages for empty channels and process them accordingly.

At the server-to-server communication level, tr-ircd has the ability to send message names as one- or two-character tokens rather than the full message names; for example, "PRIVMSG" becomes simply "P", both reducing bandwidth (albeit minimally) and enabling faster lookup of commands. These tokens are defined in the trircd_tokens[] array, and processing is handled by the token.c auxiliary source file, discussed in section 5-7-7.

5-6-13. protocol/undernet-p9

The protocol/undernet-p9 supports version 2.9 of the Undernet IRC server (ircu). This is a fairly old server, derived from the original ircd-2.8 server with TS8 additions; the only additional features it includes are autokills (set using the GLINE message, and not propogated in the connection burst) and the merging of the USER and NICK messages into a single NICK message.

5-6-14. protocol/unreal

The protocol/unreal module supports version 3.1.1 and later of the UnrealIRCd server. Unreal is originally derived from Dreamforge, but has made numerous additions, and is one of the most feature-rich IRC servers currently in use. This of course means that the protocol module is similarly complex; Unreal makes use of nearly all protocol-related routines, as well as all seven auxiliary source files. For this reason, the unreal module's source code is the most heavily commented of the protocol modules, and can be seen as a model for how the various parts of protocol modules work.

5-6-14-1. Module prologue

After including appropriate header files and the auxiliary source files that implement certain protocol-related functions, the module defines a number of local variables:

These are followed by #defines and local variables used to access the s_ChanServ variable in the ChanServ module, containing the nickname of the ChanServ pseudoclient, and the is_services_admin() function in the OperServ module, returning whether the given user is a Services administrator. As described in the comments, #define macros are used to substitute an access to the symbol pointer every time the symbol is used, in order to simplify the code.

The next section of the file contains a list of user, channel, and channel user modes supported by the Unreal server. In order to register these with the core mode processing facility (see section 2-6-4), unreal.c defines three arrays, one for each mode type, containing the mode characters to be added and the corresponding ModeInfo structure for each mode. The module also defines six local mode flags, corresponding to the mode bitmask variables listed above; these flags are ignored by the mode processing facility, but the mode setup code uses them to set the bitmask variables.

The setup code itself is located in the init_modes() routine, called during module initialization. This routine iterates through each of the arrays, storing the mode data in the arrays exported by the mode processing facility. For the user and channel modes, it also checks for the locally-defined ModeInfo flags, and if a given mode has a flag set, the mode's bitmask is added to the appropriate bitmask variable.

5-6-14-2. Message receiving

After this comes the first major portion of the actual processing code, the message handlers. Aside from the mandatory client registration handler (the NICK message in Unreal), Unreal includes several additional messages not supported by the core processing code. The messages handled are as follows (each message is handled by a routine named m_message-name, for example the NICK message is handled by m_nick()):

NICK
This is the standard client registration message (Unreal merges the dual NICK and USER of RFC 1459 into a single NICK message at the server level, as most other server protocols do). The message is also used for existing clients changing nicknames; in that case (where the message has a prefix indicating that it came from an existing client), control is simply handed off to the do_nick() routine from the core's client handling code. In the case of a new client, however, do_nick() expects the parameters in a slightly different order than Unreal sends them in, so this rearrangement is performed before calling do_nick(). Additionally, on servers supporting NICKIP (the sending of IP addresses in the NICK message), the message will include an extra parameter containing the binary representation of the client's IP address, encoded using base64 encoding; this is decoded, checked for consistency, and passed to do_nick() as well. (If NICKIP is not supported, a NULL parameter is passed, indicating that the IP address is not available.)
PROTOCTL
Unreal uses this message to send information about features supported by the server. Services checks the list of parameters for recognized keywords, and sets appropriate variables to reflect their presence or absence. The recognized feature tokens are:
UMODE2
This is equivalent to the MODE message for clients, but omits the nickname parameter (so, for example, ":nickname MODE nickname -o" becomes ":nickname UMODE2 -o").
SETHOST, CHGHOST
These allow setting changing the "fake hostname", the hostname seen in a WHO or WHOIS reply by non-operators, for a client. SETHOST is used by a client to change its own hostname, while CHGHOST is used by operators (or servers, such as Services, though Services does not use this at present) to change another client's hostname.
SETIDENT, CHGIDENT
These are like SETHOST and CHGHOST above, but change the client's username (the part of the user@host mask before the @).
SETNAME, CHGNAME
These are like SETHOST and CHGHOST above, but change the client's "real name" (also called "gecos").
SJOIN
This is a server-to-server message used both to add a user to a channel and to send a channel's state to the network. The message takes a channel name, timestamp, mode string, optional mode parameters, and a space-separated list of nicknames of clients joining the channel. Nicknames in the nickname list are prefixed with the prefix character(s) for the channel user mode of the client, if any (for example, a channel operator would be listed as @nickname. Recent versions of Unreal also add channel bans and ban exceptions to the nickname list as well, with prefix characters of & and " respectively.
SVSMODE, SVS2MODE
These messages are used to remotely change a client's modes or to remove all bans on a channel which match a client. In the former case, the messages operate identically to the MODE message, except that the source and target client may be different. In the latter case, each server checks the channel's ban list and removes all bans corresponding to that user; Services does this by calling clear_channel(), which sends out explicit MODE messages to remove the bans, but this is mostly harmless (other than taking up a small amount of bandwidth). The only difference between SVSMODE and SVS2MODE is that, when used to set client modes, the latter sends a MODE message to the target client, while the former does not.
SVSNLINE
This message is used for managing what Services refers to as the SGline list, and takes one of two formats: Unlike other access control lists, which are handled by the TKL message (discussed below), masks for SVSNLINE messages may contain spaces, and thus must be the last parameter of the message. As a corollary, the reason parameter cannot contain spaces. In Services, any spaces in the reason are converted to underscores when an SVSNLINE + message is sent.
TKL
This message is used for managing network-wide global connection control lists (autokills and S-lines). The message takes as parameters: When a new server connects to the network, all active TKL masks are forwarded to the server. This can cause problems if a mask is removed while a server is split from the network; when the split server reconnects, it will send the mask back to the network, causing it to reappear. While there is nothing that Services can do about this in the general case, it does watch for new masks added that give Services itself as the adder (Services uses its own server name as the entity adding the mask, and this is preserved when other servers forward the mask). When it sees such a mask sent from the network, it checks whether that mask actually exists in the current databases, and if not, it sends a TKL - message to remove the mask, assuming that a split server has rejoined the network and reintroduced an old mask.

The extra messages in Unreal along with their handlers are listed in the unreal_messages[] table, which is registered at initialization time using register_messages() (see section 2-5-3). Some additional messages are also listed with NULL handlers in order to prevent warning messages from being written to the log when those messages are received.

Unreal also supports the concept of tokens, short names for messages used in server-to-server communication. These slightly reduce the bandwidth needed for messages, but more importantly speed up message lookups, since only one or two characters need to be checked rather than a longer text string. The list of tokens used by Unreal is in the tokens[] array; this array is passed to init_tokens(), the initialization routine defined in the tokens.c auxiliary source file (see section 5-7-7).

5-6-14-3. Message sending

Most of the remainder of the source file consists of various routines which send messages to the network. These include the required functionality listed in section 5-3-1, as well as a number of callback functions which send messages to the network in response to certain events. The routines are as follows:

do_send_svstime()
Sends a TSCTL SVSTIME message to the network to synchronize servers' clocks. Used for periodic time synchronization when requested via the SetServerTimes configuration directive.
do_send_nick()
do_send_nickchange()
do_send_namechange()
do_send_server()
do_send_server_remote()
do_wallops()
do_notice_all()
do_send_channel_cmd()
Implement the corresponding required message routines.
do_receive_message()
Performs special actions on received messages. This callback function has two purposes; one is to watch for and reverse improper servicestamp changes, and the other is to parse the protocol version embedded in the remote server's SERVER message at connection registration time. In both of these cases, we do not want to override the standard behavior for the messages, but the current message framework does not allow for this, so we watch for the messages at this lowest level and perform the necessary processing there. With respect to the former case specifically, it would in theory be possible to handle servicestamp changes in do_user_mode(), described below, but poor design in Unreal resulted in Services stamps sharing a mode letter (+d) with "deaf" mode, so "+d stamp" needs to be treated separately from other mode changes.
do_user_create()
Called when a new User structure is created in response to a NICK message, this routine stores the "fake hostname" parameter (not handled by do_nick()) in the corresponding field in the User structure.
do_user_servicestamp_change()
Called when a client is assigned a servicestamp to send the new stamp out to the network.
do_user_mode()
Called when a client's user modes are being changed. Services handles two user modes exclusively: +r, indicating that the client's nickname is registered (and the client is in fact the owner of the nickname), and +a, indicating that the client is a Services administrator. If another server tries to change either of these modes, Services reverses the change. This routine also watches for users with Services administrator privilege who newly gain IRC operator status, and sets +a on them.
do_channel_mode()
Called when a channel's modes are being changed. This routine takes care of storing the +L (channel link), +f (flood protection), and +j (join rate limiting) parameters in the Channel structure.
do_clear_channel()
Handles requests to clear bans and exceptions from a channel. Ordinarily, clear_channel() will handle bans itself and ban exceptions are handled by the banexcept.c auxiliary source file, but Unreal has a feature called "extended ban types", in which a prefix like ~r: changes the meaning of the ban or exception mask. In order to process these bans correctly, the protocol/unreal module has to intervene via the "clear_channel" callback and process ban and exception clear requests itself. The actual work is done by unreal_clear_bans_excepts(), which calls unreal_match_ban() to determine whether a particular mask matches a given client.
do_nick_identified()
Called when a client identifies for its nickname, and sets the +a user mode on the client if it has Services administrator (and IRC operator) privileges.
do_set_topic()
Called when setting a topic on a channel (see section 5-3-1). Unreal ignores topic changes if the topic timestamp is earlier than that of the channel's current topic, so if necessary, the supplied timestamp is advanced to force the topic to be changed.
do_check_modes()
Called when checking the validity of a mode change on a registered channel; ensures that the +L, +f, and +j modes are set properly according to the mode lock data. (The "off" versions, -L and so on, do not require parameters and thus can be handled by the standard ChanServ code.)
do_check_chan_user_modes()
Called when checking whether to modify a client's channel user modes. This routine watches for and excludes from the check two classes of clients: other service-type pseudoclients (with user mode +S set), since they generally should not be subject to the same restrictions as ordinary clients, and "hiding" users (+I), whose presence would be revealed by a mode change.
do_check_kick()
Called when checking whether to allow a user to join a channel. This routine watches for users entering administrator-only, secure-only, or no-hiding channels, and kicks them out if their user modes are incompatible with the channel. (Normally the IRC server will take care of this and Services will never see the join attempt, but when joining a registered channel which is empty, the server will not know anything about the channel's locked modes, so Services must perform the check.)
do_set_mlock()
Called when setting a new mode lock on a registered channel. This routine takes care of setting the aforementioned +L, +f, and +j mode parameters in the mode lock structure, and also ensures that the new mode lock is consistent (+K requires +i to be set, and +L requires +l).
do_send_svsjoin()
Called to send a message which forces a user to join a channel. In Unreal, the message is called SVSJOIN.
do_send_akill()
do_cancel_akill()
do_send_exclude()
do_cancel_exclude()
do_send_sgline()
do_send_sqline()
do_send_szline()
do_cancel_sgline()
do_cancel_sqline()
do_cancel_szline()
Called to add or remove autokill, autokill exclusion, or S-line masks.
5-6-14-4. Module initialization and cleanup

The final part of the module source file is the module framework, used by the core module subsystem when loading and unloading the module, along with callback functions to monitor loading and unloading of relevant modules and set appropriate callbacks and local variables.

The module's configuration data is stored in the exported array module_config[]. The protocol/unreal module includes two Unreal-specific configuration options: ServerNumeric, to assign Services a "numeric" value to be used with Unreal servers, and SetServerTimes, which sets whether and how often Services should synchronize other servers' clocks. The array also includes configuration data from the SJOIN handler (defined in sjoin.h).

The do_load_module() and do_unload_module() routines watch for various pseudoclient-related modules to be loaded or unloaded. For modules that provide callbacks which the protocol/unreal module hooks into, the appropriate callback functions are added here. Additionally, the handles for the operserv/main and chanserv/main modules, as well as the values (addresses) of the symbols is_services_admin and s_ChanServ, are saved when the relevant module is loaded and cleared when it is unloaded (see section 5-6-14-1).

Finally, the initialization and cleanup functions call the appropriate subroutines in a fairly arbitrary order (except that cleanup is performed in the opposite order of initialization). The initialization and cleanup for the auxiliary source files is also handled here.


5-7. Auxiliary source file details

In addition to the main protocol files described in section 5-6, there are several auxiliary source files which implement functions common to two or more protocols. These are listed below. The source files are all designed to be included directly into the modules which use them; this is to avoid symbol clashes caused by linking the same object file into two or more modules when compiling modules statically. (Each source file also has a corresponding header file, but as this is included by the source file, modules do not need to include the header files separately.)

5-7-1. banexcept.c, banexcept.h

The banexcept.c source file implements support for ban exceptions, masks specifying clients which are not subject to channel bans. The channel mode for exceptions is assumed to be +e.

In addition to handling the actual setting and clearing of ban exceptions (adding or removing masks on the channel's ban exception array), the file also implements a handler for clearing ban exceptions using the CLEAR_EXCEPTS flag to clear_channel(), as well as a callback function for the ChanServ CLEAR command allowing CLEAR EXCEPTIONS to be used. Since the latter function is part of the chanserv/main module rather than the core, this necessitates the use of "load module" and "unload module" callbacks to watch for that module being loaded and unloaded.

5-7-2. chanprot.c, chanprot.h

The chanprot.c source file implements support for a "protected" channel user mode, preventing ordinary channel operators from kicking a user with that mode. However, the only "support" required is the modification of one language string, which is modified by the initialization routine and restored to its original value by the cleanup routine.

5-7-3. halfop.c, halfop.h

The halfop.c source file implements support for a "half-op" channel user mode, assumed to be +h. Half-ops occupy a privilege level between voiced users (+v) and channel operators (+o); typically, they can set +v on users and change the channel topic, but cannot change channel modes or kick channel operators. In addition to updating several language strings, the source file adds a callback function for the ChanServ CLEAR command allowing CLEAR HALFOPS to be used. As with banexcept.c, this necessitates the use of load/unload-module callback functions to watch for the ChanServ module being loaded or unloaded.

5-7-4. invitemask.c, invitemask.h

The invitemask.c source file implements support for invite masks, masks specifying clients which are allowed to join a +i channel without being explicitly invited into the channel. The channel mode for exceptions is assumed to be +I. Except for the actual mode character used and structure fields modified, this source file is identical to banexcept.c.

5-7-5. sjoin.c, sjoin.h

The sjoin.c source file implements support for the SJOIN message used in IRC servers such as Bahamut and Unreal. This is easily the most complex of the auxiliary source files.

The basic idea of the SJOIN message is to condense several types of channel information—channel creation time, channel modes, and users on the channel—into a single message, reducing the bandwidth required to send channel state across the network and reducing the potential for race conditions. There are a few slightly different message formats used, as noted in the source code comments, but in general the message takes the channel name, channel creation time, mode string, (optional) mode parameters, and finally (in a colon-prefixed parameter) the list of clients (nicknames) on the channel. In this last parameter, each client's nickname is prefixed with characters indicating the client's channel user modes; for example, if Nickname is a channel operator on the channel, the client will be listed as @NickName in the message.

Processing of the SJOIN message is handled by the do_sjoin() routine; the module should call this routine when in receives an SJOIN message. do_sjoin() looks at the parameters to determine the message's format, then walks through the list of nicknames (and possibly bans and exceptions), adding each one to the channel. If any users actually joined the channel, then the channel modes and timestamp are set afterwards.

The SJOIN message can also be used as a shortcut to remove all users from a channel with one command. By sending an SJOIN with a channel timestamp older than the channel's current timestamp, the remote server will treat Services' SJOIN as authoritative. If that message indicates that no users are in the channel (an empty last parameter), then the IRC server will automatically kick all the users currently on the channel, saving Services the trouble of having to send KICK messages for all users.

Additionally, since SJOIN includes the channel's creation time as a parameter, it is possible to use a dummy SJOIN message to deliberately set the creation timestamp of a registered channel to the time the channel was registered, ensuring that the channel will always be considered the "canonical" one even if the same channel is created on a split server.. The configuration option CSSetChannelTime activates this behavior. (This configuration option is defined in a macro, SJOIN_CONFIG, in sjoin.h; the module should include this macro as part of its module_config[] array.) The actual setting of the channel creation time is done using the "channel create" callback; when a user first enters an empty channel, Services sends an SJOIN message to the network with the altered channel creation time, including the newly-joining user in the user list; this results in the channel creation time being updated without any other changes being made to the channel.

Unfortunately, both of the major IRC servers using SJOIN, Bahamut and Unreal, have idiosyncrasies that prevent this behavior from working exactly as designed; Bahamut ignores any channel user modes set on clients who are not behind the sending server, causing the client to lose channel operator privileges, while Unreal interprets an empty mode string (a single "+") to mean "clear all channel modes". To work around this, if the module defines the preprocessor symbol BAHAMUT_HACK before including this source file, this processing will be modified to set +o again on the newly-joining client if it was +o before the Services SJOIN was sent; this restores the original channel state, at the cost of having an unsightly MODE -o and MODE +o in quick succession when a user first enters a registered channel. Likewise, defining UNREAL_HACK will work around the Unreal problem by using the channel mode parameter to set +o on the user rather than listing the user in the final parameter.

5-7-6. svsnick.c, svsnick.h

The svsnick.c source file implements support for forced changing of clients' nicknames; this is used, for example, by NickServ to change a client's nickname to a "guest" nickname rather than disconnecting the client outright. The implementation itself simply consists of setting the PF_CHANGENICK protocol feature flag and assigning a function to the send_nickchange_remote() function pointer. However, unlike most other auxiliary source files, the initialization function init_svsnick() takes a parameter; this is so that the protocol module can specify the name of the message used, in case it is not SVSNICK.

5-7-7. token.c, token.h

The token.c source file implements the use of one- or two-character tokens to substitute for full message names in server-to-server communications. The mapping of tokens to message names is given by a TokenInfo array passed to init_tokens(), terminated by an entry with the token field of the structure set to NULL. The initialization routine uses this mapping array to generate a 65,536-entry lookup table, indexed by the two characters of the token taken as a 16-bit value with the first character in the high eight bits (a value of zero is used for the second character in the case of a one-character token). The values in the lookup table point directly to the handler functions; this eliminates the necessity to search through the message table, but also means that any later changes to the message table will not be seen.

The actual processing of the tokens is done by a callback function added to the "receive message" callback. The function checks whether the message name is two characters long or less and whether there is a function in the table corresponding to the one- or two-character message name; if so, that function is called, and the ordinary message processing is skipped.