# IRC Services Technical Reference Manual

## 7. Services pseudoclients

Previous section: Database handling | Table of Contents | Next section: Other modules

### 7-1. Basic features of a pseudoclient

Pseudoclients are the user-visible part of Services, providing the actual service functions which IRC users take advantage of. While the details of each pseudoclient differ greatly, all pseudoclients share some common features:

- Register one or more nicknames via the "`introduce_user`" callback, through which the pseudoclient communicates with IRC clients; see below for details.
- Receive commands from IRC clients via `PRIVMSG` messages. (According to RFC 1459, pseudoclients must not respond to `NOTICE` messages, in order to prevent infinite loops in which two pseudoclients repeatedly respond to each others' notices.)
- Communicate to IRC clients via `NOTICE` messages. (While not explicitly mandated by the RFC, this is in order to avoid potential message loops with other pseudoclients. Occasional requests have been made for Services to allow using `PRIVMSG` for communication with users, apparently because some IRC programs do not show `NOTICE` messages to users, but such requests have been intentionally disregarded for the above reason.)
- Provide a set of commands which users can use to invoke the pseudoclient's functions. (The miscellaneous modules described in section 7-7 are an exception; the HelpServ pseudoclient has only one function with no associated command name, and the DevNull pseudoclient has none.) Each message to a pseudoclient is interpreted as a command name and parameters, each separated by one or more space characters (ASCII 0x20—note that the tab character is not treated as a separator).

The "`introduce_user`" callback mentioned above is called by the same-named function, `introduce_user()`, to request that each module introduce its nickname(s) to the IRC network by calling `send_pseudo_nick()` (in `send.c`). The callback takes a single `const char *` parameter, which is `NULL` when the callback is called at startup, or the nickname seen in a `KILL` message when one is received; accordingly, pseudoclients should introduce their nicknames only when the parameter either is `NULL` or matches (case-insensitively, as determined by `irc_stricmp()`) the nickname to be introduced. `send_pseudo_nick()` takes three parameters: the nickname of the pseudoclient to be introduced, the pseudoclient's "real name" string (usually a description of the pseudoclient), and a flag value, composed of zero or more of the following flags:

- `PSEUDO_OPER`: The client requires IRC operator privileges. (This does not necessarily guarantee that the client will be given the `+o` user mode; some IRC servers allow any Services pseudoclient to use IRC operator functions, and `+o` is omitted with such servers.)
- `PSEUDO_INVIS`: The client should be marked invisible (user mode `+i`).

Command processing is typically performed by hooking into the core's "`m_privmsg`" callback, which is intended specifically for this purpose. Depending on the pseudoclient, it may also be necessary to respond to other events on the IRC network, such as channel joins or mode changes; these can typically be handled by hooking into the relevant callback. In extreme cases, it may be necessary to make use of the low-level "`receive message`" callback as well; this should be avoided when possible, however, as it circumvents the standard message processing and can result in network desynchronization.

For databases maintained by pseudoclients, the following six functions are typically provided by the pseudoclient for other code that needs direct access to the database (`type` is the record data type, `name` is a distinguishing name generally derived from `type`, and `keytype` is the data type of the key field):

`type *add_name(type *record)`
  Adds the given record to the database, returning a pointer to the record structure as stored (which may be different from the pointer passed into the function).
`void del_name(type *record)`
  Deletes the given record from the database. `record` is assumed to be valid, i.e. a pointer returned by a previous call to another database function.
`type *get_name(keytype *key)`
  Returns the record for the given key, or `NULL` if the key is not found in the database.
`void put_name(type *record)`
  Indicates that the given record is no longer in use. Each call to a database's `get()` function must be followed by a `put()` call for the same record, unless the record is deleted first. Implementation note: A `put()` function was first introduced in 5.0 with the intention that it be used for indicating when a record had been updated, for the purpose of storing the updated data into persistent storage such as an SQL-based database. DBMS support never materialized, and the function's purpose was redefined for version 5.1 to keep track of which records are actively in use, but I have no confidence that the code does `put()` calls in all necessary cases and no others; in fact, it appears that `put_nickgroupinfo()` (at least) is called more often than it should be. It may have been better to drop `put()` entirely and use other methods of checking whether records are in use before expiring them.
`type *first_name()`
`type *next_name()`
  Iterates through the database. `first()` returns the first record in the database, and subsequent `next()` calls return each successive record, finally returning `NULL` when all records have been iterated through (if there are no records at all, both `first()` and `next()` return `NULL`). The order is database-dependent, but no record will be returned more than once from the time `first()` is called to the time `next()` returns `NULL`. It is unspecified whether records added while iterating through the database will be included in the iteration. Note that these functions are not considered a "get" for the purposes of the `put()` function; thus a call to `put()` is not required for simple iteration, but if any other database function is to be called before the following `next()`, then an explicit call to `get()` (and a matching call to `put()`) must be made to prevent the record in use from being deleted.

The following sections describe each of the Services pseudoclients in detail. The reader is assumed to be familiar with the functions of each pseudoclient from a user's point of view, as described in section 3 of the user's manual.

Back to top

### 7-2. OperServ

The OperServ pseudoclient provides services to IRC operators allowing control of the network and of Services itself. While not seen by most IRC users, this pseudoclient is discussed first since it provides functionality used by most other pseudoclients.

As with most of the Services pseudoclients, OperServ is composed of one core or "main" module, `operserv/main`, and several optional modules providing additional functionality. These are each discussed in separate sections below.

Back to top

#### 7-2-1. OperServ core functionality

The core functionality of OperServ is contained in the `modules/operserv/main.c` source file, compiled into the `operserv/main` module. In addition to the implementation of the core OperServ commands, this file also defines several utility functions used by several other pseudoclient modules; external declarations of these functions and associated constants are located in `modules/operserv/operserv.h`.

`main.c` is written using the same general structure as most module source files. At the top of the file are variable definitions, including configuration variables, which are given the same names as their corresponding configuration directives; these are followed by forward declarations of individual command routines and the command list. Next come database-related structures and routines, followed by the top-level pseudoclient routines (the "`introduce_user`", "`m_privmsg`", and "`m_whois`" callback functions), and finally the actual command routines, along with any utility routines needed.

For OperServ, the first item of note is the list of several commands and command routines inside `#ifdef DEBUG_COMMANDS`. These are, as the conditional name suggests, commands used for debugging Services, and are only available to the Services superuser; the commands are described in detail below.

OperServ stores several values to persistent storage, including the maximum client count, the time at which that maximum was reached, and the superuser (`SU` command) password. This data is stored by aggregating the data into a single structure, `operserv_data`, and storing that structure as a single database "record" in a table named "`oper`". Two exported functions, `get_operserv_data()` and `put_operserv_data()`, are also provided to allow external modules, in particular the XML import and export modules (see section 8-4), to access the data as well.

In order to check a client's Services privilege level (Services operator, Services administrator, or Services superuser), OperServ requires access to the nickname data, in which each registered nickname group's privilege level is stored (the `os_priv` member). However, since NickServ requires that OperServ be loaded first, OperServ must look up the symbols for these routines during its normal operation. Six local functions are defined, one for each of the imported routines (`get_nickinfo()`, `put_nickinfo()`, `_get_ngi()`, `put_nickgroupinfo()`, `first_nickgroupinfo()`, and `next_nickgroupinfo()`), taking the same parameters and returning the same values as the real routines; the local versions look up the symbol for each routine and then call the corresponding address, returning an appropriate error value if the symbol cannot be resolved (or NickServ is not loaded).

The main processing routine itself, `operserv()`, is registered as a callback function for the "`m_privmsg`" callback, called for each `PRIVMSG` received by Services. The routine first checks that the message is intended for OperServ; then it ensures that the client that sent the message is an IRC operator, to avoid any possibility of non-operator clients exploiting a bug in the OperServ code. The message received is then logged in the log file, except that parameters to the `SU` and `SET SUPASS` commands are replaced with a dummy string to avoid leaving the superuser password in the log file. (OperServ has no way to detect if one of these commands is misspelled, so for example, a mistaken `SET SUPSAS` will be logged in full, including the password.) Next, the command name is extracted from the message using the `strtok()` function; this also prepares `strtok()` for the command handler to use in extracting the command parameters (see below). After handling CTCP `PING` messages separately, OperServ calls a "`command`" callback, allowing other modules a chance to process the command first. Finally, if no callback function responds to the command, it is looked up in the command table and the command's handler function is called (if the command is not found, an error message is sent instead).

Following this and other callback functions are the privilege check functions; `is_services_root()`, `is_services_admin()`, and `is_services_oper()`, which check whether a client has Services superuser, Services administrator, and Services operator privileges respectively (any client with a higher privilege level is treated as having all lower privilege levels as well). The `is_services_root()` function relies on the `ServicesRoot` configuration setting, along with the `UF_SERVROOT` flag in the `User` structure indicating clients which have successfully used the `SU` command; lower privilege levels check the `os_priv` field of the nickname group data structure (see section 7-3-1) for privilege determination. These routines are exported, and used widely throughout the other pseudoclient modules to perform privilege checks on clients; in particular, they can be used as privilege check functions in the `has_priv` member of a `Command` structure. There is another routine, `nick_is_services_admin()`, which checks if a particular nickname can potentially can Services administrator access, ignoring whether the nickname is actually in use at the time; this is used by NickServ to prevent certain operations from being performed on such nicknames by clients without Services administrator privilege.

The command routines themselves are fairly straightforward. One thing to note is that the routines all obtain parameters via `strtok(NULL,...)` and `strtok_remaining()`; this relies on the fact that `operserv()` leaves the message string in the `strtok()` buffer after stripping the command name, so that each routine can parse the command's parameters as appropriate for that command. `strtok_remaining()` is used when the full remaining string is desired, such as when sending a global message; this function is preferred to `strtok(NULL,"")` because the latter can leave leading or trailing whitespace in the result.

The processing for the `HELP` command, in `do_help()`, is somewhat tortuous (although still simpler than in other pseudoclients) in order to give proper help responses depending on how Services is configured. In the case of OperServ, some commands may or may not be available depending on what submodules are loaded; the `COMMANDS` help text, which lists the available commands, is combined from several language strings depending on whether the appropriate modules are available to provide a list of the commands which are actually usable at that particular time. Other modules include more complex processing, such as checking the configuration variable values or protocol features. For commands that do not need such special-casing, the `help_cmd()` routine in the Services core (see section 2-10) sends a help message as defined by the `Command` structure.

The debug commands, defined toward the bottom of `main.c`, are as follows:

`LISTSERVERS` (`send_server_list()`)
  Sends a `NOTICE` for each server in the server list giving the contents of the `Server` structure.
`LISTCHANS` (`send_channel_list()`)
  Sends two `NOTICE`s for each channel in the channel list. The first `NOTICE` gives the channel name, creation time, modes (as a string), limit, key ("`-`") if none, and topic; the second contains each client on the channel along with that client's channel user modes on the channel. Any messages which exceed the maximum length of an IRC line are silently truncated.
`LISTCHAN channel` (`send_channel_info()`)
  Sends a `NOTICE` for each client on the given channel with the client's channel user modes (as a hexadecimal value) and nickname.
`LISTUSERS` (`send_user_list()`)
  Sends a `NOTICE` for each client on the network, giving the client's nickname and usermask, the "fake host" or "`-`" if none, the IP address or "`-`" if none, user modes as a string, signon timestamp (from the remote server), servicestamp, server name, nickname status flags or "`-`" if the nickname is not registered, ignore value, and real name field.
`LISTUSER nickname` (`send_user_info()`)
  Sends three `NOTICE`s describing the state of the given client. The first is identical to the line that `LISTUSERS` would output for the client; the second gives the channels which the client has currently joined; and the third gives the channels which the client has identified to ChanServ for.
`LISTTIMERS` (`send_timeout_list()`, in `timeout.c`)
  Sends a `NOTICE` giving the current time, followed by a `NOTICE` for each timeout currently in the list giving the timeout pointer, timestamp, function pointer, and function argument. This routine is defined in `timeout.c` because the internal timeout fields are hidden from other source files.
`MATCHWILD pattern string` (`do_matchwild()`)
  Sends a `NOTICE` giving the result of calling `match_wild` with the given parameters.
`SETCMODE [channel modes mode-params...]` (`do_setcmode()`)
  Calls `set_cmode()` with the given parameters, using `ServerName` as the message sender. If no parameters are given, calls `set_cmode(NULL,NULL)` to flush out all pending mode changes. Note that the number of mode parameters (including the mode string itself) is limited by the `SETCMODE_NPARAMS` macro, defined to 10 in the source code.
`MONITOR-IGNORE [nickname]` (`do_monitor_ignore()`)
  If a nickname is given, starts recording that nickname's ignore value to the log file at 100ms intervals; if no nickname is given, cancels any previous monitoring. Note that in order to ensure sub-second resolution, the `TimeoutCheck` configuration variable is set to 10 (milliseconds) when a nickname is given, potentially causing a reduction in performance; the old value is not restored when the command is given without a nickname.
`GETSTRING language string` (`do_getstring()`)
  Sends a `NOTICE` containing the text corresponding to the given string in the given language. Both `language` and `string` can be given as either names or raw numbers.
`SETSTRING language string [text]` (`do_setstring()`)
  Calls `setstring()` to set the given string in the given language to the given text. If no text is given, the string becomes empty.
`MAPSTRING old new` (`do_mapstring()`)
  Calls `mapstring()` to map one string to another. `old` and `new` are string names or numbers.
`ADDSTRING name` (`do_addstring()`)
  Calls `addstring()` to add a string with the given name to the language table. On success, sends a `NOTICE` with the new string number.

Back to top

#### 7-2-2. Usermask-related functions

##### 7-2-2-1. Common mask data support

One of the major features of OperServ not included in the core module is the autokill and S-line functionality. While these are in fact defined in separate modules, most of the processing for both sets of functions is subsumed under the `MaskData` structure and its generic processing routines included in the core OperServ module, located in `maskdata.c` and `maskdata.h`. This structure contains the elements common to all of these features: a mask string (whose interpretation is left to the particular module), an associated reason string, the nickname of the client that added the mask, the time the mask was added, the time it expires, and the last time the mask was applied to a client; these are stored in the `mask`, `reason`, `who`, `time`, `expires`, and `lastused` fields of the structure, respectively. The remaining fields (other than the record management fields `next`, `prev`, and `usecount`) are: `type`, containing an 8-bit value identifying the type of mask; `num`, giving the user-visible index number for session exception records (see section 7-2-3); and `limit`, used for the session limit in session exception records.

As mentioned above, each `MaskData` record has an associated type; more accurately, there are multiple sets of `MaskData` records, one set for each type (the `type` field is only used internally for loading and saving data from or to persistent storage, and does not need to be set by the caller). The available types, defined by the `MD_*` constants in `maskdata.h`, are:

- `MD_AKILL`: An autokill record.
- `MD_EXCLUDE`: An autokill exclusion record.
- `MD_EXCEPTION`: A session exception record.
- `MD_SGLINE`: An SGline record.
- `MD_SQLINE`: An SQline record.
- `MD_SZLINE`: An SZline record.

It is worth noting that (as also mentioned in section 7-2-2-3 below) the values chosen for `MD_SGLINE`, `MD_SQLINE`, and `MD_SZLINE` are the ASCII values of the characters `G`, `Q`, and `Z` respectively; this was done in order to simplify common code for all three types of S-lines, so that the type value could be used as is in messages sent to clients rather than having to make separate tests to determine the appropriate text to send. (For example, most of the language file messages for S-line actions use "`S%cLINE`" in the format string, where the `%c` is replaced by the type value.)

One other structure, `MaskDataCmdInfo`, can be found in `maskdata.h`; this structure collects information particular to a single `MaskData` type for use by the common command processing. The bulk of the structure consists of language string indices, which are used in sending responses to commands procesed by the common code. These are preceded by: `name`, the command name (such as "`AKILL`" or "`SGLINE`"); `type`, the record type to be used; and `def_expiry_ptr`, a pointer to a variable containing the default expiration period in seconds (a pointer is used so that any changes to the value can be recognized immediately without having to modify this structure as well). There are also five function pointers, all of which are optional (and should be set to `NULL` if not needed):

`void (*mangle_mask)(char *mask)`
  Makes any necessary changes to a mask before it is operated on. Any modifications may be made to the mask as long as they do not lengthen it beyond the original string length. Currently, this is used to force case-insensitive masks to lowercase, to avoid the possibility of multiple matching masks with differing case from being added to the database.
`int (*check_add_mask)(const User *u, uint8 type, char *mask, time_t *expiry_ptr)`
  Checks whether the given mask of the given type is allowed to be added with the given expiration time (in seconds from the present time, passed as a pointer); returns nonzero to allow the mask to be added, zero to deny it. The mask and expiration time may be modified by the function, provided that the mask is not lengthened beyond the original string length.
`void (*do_add_mask)(const User *u, uint8 type, MaskData *md)`
  Performs any extra actions necessary when a mask is added to the database, such as sending that mask to the network.
`void (*do_del_mask)(const User *u, uint8 type, MaskData *md)`
  Performs any extra actions necessary when a mask is removed from the database.
`int (*do_unknown_cmd)(const User *u, const char *cmd, char *mask)`
  Processes an unknown subcommand for the associated command, returning nonzero if the subcommand was handled, zero otherwise.

The function which implements the mask-related command support is `do_maskdata_cmd()`, defined below the database support functions in `maskdata.c`. This function behaves in the same way as a standard pseudoclient command handler, taking a single `User *` parameter giving the client that sent the command, and retrieving command parameters via `strtok(NULL,...)`. After obtaining the subcommand name and its parameters (with double-quote processing for masks), the routine checks for the known subcommands `ADD`, `DEL`, `CLEAR`, `LIST`, `VIEW`, `CHECK`, and `COUNT`, processing each appropriately. If the subcommand is not one of these, it is passed to the command's `do_unknown_cmd()` function; if that function returns zero or does not exist, an error is sent to the client.

The subcommands themselves are implemented by local routines: `do_maskdata_add()`, `do_maskdata_del()`, and so on. These check the validity of the parameters for the particular subcommand and perform the appropriate action, using the language string indices in the `MaskDataCmdInfo` structure to send replies to the client that issued the command.

There are also three utility functions defined after the command handler routines:

`void *new_maskdata()`
  Allocates, initializes, and returns a new `MaskData` structure. (The return value is `void *` to avoid a type warning in the database table declaration.)
`void free_maskdata(void *record)`
  Frees the `MaskData` structure pointed to by `record`, Does nothing if `record` is `NULL`.
`char *make_reason(const char *format, const MaskData *data)`
  Returns the reason string for the given mask and format string. If `format` contains a "`%s`", it will be replaced by the mask's reason string in the return value; otherwise, the returned string is identical to `format`. The result string is stored in a static buffer, which is overwritten by subsequent calls to `make_reason()`.

With respect to the database management routines, `MaskData` records are stored in variable-length arrays, one array for each of the 256 possible mask types. (Arrays were chosen over hash tables for simplicity; since the most common use of these records is searching all records of a particular type for one that matches a given string, there would be little benefit to the use of a hash table.) The `next` and `prev` fields are not ordinarily required for array handling, but the code stores the (integer) array index value in the `next` field via a cast to `void *`, allowing the code to know where in the array a given record is located without having to search through the array every time (note that the `num` field is used for a different purpose—index numbers for session exceptions—and is not available). Aside from the standard database operations, which take a `uint8 type` parameter in addition to the mask record itself, the following functions are included:

`MaskData *get_matching_maskdata(uint8 type, const char *str)`
  Searches for and returns (in the same manner as `get_maskdata()`, including expiration checks) a mask which matches the wildcard pattern given by `str`. If more than one mask matches, an arbitrary one is returned.
`MaskData *get_exception_by_num(int num)`
  Retrieves a mask of the `MD_EXCEPTION` (session exception) type by its index number.
`MaskData *move_exception(MaskData *except, int newnum)`
  Changes the index number of the given `MD_EXCEPTION` mask. (Internally, this reorders the mask array so that entries remain in order by index number.)

Back to top

##### 7-2-2-2. Autokills

As mentioned above, one of the main uses of this mask data code is the autokill module, `operserv/akill`, defined in `akill.c` and `akill.h`. While support for the OperServ `AKILL` and `EXCLUDE` commands simply makes use of the aforementioned `do_maskdata_cmd()` function, handling for the actual autokills and autokill exclusions themselves is defined within the autokill module. This includes:

- `send_akill()` and `cancel_akill()`, and their autokill exclusion companions `send_exclude()` and `cancel_exclude()`, which (via callbacks hooked into by protocol modules) take care of adding and removing autokills and exclusions on the network. Note that `cancel_akill()` and `cancel_exclude()` destroy their `char *` parameters, but as they are only called when deleting a record, this is not a problem.
- `do_user_check()`, which hooks into the "`user check`" callback to check whether a newly connecting client matches any active autokills or exclusions and take appropriate action.
- `create_akill()`, an exported function which creates a new autokill record given the usermask, reason, setter nickname, and time to expiration. (There is currently no equivalent function for autokill exclusions.)
- The data structure and helper functions for `do_maskdata_cmd()`. In particular, `check_add_akill` uses simple heuristics to check for masks that are so general that they would match any conceivable combination of username and hostname (for example, "`*@*`" or "`?*@*?.*?*`") and disallow such masks, in order to avoid situations where no one could connect to the network because every client matched the autokill.
- The `AKILLCHAN` command implementation, `do_akillchan()`. It is worth noting that (as commented in the code) there is a race condition that can allow the client to reconnect in the miniscule interval between disconnecting the client with a `KILL` command and adding an autokill for that client; this negligible risk was taken in light of the fact that doing it the other way (sending the autokill first) causes some IRC servers to automatically kill the client, and OperServ's `KILL` would cause a "user not found" message to be logged (which did in fact generate some complaints from users). However, once the client is killed, the username and hostname from the `User` structure are no longer available, so they must be saved ahead of time (the code at one point failed to do this, predictably resulting in crashes—hence the vitriolic comment about that mistake).
- The "`connect`" callback function, which sends all autokills to the network on initial connection.
- The "`expire_maskdata`" timeout function, which checks for autokill expiration. In order to prevent flooding of IRC operators, for example when autokills set by an `AKILLCHAN` command expire, expiration announcements via `WALLOPS` are (if enabled) only sent at the rate of one per second, and any further expirations are merged into a single "nnn more autokills have expired" message sent after all expirations are complete.
- The OperServ "`HELP`" callback function, which is required to display the `AKILL` help message correctly.
- The OperServ "`STATS ALL`" callback function, used to calculate and display autokill memory usage in response to a `STATS ALL` command.

One item of interest in the module setup code at the bottom of the source file is the handling of the `EXCLUDE` command. `EXCLUDE` can be enabled or disabled via a configuration file option (`EnableExclude`), for reasons discussed below. Rather than create two command tables, one with `EXCLUDE` and one without, the code simply modifies the `EXCLUDE` entry to have an empty command name if the command is disabled; since command names parsed by OperServ cannot be empty, the command will never be found. (In order to locate the entry again once the name has been cleared, a static variable is used, set during module initialization to point to the proper element of the command array. The name is restored during module cleanup so that a subsequent initialization will likewise be able to find it.)

The handling of autokill exclusions is trickier than it may seem at first glance, due to protocols which support network-wide autokill masks but not exclusion masks. If one takes the naive approach of simply adding autokill masks as usual, one will find that the effectiveness of the autokill exclusions is severely limited. For example, consider a network with an autokill for `*@*.example.com` and an exclusion for `*@oper.example.com`. As long as the only users in the `example.com` domain to connect are from the `oper.example.com` host, the exclusion will function as expected. However, as soon as a user from another `example.com` host connects, the autokill will be triggered and sent out to the network—with the unintended result that even users from `oper.example.com` are prevented from connecting, since Services has no way to inform other servers on the network about the autokill exclusion.

In order to avoid this problem, the `operserv/akill` module will not make use of a protocol's autokill features if that protocol does not also support autokill exclusions, and will simply send out a `KILL` message for each user to be disconnected from the network. However, this may not be desirable for networks that have no intention of using the autokill exclusion functionality. For this reason, the `EnableExclude` configuration option was added, allowing such networks to choose between taking advantage of the protocol's autokill feature and using exclusions with autokills.

Back to top

##### 7-2-2-3. S-lines

S-lines are the other primary user of the mask data code. These sets of client restriction masks (SGlines, SQlines, and SZlines) are implemented by the `operserv/sline` module, defined in `sline.c` and `sline.h`.

The S-line module is very similar to the autokill module, both functionally and internally (in fact, `sline.c` started out as a copy of `akill.c`). The primary difference is in the sharing of data and code between the three mask types. Since the command names differ only in a single character, the code takes the shortcut of using that character as the mask type for use with the `MaskData` support functions—this is the reason for the warning comment in `maskdata.h` about changing the type values—and including a `%c` token in relevant messages which is replaced by the mask type. This enables a single message, such as "`%d masks on the S%cLINE list.`", to be shared by code for all three sets of masks. Several functions are likewise shared by the three mask types, taking a type parameter to select which data set to operate on.

Naturally, each type of mask is interpreted differently, so the actual processing for each type is handled separately. SQlines, in particular, are checked by the utility routine `check_sqline()`, called from the callback functions `do_user_check()` and `do_user_nickchange_after()`, because there are several possible ways of handling a match:

- If the client is an IRC operator and the `SQlineIgnoreOpers` configuration option is enabled, then SQlines are not checked at all. (The `new_oper` parameter is required because, when called from `do_user_check()`, the `User` record will not yet have been created.)
- If the SQline is for a guest nick, the client is left alone, though the SQline itself is sent to the network.
- If the `SQlineKill` configuration option is not set and the protocol in use supports forced nickname changing, the client's nickname is changed to a guest nickname, and the SQline is sent to the network.
- Otherwise, the client is killed, and the SQline is sent to the network.

Back to top

#### 7-2-3. Session limiting

The last method of client control, session limiting, is implemented by the `operserv/sessions` module, defined in `sessions.c`. The module consists of two major parts: the actual session maintenance and limiting code, and session exception mask management via the `EXCEPTION` command.

The session maintenance functionality is contained within the `add_session()` and `del_session()` routines, called from callback functions when clients join or leave the network, respectively. These routines keep track of each host with one or more clients on the network by means of a hash table containing `Session` structures, one per host. When `add_session` is called, it looks up the `Session` record for the new client's hostname, creating a new record with a count of zero if no record for that host already exists, then increments the host's client count by one; conversely, `del_session()` decrements the host's client count, deleting the `Session` structure when the count reaches zero.

The "limiting" part of session limiting is a check in `add_session()` to determine whether the host has "too many" clients on the network. Here, "too many" is defined by either the limit given in an exception record (see below) or the default limit given in the `DefSessionLimit` configuration directive; if adding the new client would cause the host's client count to exceed the relevant limit, then the client is killed (any clients already connected are left alone). An autokill can also be added for thte host depending on the configuration settings.

The module also provides a `SESSION` command to allow Services operators to view the contents of the session list; this can be used to find hosts from which a large number of clients are connecting. The `SESSION` command is available even if actual limiting of sessions is disabled (by setting `DefSessionLimit` to zero).

The second part of the module, session exceptions, allows fine-tuning of the default limit on client connections. For example, it may be desirable to allow extra connections from a particular host known to be used by IRC operators. Such exceptions to the default session limit are stored using `MaskData` structures with the `MD_EXCEPTION` type; each record is treated as a wildcard pattern against which a connecting client's hostname is matched, and if a match is found, that limit is used instead of the default, as described above. If more than one matching session exception exists, the first one in the list (from a user's point of view, the one with the lowest index number) is used.

Session exceptions are maintained using the `EXCEPTION` command. The processing for this command, in `do_exception()` and its subroutines, is very similar to other mask-type commands like `AKILL`; however, session exceptions require a limit value in addition to the mask, and it was considered simpler to keep the handling for `EXCEPTION` separate rather than modify `do_maskdata_cmd()` to handle both command formats. The `EXCEPTION` command also includes a `MOVE` subcommand, allowing one record to be moved relative to another within the list (so, for example, a newly-added exception can be moved earlier in the list to take precedence over other exceptions).

A careful look at the initialization code will show that the "`user check`" callback function, `check_sessions()` (which in turn calls `add_session()`), is added at a priority of -10. This is in order to ensure that the function is called after all other checks have been performed, as described in section 4-5-2 (and is a poor design choice for the reasons described there).

Back to top

#### 7-2-4. News

The last OperServ submodule is the `operserv/news` module, defined in `news.c` and `news.h`. This is a fairly simple module, containing routines to handle news item storage and implement the `LOGONNEWS` and `OPERNEWS` commands.

News items are stored in a single variable-length array of `News` structures, `newslist`. The `News` structure contains, aside from the text of the news item itself, the news type (`NEWS_LOGON` or `NEWS_OPER`), an index number used when deleting the item, the nickname of the client that added the item, and the time the item was added. As with `MaskData` structures, `next` and `prev` fields are included only to mirror other structures, and the array index is stored in the `next` field.

The two news commands, `LOGONNEWS` and `OPERNEWS`, share the same code, `do_news()`; this routine is called by the actual command handlers with one of the news type codes, either `NEWS_LOGON` or `NEWS_OPER`, and accesses an array of language string indices (`msgarray[]`) to return proper messages for each command, similar to `do_maskdata_cmd()`. Unlike most other commands (but like the `OPER` and `ADMIN` commands in the OperServ core), the news commands include a `LIST` command available to all IRC operators, and other subcommands (`ADD` and `DEL`) restricted to Services operators; thus, the command handler must perform privilege checks on its own rather than specifying a privilege check routine in the command table.

Back to top

### 7-3. NickServ

NickServ is typically the first pseudoclient IRC users interact with. It was also the first pseudoclient created during Services' initial design, on which all other pseudoclients were based. The current NickServ is divided into a core module and four submodules implementing additional features; each module is described in its own section below.

Back to top

#### 7-3-1. NickServ core functionality

The core NickServ functionality is implemented by the `nickserv/main` module. Alongside the primary module source file, `main.c`, the module makes use of three additional source files: `collide.c`, handling the disconnection or forced removal of clients using unauthorized nicknames; `set.c`, implementing the `SET` command and its subcommands; and `util.c`, containing various utility routines used by NickServ.

NickServ makes use of two distinct header files. One, `nickserv.h`, defines the data structures used for storing nickname information (`NickInfo` and `NickGroupInfo`, described below) along with declarations of exported routines and macros used with nickname records. The other, `ns-local.h`, contains declarations of routines used within NickServ, necessarily declared `extern` because they reside in separate source files but not intended to be used outside the NickServ modules.

Back to top

##### 7-3-1-1. Nickname data structures and utility macros

Two separate structures are used to store nickname data; this is to facilitate the implementation of nickname links, as described in section 7-3-4. One structure, `NickInfo`, contains data that is distinct for each individual nickname; the other, `NickGroupInfo`, contains data that is shared among each group of linked nicknames.

The `NickInfo` structure includes the following data:

`NickInfo *next, *prev`
  Used to link records together in the internal hash table.
`int usecount`
  The record's usage count (number of gets minus number of puts).
`char nick[NICKMAX]`
  The actual nickname. Capitalization is as used when the nickname was registered, and does not change due to later actions. The buffer size, `NICKMAX`, is defined in the global header file `defs.h`.
`int16 status`
  The nickname's status. This is a combination of zero or more of the following flags:
  - `NS_VERBOTEN`: The nickname is a forbidden nickname set with the `FORBID` command. ("Verboten" is German for "forbidden", but there is no particular meaning behind this choice other than a whim of the developer as he was writing the code.)
  - `NS_NOEXPIRE`: The nickname is not to be expired regardless of how long it remains unused (`SET NOEXPIRE`).
  - `NS_KILL_HELD`: The nickname is currently being held by an "enforcer" pseudoclient after killing (or changing the nickname of) a client that was using the nickname without permission.
  - `NS_GUESTED`: An IRC message has been sent to change the nickname of the client using the nickname, but the nickname change has not yet occurred.
  Of these flags, `NS_VERBOTEN` and `NS_NOEXPIRE` are "permanent" flags (collected in the `NS_PERMANENT` mask), which are retained across restarts of Services, while `NS_KILL_HELD` and `NS_GUESTED` are "temporary" flags (collected in the `NS_TEMPORARY` mask), which are cleared each time the database is loaded from persistent storage. Note that the value 0x0001 (bit 0) is not used because it served a separate purpose in previous versions of Services.
`char *last_usermask`
  The last `user@host` mask used by the owner of the nickname (i.e., a client authorized to use the nickname). If the owner is currently online, that client's `user@host` mask is used. On IRC networks where a "fake hostname" is available, that hostname is used instead of the client's actual hostname.
`char *last_realmask`
  Like `last_usermask`, but uses the real hostname instead of any "fake hostname". On networks without such a fake hostname, this field is identical to `last_usermask`.
`char *last_realname`
  The last "real name" string used by the owner of the nickname.
`char *last_quit`
  The message used the last time the owner quit IRC. `NULL` if not available, such as for newly-registered nicknames.
`time_t time_registered`
  The timestamp when the nickname was registered.
`time_t last_seen`
  The timestamp at which the owner most recently used the nickname. Only updated when the owner stops using the nickname; if the nickname is currently in use, this field should not be relied on.
`uint32 nickgroup`
  The ID of the nickname group with which this nickname is associated (see below).
`uint32 id_stamp`
  The servicestamp of the client that last identified for the nickname. Used to retain identification status across Services restarts.
`User *user`
  Not saved to persistent storage. A pointer to the `User` structure for the client currently using the nickname, or `NULL` if the nickname is not currently in used.
`int16 authstat`
  Not saved to persistent storage. Zero or more of the following flags, indicating the nickname's authentication status:
  - `NA_IDENTIFIED`: The current user of the nickname has identified by password as the nickname's owner. Mutually exclusive with `NA_IDENT_NOEMAIL`.
  - `NA_IDENT_NOMAIL`: The current user of the nickname has identified by password as the nickname's owner, but has not registered an E-mail address with the nickname when one is required. Mutually exclusive with `NA_IDENTIFIED`.
  - `NA_RECOGNIZED`: The current user of the nickname is recognized via the nickname access list (see section 7-3-2).
`int bad_passwords`
  Not saved to persistent storage. The number of consecutive failed password identification attempts for the nickname. Used to determine whether or not to kill a client for attempted password cracking.

The `NickGroupInfo` structure includes the following data:

`NickGroupInfo *next, *prev`
  Used to link records together in the internal hash table.
`int usecount`
  The record's usage count (number of gets minus number of puts).
`uint32 id`
  The nickname group's ID, a unique 32-bit value. Typically, this value is randomly assigned. The value zero is not allowed (it is used in `NickInfo` records to indicate the lack of an associated nickname group, as for forbidden nicknames).
`nickname_t *nicks`
`uint16 nicks_count`
  Not saved to persistent storage. A variable-length array containing the nicknames associated with this nickname group. Used for convenience, to avoid having to search through the nickname database every time a list of nicknames is needed.
`uint16 mainnick`
  The "main nickname" for the group, used to represent the nickname group in things such as channel access lists. Specified as an index into the `nicks[]` array.
`Password pass`
  The password used for identification for the nickname group.
`char *url`
  A URL associated with the nickname group. Can be arbitrarily set by the owner.
`char *email`
  An E-mail address associated with the nickname group. Can be arbitrarily set by the owner, and may be required at registration time by setting the `NSRequireEmail` configuration option.
`char *last_email`
  When mail-based authentication (see section 7-3-5) is in use, this field is set to the previous contents of the `email` field when the owner changes the E-mail address, and is cleared when the new address is authenticated; this allows the `RESTOREMAIL` command to function.
`char *info`
  A free-form text string associated with the nickname group. Can be arbitrarily set by the owner.
`int32 flags`
  A bitmask containing zero or more of the following nickname group flags:
  - `NF_KILLPROTECT`: NickServ should prevent other clients from using the nickname by either killing them or changing their nicknames, depending on the IRC protocol in use (`SET KILL ON/QUICK/IMMED`).
  - `NF_SECURE`: NickServ should require password identification for the nickname even if the client is on the nickname's access list (`SET SECURE`).
  - `NF_MEMO_HARDMAX`: The client is not permitted to change the nickname's memo limit (MemoServ `SET LIMIT HARD`).
  - `NF_MEMO_SIGNON`: MemoServ should inform the client of new memos at signon (MemoServ `SET NOTIFY ON/LOGON`).
  - `NF_MEMO_RECEIVE`: MemoServ should inform the client of new memos when they are sent (MemoServ `SET NOTIFY ON/NEW`).
  - `NF_PRIVATE`: The nickname is hidden from the `LIST` and `LISTEMAIL` command output, except when used by Services administrators (`SET PRIVATE`).
  - `NF_HIDE_EMAIL`: The nickname's E-mail address is hidden from the `INFO` and `LISTEMAIL` command output, except when used by Services administrators (`SET HIDE EMAIL`).
  - `NF_HIDE_MASK`: The nickname's `user@host` mask is hidden from the `INFO` and `LIST` command output, except when used by Services administrators (`SET HIDE USERMASK`).
  - `NF_HIDE_QUIT`: The nickname's last quit message is hidden from the `INFO` command output, except when used by Services administrators (`SET HIDE QUIT`).
  - `NF_KILL_QUICK`: NickServ should allow only 20 seconds instead of 60 for an unauthorized client to change nickname (`SET KILL QUICK/IMMED`).
  - `NF_KILL_IMMED`: NickServ should kill or nickchange unauthorized clients immediately with no grace period (`SET KILL IMMED`).
  - `NF_MEMO_FWD`: MemoServ should forward received memos to the nickname's E-mail address (MemoServ `SET FORWARD ON/COPY`).
  - `NF_MEMO_FWDCOPY`: MemoServ should save copies of forwarded memos (MemoServ `SET FORWARD COPY`).
  - `NF_SUSPENDED`: The nickname group is suspended (`SUSPEND`).
  - `NF_NOOP`: ChanServ should prevent the nickname from being added to channel access lists (`SET NOOP`).
  Note that the value 0x00000004 (bit 2) is not included in the above flags because it served a separate purpose in previous versions of Services. Instead, this value is used as a temporary flag (`NF_NOGROUP`) when loading databases from earlier versions of Services, to indicate that a nickname group does not yet have an ID value assigned.
`int16 os_priv`
  The nickname group's privilege level with respect to OperServ. Can be any value, but typically either zero (no special privileges) or one of the following values:
  - `NP_SERVOPER`: Services operator privilege.
  - `NP_SERVADMIN`: Services administrator privilege.
  Other values are treated by the OperServ privilege checking code (see section 7-2-1) as having the next lowest recognized value.
`int32 authcode`
  The authentication code set for the nickname group, or zero if no code is set. See section 7-3-5.
`time_t authset`
  The timestamp when the nickname group's authentication code was set (meaningless if no code is set).
`int16 authreason`
  The reason the nickname group's current authentication code was set (meaningless if no code is set). One of the following constants:
  - `NICKAUTH_REGISTER`: The nickname was newly registered, and the E-mail address provided in the `REGISTER` command requires authentication.
  - `NICKAUTH_SET_EMAIL`: The nickname group's E-mail address was changed with the `SET EMAIL` command, and the new address requires authentication.
  - `NICKAUTH_SETAUTH`: Authentication is required as the result of a Services administrator using the `SETAUTH` command.
  - `NICKAUTH_REAUTH`: Authentication is required as the result of the nickname owner using the `REAUTH` command.
`char suspend_who[NICKMAX]`
  The nickname of the client that suspended the nickname group (meaningless if the `NF_SUSPENDED` flag is not set).
`char *suspend_reason`
  The reason the nickname group was suspended (meaningless if the `NF_SUSPENDED` flag is not set).
`time_t suspend_time`
  The timestamp when the nickname group was suspended (meaningless if the `NF_SUSPENDED` flag is not set).
`time_t suspend_expires`
  The timestamp at which the nickname group's suspension expires (meaningless if the `NF_SUSPENDED` flag is not set).
`int16 language`
  The language preferred for messages sent to the nickname group. One of the `LANG_*` constants in the Services core's `language.h`.
`int16 timezone`
  The time zone offset specified by the nickname group's owner for use in displaying times. A number of minutes (possibly negative) to be added to the UTC timestamps, or `TIMEZONE_DEFAULT` to use the Services process' default.
`int16 channelmax`
  The maximum number of channels the nickname group is allowed to register, `CHANMAX_UNLIMITED` for no limit, or `CHANMAX_DEFAULT` for the default limit (set by the ChanServ `CSMaxReg` configuration setting).
`char **access`
`int access_count`
  The nickname group's access list (see section 7-3-2).
`char **ajoin`
`int ajoin_count`
  The nickname group's auto-join list (see section 7-3-3).
`char **ignore`
`int ignore_count`
  The nickname group's memo ignore list (see section 7-5-2).
`MemoInfo memos`
  The nickname group's stored memos (see section 7-5-1).
`channame_t *channels`
`int channels_count`
  Not saved to persistent storage. The names of the channels currently registered by this nickname group.
`User **id_users`
`int id_users_count`
  Not saved to persistent storage. Pointers to `User` structures for clients which have identified for this nickname group.
`time_t last_sendauth`
  Not saved to persistent storage. The timestamp when the `SENDAUTH` command was last used for this nickname group (see section 7-3-5).
`int bad_auths`
  Not saved to persistent storage. The number of times the `AUTH` command has been used with a bad authentication code for this nickname group (see section 7-3-5).

In addition, `nickserv.h` declares the following convenience functions and macros:

`int nick_recognized(const NickInfo *ni)`
`int user_recognized(const User *u)`
  Returns whether the given client is recognized via an access list entry, regardless of whether the client has identified for the nickname group or not. The client can be specified by either `NickInfo` or `User` structure. (Nickname authorization flags are always cleared when a client disconnects from the network, so the `NickInfo` form will always return false if used on a nickname not currently in use.)
`int nick_identified(const NickInfo *ni)`
`int user_identified(const User *u)`
  Returns whether the given client has identified for its nickname. The client can be specified by either `NickInfo` or `User` structure.
`int nick_id_or_rec(const NickInfo *ni)`
`int user_id_or_rec(const User *u)`
  Returns whether the given client is recognized via access list or has identified for its nickname (or both). The client can be specified by either `NickInfo` or `User` structure.
`int nick_ident_nomail(const NickInfo *ni)`
`int user_ident_nomail(const User *u)`
  Returns whether the `NA_IDENT_NOMAIL` flag is set for the given client; i.e., evaluates to true when has identified for its nickname but the nickname lacks a required E-mail address. The client can be specified by either `NickInfo` or `User` structure.
`int ngi_unauthed(const NickGroupInfo *ngi)`
  Returns whether the given nickname group (more accurately, the nickname group's E-mail address) is unauthenticated. Note that use of the `REAUTH` command does not cause the nickname group to lose its authenticated status.
`int valid_ngi(const NickGroupInfo *ngi)`
  Returns whether the given `NickGroupInfo` structure pointer is valid; i.e., evaluates to true when `ngi` is neither `NULL` nor `NICKGROUPINFO_INVALID`. (The latter value is used in `User` structures to indicate that the client has an associated `NickInfo` structure but no `NickGroupInfo` structure, as is the case for forbidden nicknames.)
`const char *ngi_mainnick(const NickGroupInfo *ngi)`
  Returns the given nickname group's main nickname.
`NickGroupInfo *get_ngi(const NickInfo *ni)`
`NickGroupInfo *get_ngi_id(uint32 id)`
  Retrieves the `NickGroupInfo` record corresponding to the given `NickInfo` or ID value; if there is no corresponding record, a warning message is logged and `NULL` is returned. (Like any other database "get" routine, the structure must be "put" with `put_nickgroupinfo()` when no longer needed.)
`int check_ngi(const NickInfo *ni)`
`int check_ngi_id(uint32 id)`
  Returns whether there is a corresponding `NickGroupInfo` record for the given `NickInfo` or ID value, logging a warning message if not. (The `put_xxx(get_xxx(...))` is a common way of checking for the existence of a record, since the `put_xxx()` explicitly allow a `NULL` parameter.)

The `STANDALONE_NICKSERV` define and (non-macro) utility functions are discussed in section 7-3-1-4.

Back to top

##### 7-3-1-2. Overall module structure

The overall structure of the NickServ module generally follows the same pattern as the OperServ module: variable and command declarations, database handling, `PRIVMSG` and other callbacks, and command routines.

The `nickserv/main` module uses two separate databases, one for `NickInfo` records and one for `NickGroupInfo` records. The database handling code is more or less straightforward, using `hash.h` to maintain the in-memory tables; however, since most of the record management routines take additional actions (for example, the "add" and "get" functions update the record's use count), the base hash functions are defined with a trailing underscore, like `add_nickinfo_()`, and the actual functions (like `add_nickinfo()`) are wrapped around these. NickServ exports all of the `NickInfo` and `NickGroupInfo` database functions, as well as a "put" function for each record type.

When saving the databases to persistent storage, the `nicks[]` array and `mainnick` field of `NickGroupInfo` records are not saved directly; rather, the main nickname itself is saved as a `NICKMAX`-sized buffer, and the nickname group is initialized with this nickname when first loaded (the array is subsequently filled in when the relevant `NickInfo` records are loaded).

NickServ keeps track of clients' status using callback functions for new clients, clients changing nickname, and disconnecting clients. The routines that do the actual processing, `validate_user()` (for a client starting to use a nickname) and `cancel_user()` (for a client no longer using a nickname), are located in `util.c`, discussed in section 7-3-1-4.

The NickServ commands themselves tend to be fairly complex, especially when compared to the OperServ command handlers. This is in part due to the wide range of features available in NickServ, and in part due to the fact that NickServ and its system of registered nicknames are the primary way by which clients authenticate themselves, and as such must handle a variety of circumstances to maintain security, while other pseudoclients simply rely on the authorization flags set by NickServ. The following command handlers are particularly worthy of note:

`do_help()`
  NickServ's commands include a number whose help text changes based on factors such as the requesting client's IRC operator status, features available in the IRC protocol in use, and NickServ configuration settings. The code to handle help requests is accordingly complex, with many commands unable to rely on the `help_cmd()` routine.
`do_register()`
  Just as NickServ is the gateway to the rest of Services' functions, the `REGISTER` command is the gateway to NickServ, providing one of the two methods by which a client can gain access to Services (the other being authentication to a previously registered nickname). The `REGISTER` handler must therefore be particularly careful to guard against abuse, both to prevent improper access to other Services commands and to prevent the `REGISTER` command itself from being abused by arbitrary clients. The command handler takes the following precautions before allowing a nickname to be registered:
  - Prevents the command from being used by any particular client more than once every `NSRegDelay` seconds. This stops mass-registration of nicknames by automated clients, avoiding both the accompanying load on Services itself (more memory usage, more time spent looking up nicknames) and any undesirable side effects, such as the sending of automated E-mail to arbitrary address when mail authentication is in use (see section 7-3-5).
  - Prevents the command from being used by a client within `NSInitialRegDelay` seconds of connecting to the network. This prevents automated clients from getting around the `NSRegDelay` limitation by repeatedly connecting, issuing a `REGISTER` command, and disconnecting in rapid succession. (It is still possible to avoid the limitation by connecting a large number of clients at once, but as a practical matter it is impossible for NickServ to distinguish such attempts from ordinary registration requests, and the sudden presence of a large number of clients on the network should itself be an indication of trouble.)
  - Prevents "guest" nicknames from being registered, which could result in unauthenticated clients suddenly gaining Services access after a forced nickname change. (The check itself is performed by the `do_reglink_check()` earlier in `main.c`, a callback function attached to the "`REGISTER/LINK check`" callback; `do_register()` calls this callback via the `reglink_check()` function in `util.c`.)
  - Ensures that the nickname is not already registered. Ordinarily, if a nickname is registered then the client's `User` structure will have a pointer to the record in its `ni` field, but if the nickname is missing a corresponding nickname group (a database error unless the nickname is forbidden), the `ni` field will be `NULL` and the `ngi` field will be set to the constant `NICKGROUPINFO_INVALID`, so that combination is checked for as well. Also, just in case, `do_register()` performs a final check by accessing the database directly, to ensure that the nickname is not registered in duplicate.
  - Checks that the E-mail address, if given, is (1) syntactically valid and (2) not disallowed due to a `RejectEmail` configuration directive.
  - Prevents more than `NSRegEmailMax` nicknames from being registered to the same address, again to avoid undue load on Services from a registration flood. This check calls `count_nicks_with_email()` to actually count nicknames; this routine has to search the entire nickname database, which can take a significant amount of time if many nicknames are registered, so this check is performed last.
  - As an adjunct to the previous check (and regardless of the setting of `NSRegEmailMax`), also prevents a nickname from being registered if the E-mail address given is already in use by another nickname which is awaiting mail authentication; this acts as a further guard to prevent a particular mail address from getting "mailbombed" by multiple registration requests that make it through the previous checks.
`do_dropemail()`
`do_dropemail_confirm()`
  The `DROPEMAIL` and `DROPEMAIL-CONFIRM` commands are the only two commands in Services that require state to be kept specifically for those commands. Due to the potential for data loss through an erroneous `DROPEMAIL` command, some form of confirmation was desired, such as an "Are you sure?" requester in response to a user deleting a file in a GUI. Since Services' interface is limited to single-line commands, however, this can only be accomplished through two commands, the second of which (`DROPEMAIL-CONFIRM`) serves to confirm the action requested by the first (`DROPEMAIL`). In order for this to be effective, the `DROPEMAIL-CONFIRM` handler must know which commands have been sent by whom, so that clients cannot send arbitrary `DROPEMAIL-CONFIRM` commands to get around the confirmation check. This is accomplished through the file-local `dropemail_buffer[]` array, which holds the most recently issued, unconfirmed `DROPEMAIL` commands. (A single state record stored in the `User` structure was another possibility, but one that was discarded to avoid bloat in that structure, particularly since the vast majority of clients would never use the command anyway.) When a valid `DROPEMAIL` command is given, the client is told the number of nicknames that would be deleted, and the given mask is stored in the buffer array, with the oldest unconfirmed mask removed if no slots are empty. A `DROPEMAIL-CONFIRM` for the same mask will then locate the appropriate buffer slot, ensure that the same client sent both commands and that the elapsed time between the two commands is not too long (as defined by the `NSDropEmailExpire` option), and performs the actual nickname deletion.
`do_info()`
  The `INFO` command has the ability to show extended information about a nickname with the option `ALL` (only available to the nickname owner or Services administrators). However, not all nicknames have any additional information to be displayed. To prevent the "use `ALL` for more information" message from being appended if there is not actually anything else to show, the `INFO` command handler uses a method inspired by superuser privilege checks in the Linux kernel, which keeps track of whether a process has taken advantage of those privileges. When the macro `CHECK_SHOW_ALL` is included in a conditional test, it will evaluate to true when the `ALL` option is present (and the client has permission to use it), but a separate flag variable, `used_all`, will also be set regardless of the presence of `ALL`; the routine can then determine whether there were any items that would have been displayed if `ALL` was given. As noted in the source code comments, the macro should be the last test in any conditional expression which uses it, to prevent `used_all` from being set for an item that will not actually be displayed due to a subsequent test.
`do_set()`
`do_unset()`
  As the `SET` and `UNSET` commands (`SET` in particular) have a large number of options, they are defined in a separate source file, `set.c`. See section 7-3-1-3 for details.

NickServ also includes a debug command enabled by the `DEBUG_COMMANDS` preprocessor symbol: `LISTNICK`, which displays the `NickInfo` and (if present) `NickGroupInfo` data for a given nickname.

In addition to the ordinary module setup code, the `nickserv/main` module supports two command-line options. One, `-encrypt-all`, is recognized by the Services core; NickServ's `init_module()` routine checks the corresponding global flag, `encrypt_all` and, if it is set, encrypts all nicknames using the encryption type specified by the core's `EncryptionType` setting. The other option, `-clear-nick-email`, is NickServ-specific, and is handled by `do_command_line()`, a callback function for the core's "`command line`" callback; when the option is encountered, the callback function clears the E-mail address from all nickname groups.

As several messages used by NickServ can change based on configuration options or the features available in the IRC server, the initialization and cleanup code (as well as the reconfiguration handler, `do_reconfigure()`) call `mapstring()` to adjust the messages appropriately. The commands `REGISTER`, `DROPEMAIL`/`DROPEMAIL-CONFIRM`, and `GETPASS` can also be disabled by configuration options; the commands are disabled by setting the `name` field of the corresponding `Command` structure to the empty string, so that it will not be found when the command table is searched. Pointers to the structures are saved in file-local variables so that the names can be restored at reconfiguration or module cleanup time.

Back to top

##### 7-3-1-3. The `SET` and `UNSET` commands

The handlers for the `SET` and `UNSET` commands, `do_set()` and `do_unset()` in `set.c`, work much like miniature versions of the top-level NickServ message handler `nickserv()`, in that they check which option name was used with the command and call an appropriate subroutine to do the actual work. However, the `do_set()` routine parses the option parameters itself, rather than leave such parsing to the individual routines (`UNSET` does not take any additional parameters, so no such parsing is needed); for this reason, the setup code in `do_set()` is more complex than that in `nickserv()`, since the `INFO` option treats the entire line as a single parameter, `HIDE` takes two single-word parameters separated by a space, and the other options take a single one-word parameter.

Additionally, both `SET` and `UNSET` can be used by Services administrators to set options for other users' nicknames. For this reason, the individual option-setting routines take both a `User *` and a `NickInfo *` parameter, where the `NickInfo *` parameter is the nickname whose options are to be changed (if the client giving the command is not a Services administrator or does not give a target nickname, this will simply be equal to the `ni` field of the `User` structure). Implementation note: This raises an interesting problem—how does an option's handler routine tell the difference between `SET option`, `SET !MyNick option`, and `SET !OtherNick option` when sending result messages? The simple answer is that it doesn't: all option handlers use the "your nick" message style, as mentioned in Appendix D of the user's manual. If implemented, it would probably be reasonable to ignore the distinction between the `SET option` and `SET !MyNick option` cases, and simply judge which message to use by comparing the `NickInfo` parameter with the `ni` field of the client's `User` structure.

The option handlers themselves are simple for the most part, checking the option value given and setting or clearing the relevant flag or field in the `NickInfo` structure or its associated `NickGroupInfo` structure. Routines which deserve special mention are:

`do_set_password()`
  The password setting itself is straightforward (note that the memory containing the cleartext password and the temporary copy of the encrypted password is cleared as soon as it is no longer needed); however, the routine first checks the `NSSecureAdmins` option, and disallows the change if the target is a (different) Services administrator and the command sender is not the Services superuser.
`do_set_email()`
  This routine makes several checks mostly related to mail authentication before allowing the E-mail address to be changed:
  - The address must be a valid E-mail address.
  - The address must not be rejected by a `RejectEmail` configuration directive.
  - The address must not be in use by a nickname awaiting mail authentication (as with `REGISTER`).
  - The number of nicknames currently using the address must be less than `NSRegEmailMax`, if set. (Note that if the current nickname's group contains other linked nicknames, the E-mail address change can cause the nickname total to exceed `NSRegEmailMax`. This is not seen as a significant problem, and it avoids the opposite problem in which a user who somehow exceeded the limit would no longer be able to change their E-mail address at all.)
  - The time since the last successful `SET EMAIL` must be at least `NSSetEmailDelay` seconds, if set.
  If the above checks all pass, the change is performed, and if the client used to have the `NA_IDENT_NOMAIL` status and an E-mail address was set, the status is changed to `NA_IDENTIFIED`. The routine also features its own callback, "`SET EMAIL`", used by the mail authentication code. Note that this routine does not check the `NSRequireEmail` configuration option, and assumes that if it is passed a `NULL` value, indicating that the address should be unset, then that is valid. (In fact, `do_unset()` checks `NSRequireEmail` before calling `do_set_email()`.)
`do_set_timezone()`
  `SET TIMEZONE` allows the time zone to be specified as either a literal time offset (-5, +6:30, etc.) or a time zone name. Time zone names (other than "GMT+/-n" and "UTC+/-n", which are treated as literal offsets) are parsed using the `timezones[]` table defined just above the `do_set_timezone()` routine itself, which (hopefully) includes most common time zones; the table can of course be modified to include other time zones as particular networks desire. Once the the time zone has been set, a message is sent to the calling client giving the current time in the resulting time zone; however, this is tricky if the calling client is a Services administrator changing the setting for another nickname, because `strftime_lang()` always uses the time zone setting of the nickname used to select the language. To get around this, the routine determines the difference between the calling nickname's time zone and the target nickname's time zone, adjusting the timestamp passed to `strftime_lang()` by that amount (multiplied by 60, since the `timezone` field is specified in minutes). Incidentally, support for "daylight saving time" as used in some countries was deliberately omitted, partly due to the difficulty of supporting the various systems used in different countries, and partly because the details of such systems are highly dependent upon each country's political landscape and can change at any time (witness the abrupt extension to DST proposed, and eventually implemented, in the United States of America in 2006).

Back to top

##### 7-3-1-4. NickServ utility routines

Most of the utility routines used by NickServ are collected in the file `util.c`. This file has two functions: aside from providing utility functions to NickServ itself (several of which are exported for use by other modules), it can also be `#include`'d in an external source file to provide definitions of the four routines `new_nickinfo()`, `free_nickinfo()`, `new_nickgroupinfo()`, and `free_nickgroupinfo()`, so that such files do not have to define similar routines themselves. This latter mode is activated by defining the `STANDALONE_NICKSERV` preprocessor symbol, as documented in the comments at the top of `util.c`. In this case, only the four routines mentioned above are defined, with the rest of the file commented out using `#ifndef`; additionally, the `new_nickgroupinfo()` routine does not check for the presence of the nickname group IDs it generates, as it cannot assume that `get_nickgroupinfo()` is available.

With respect to its primary use as part of NickServ, `util.c` defines the following routines:

`NickInfo *new_nickinfo()`
  Returns a pointer to a newly-allocated and initialized `NickInfo` structure. (For creating a new record in the database, `makenick()` is preferred; see below.)
`void free_nickinfo(NickInfo *ni)`
  Frees the given `NickInfo` structure and all associated data. (See `delnick()` below for removing a nickname record from the database.)
`NickGroupInfo *new_nickgroupinfo(const char *seed)`
  Returns a pointer to a newly-allocated and initialized `NickGroupInfo` structure. If `seed` is not `NULL`, then it is used to generate an initial ID value for the nickname group; if that ID value is used, new values are randomly generated until an unused one is found. (If the code loops `NEWNICKGROUP_TRIES` times without finding an unused value, an error is returned; assuming a good random number generator, the default value of 1000 should ensure success on typical databases. `NEWNICKGROUP_TRIES` is defined in `ns-local.h`.) If `seed` is `NULL`, then the new nickname group's ID is left at zero.
`void free_nickgroupinfo(NickGroupInfo *ngi)`
  Frees the given `NickGroupInfo` structure and all associated data. (See `delgroup()` below for removing a nickname group and all its nicknames from the database.)
`NickGroupInfo *_get_ngi(NickInfo *ni, const char *file, int line)`
`NickGroupInfo *_get_ngi_id(uint32 id, const char *file, int line)`
  Implement the `get_ngi()` and `get_ngi_id()` macros, respectively. `file` and `line` are the source file and line from which the function was called, and are filled in by the corresponding macro with `__FILE__` and `__LINE__`.
`int has_identified_nick(const User *u, uint32 group)`
  Returns whether the given client has identified for the nickname group indicated by `group`.
`int reglink_check(User *u, const char *nick, char *password, char *email)`
  Calls the "`REGISTER/LINK check`" callback and returns its result. (A utility function is used rather than directly calling `call_callback_4()` because the `nickserv/link` module needs to make use of the callback as well, and the module system does not allow one module to call another's callbacks (which would be bad design in any case).
`void update_userinfo(const User *u)`
  Updates the user information for the client's nickname. The `NickInfo` fields `last_usermask`, `last_realmask`, and `last_realname` are set from the corresponding fields of the `User` structure, and the `last_seen` field is set to the current time. `u->ni` is assumed to be non-`NULL`.
`int validate_user(User *u)`
  Sets the `ni` and `ngi` fields of the `User` structure to point to the `NickInfo` and associated `NickGroupInfo`, if any, for the client's nickname (if an error occurs looking up the nickname group, `u->ni` is set to `NULL` and `u->ngi` is set to `NICKGROUP_INVALID`); then compares the client's information with the nickname data and determines what level of access for the nickname should be granted to the client. Returns 1 if the client is granted either `NA_IDENTIFIED` or `NA_RECOGNIZED` access, otherwise zero.

  This routine, along with the `REGISTER` command handler, is one of the two "points of entry" into Services, and as such is a critical point for Services security. This is particularly relevant for the section of code conditionally granting full (`NA_IDENTIFIED`) nickname access; while `has_identified_nick()`, mentioned above, operates purely on data which has been seen since Services started (specifically, the list of nicknames the client is known to have become identified for, maintained by `set_identified()`) and is comparatively safe, the second check, which matches the servicestamp, username, and hostname of the last client to identify with those of the current client, needs special attention to ensure that it does not allow clients to gain improper access. As noted in the comments in that section of code, the servicestamp provides a fairly high level of protection on servers which support it natively, while that level is reduced for servers which do not (such servers are rare nowadays). The entire section of code can be disabled with the `NoSplitRecovery` configuration option for added security.

  If the client is determined not to have identified for the nickname previously, the routine continues, determining whether to give `NA_RECOGNIZED` access. "Recognized" status is only implemented by access lists (see section 7-3-2), and if the corresponding module is not loaded, the `NA_RECOGNIZED` flag will never be set on any nickname, except when set along with `NA_IDENTIFIED`. Likewise, if a nickname's `NF_SECURE` flag is set, then `NA_RECOGNIZED` will not be set (and zero will be returned from the routine) even if the client is in fact recognized.

  If the client is not identified or recognized for the nickname, `validate_user()` checks whether the client should be killed or nick-changed, setting an appropriate timeout or calling the collide routines (see section 7-3-1-5 below) depending on the nickname group settings. However, if the client was recognized (which will only be true if the nickname has the `SECURE` option set and thus `NA_RECOGNIZED` was not set), the kill checks are not performed, allowing the client to identify at its leisure.

  Finally, the routine checks the nickname's expiration time, and if it is due to expire "soon" (as defined by the `NSExpireWarning` configuration option), a warning notice is sent to the client.

`void cancel_user(User *u)`
  Updates a client's nickname data when the client stops using the nickname. The `last_seen` field is updated if the client was either identified or recognized; the `authstat` field is cleared along with temporary status flags in the `status` field; an enforcer is introduced if the client was killed or nick-changed (see section 7-3-1-5); the `cancel user`" callback is called; and any active nick collide timeouts are removed. The `ni` and `ngi` fields of the client's `User` structure are also reset to `NULL`.
`void set_identified(User *u)`
  Marks the given client as having identified for the nickname it is currently using. In addition to setting the authentication status to `NA_IDENTIFIED | NA_RECOGNIZED` and updating the nickname's `id_stamp` field, the routine adds the nickname group ID to the list of nickname groups for which the client has identified, stored in the `User` structure and checked by `has_identified_nick()`.
`NickInfo *makenick(const char *nick, NickGroupInfo **nickgroup_ret)`
  Creates a new `NickInfo` record with the given nickname, adds it to the database, and returns a pointer to the new record. If `nickgroup_ret` is not `NULL`, then a new `NickGroupInfo` record is also created for the nickname, the nickname's `nickgroup` field is set accordingly, and a pointer to the `NickGroupInfo` record (which is also added to the database) is stored in the variable pointed to by `nickgroup_ret`. Returns `NULL` on error.
`int delnick(NickInfo *ni)`
  Removes the given nickname from the database and frees all resources used by the `NickInfo` structure. If the nickname was the last of its group, then the nickname group is deleted as well. Returns nonzero on success, zero on error.
`int delgroup(NickGroupInfo *ngi)`
  Removes the given nickname group from the database, along with all associated nicknames. Returns nonzero on success, zero on error.
`int drop_nickgroup(NickGroupInfo *ngi, const User *u, const char *dropemail)`
  Removes the given nickname group from the database, like `delgroup()`, but first log information about the nicknames to be deleted. `u` is the `User` structure for the client that sent the command resulting in the deletion. `dropemail` should be:
  - `NULL` if the call is because of a `DROP` command from the nickname owner;
  - `PTR_INVALID` if the call is because of a `DROPNICK` command from a Services administrator;
  - for a `DROPEMAIL` command, the parameter (address wildcard) used with the command.
`void suspend_nick(NickGroupInfo *ngi, const char *reason, const char *who, const time_t expires)`
  Suspends the given nickname group, copying the parameters `reason`, `who`, and `expires` into the suspension data fields. (If `expires` is zero, then the suspension will not expire.)
`void unsuspend_nick(NickGroupInfo *ngi, int set_time)`
  Cancels the suspension on the given nickname group. If `set_time` is nonzero, the last-seen time of each nickname in the group will be updated according to `NSSuspendGrace` to prevent the nickname from expiring for that length of time (if `NSSuspendGrace` or `NSExpire` are not set, or if the nickname already has enough time before expiration, the last-seen time will not be changed).
`int nick_check_password(User *u, NickInfo *ni, const char *password, const char *command, int failure_msg)`
  Performs a password check for a nickname as part of a NickServ command. If the password is incorrect or an error occurs when checking, a notice will be sent to the client; a `WALLOPS` will also be sent for repeated bad password attempts on the same nickname. `u` is the `User` structure for the client that issued the command; `ni` is the `NickInfo` structure for the nickname whose password is being checked; `password` is the password given by the client, `command` is the name of the command being executed; and `failure_msg` is the index of the message (language string) to be sent if an error occurs when checking the password.
`int count_nicks_with_email(const char *email)`
  Counts and returns the number of registered nicknames with the given E-mail address. If a nickname has the given address but it is awaiting mail authentication, the value returned is negative; for example, if there are five nicknames using a given address but the address is not authenticated, -5 would be returned. Note that this function must scan through the entire nickname database, so care should be taken not to call it too frequently.

`util.c` also defines initialization and cleanup routines, `init_util()` and `exit_util()`, which take care of registering and unregistering the callbacks used by various utility functions. The routines are called as part of the `nickserv/main` module iniitialization and cleanup.

Back to top

##### 7-3-1-5. Nickname colliding

In the general sense, a "nickname collision" is what happens when a client on an IRC network attempts to use a nickname that is already in use by another client. The original (RFC 1459) solution to this was to kill both clients, but with the advent of timestamps, most modern servers only kill one or the other depending on the timestamps of the two colliding clients. Early versions of Services took advantage of this behavior to implement kill protection: by introducing an "enforcer" pseudoclient with an appropriate timestamp, the old client would be killed and would not be able to reconnect with the same nickname.

With respect to Services, then, "nickname colliding" is the act of forcing a client to stop using a particular nickname. While the nickname collision method itself has been abandoned, both to avoid depending on particular collision semantics and to provide a more meaningful disconnect message to the client ("Nick kill enforced" rather than an arbitrary server collision message), and although many modern IRC servers allow Services to forcibly change a client's nickname without going as far as disconnecting the client altogether, the term "colliding" is still used to refer to this set of actions.

Nickname colliding functionality is provided by the file `collide.c`. This file provides two methods of colliding nicknames: directly, or via timeouts. The nickname colliding code also has its own initialization and cleanup functions (`init_collide()` and `exit_collide()`), which are called from the `nickserv/main` module initialization and cleanup routines.

The following routines are used when colliding nicknames directly:

`void collide_nick(NickInfo *ni, int from_timeout)`
  Collides the given nickname, either killing the client using the nickname or forcibly changing the client's nickname to a "guest" nickname depending on configuration settings. `from_timeout` is used internally with collide timeouts, and should always be zero when called externally. This routine automatically calls `introduce_enforcer()` after the client has been killed or nick-changed (in the case of a forced nickname change, the enforcer is introduced by `cancel_user()` upon receipt of the `NICK` message indicating that the client's nickname has been changed). Any pending nickname collide or "433" timeouts (see below) on the nickname are cancelled by thie routine.
`void introduce_enforcer(NickInfo *ni)`
  Introduces an enforcer pseudoclient for the given nickname, to prevent other clients from using the nickname. This routine automatically adds a timeout to call `release_nick()` after `NSReleaseTimeout` seconds have passed.
`void release_nick(NickInfo *ni, int from_timeout)`
  Removes the enforcer pseudoclient for the given nickname, allowing other clients to use it again. As with `collide_nick()`, `from_timeout` is used internally with collide timeouts, and should always be zero when called externally. Any pending release timeouts on the nickname are cancelled by this routine.

Callers can also establish timeouts to collide or release a nick after a certain time. To avoid each caller having to include its own timeout handlers, `collide.c` provides two wrapper routines around the generic timeout functions:

`void add_ns_timeout(NickInfo *ni, int type, time_t delay)`
  Adds a timeout of the given type on the given nickname to occur in `delay` seconds. `type` can be any of the following:
  - `TO_COLLIDE`: A timeout for colliding a nickname (`collide_nick()` will be called).
  - `TO_RELEASE`: A timeout for releasing the hold on a nickname (`release_nick()` will be called).
  - `TO_SEND_433`: A timeout for sending the client using the nickname a "433" error message. (433 is the code for the `ERR_NICKCOLLISION` reply to a `NICK` message, and will cause most interactive client software to request a new nickname from the user. However, some server software has been known to disallow servers from sending 433 replies to remote clients.)
`void rem_ns_timeout(NickInfo *ni, int type, int del_to)`
  Removes any timeout of the given type on the given nickname; `ni` can be `NULL` to cause timeouts on all nicknames (of the given type) to be removed. `type` can be any of the type values used with `add_ns_timeout()`, or -1 to remove timeouts of all types. `del_to` should always be nonzero when called externally (the parameter is used for calling from within a timeout function, where it is not necessary to delete the `Timeout` structure as well).

Each of the three timeout types has its own timeout function: `timeout_collide()`, `timeout_release()`, and `timeout_send_433()`. The timeout functions check for any change in status (such as identification for the nickname) before performing their respective functions.

Back to top

#### 7-3-2. Nickname access lists

Nickname access lists are managed by the `nickserv/access` module, defined in `access.c`. The module is quite simple, consisting of a database table, two callback functions, and a NickServ command (`ACCESS`). For simplicity, the module (along with other NickServ submodules) assumes the presence of the `nickserv/main` module rather than explicitly importing every required NickServ symbol, although the module's initialization does look up the `nickserv/main` module handle for use in adding the requisite callbacks and the `ACCESS` command.

One unusual feature is the use of a static buffer for reading and writing database records. Since the access list itself is stored in the corresponding nickname group's `NickGroupInfo` structure as an array, the entries must be extracted and made available to the database subsystem in an independent (normalized) format. This is done by using a `{nickgroup-ID,access-mask}` record format, and providing a single record buffer. When loading data from persistent storage, the table's `newrec()` function returns a pointer to this buffer, taking advantage of the fact that only one record is loaded at a time (see section 6-2-1); the `insert()` routine then looks up the nickname group stored in the buffer and appends the given access mask to that nickname group's access list, while the `freerec()` routine frees the access mask string. When saving data, the `first()` and `next()` routines call `first_nickgroupinfo()` and `next_nickgroupinfo()` in turn, looping through each access mask of each nickname group.

Ideally, the access lists would be stored in memory in the same fashion, as a distinct table using the nickname group ID as a key. However, since the current database implementation does not provide an efficient way to look up records matching arbitrary criteria (like a `SELECT` statement in SQL), and since problems would ensue when trying to save the data using the (deprecated, but still available) `database/version4` module, the in-memory data structures were left as is. See the comments in the `autojoin.c` source file for a more complete description.

Other than this, there is little of note in the module; the `do_access()` command handler simply adds or removes the requested mask to or from the access list, and the callback functions take care of setting an initial access mask on registration and determining whether the user should be treated as recognized by `validate_user()`.

Back to top

#### 7-3-3. Nickname auto-join lists

Nickname auto-join lists are managed by the `nickserv/autojoin` module, defined in `autojoin.c`. Aside from the details of its operation, this module is nearly identical to the `nickserv/access` module, including the hack used for database loading and saving; see the discussion of that module above (section 7-3-2) for details.

Back to top

#### 7-3-4. Linking and nickname groups

The distinction between "nicknames" and "nickname groups" has been made several times above. Ordinarily, this is only of importance as far as which structure is accessed (`NickInfo` or `NickGroupInfo`); however, the `nickserv/link` module, defined in `link.c`, allows nicknames to be linked together by assigning the same nickname group to both nicknames. This results in all information in the `NickGroupInfo` structure being shared among all linked nicknames, with only the data in the `NickInfo` structure kept separately for each nickname.

The `nickserv/link` includes three commands: `LINK`, `UNLINK`, and `LISTLINKS`, as well as one additional `SET` option, `SET MAINNICK` (linked in through NickServ's "`SET`" callback). Of these, `LISTLINKS` and `SET MAINNICK` are straightforward: `do_listlinks()` simply echoes the contents of the `nicks[]` array for the calling client's nickname group (or the specified nickname's group for Services operators), and `do_set_mainnick()` modifies the nickname group's `mainnick` field based on the given nickname.

When the `LINK` command is given to link a new nickname to the caller's nickname group, `do_link()` first ensures that the new nickname is not already in use and that creating the link would not cause the caller's total number of nicknames to exceed the `NSRegEmailMax` limit, if set. (Note that `LINK` does not abort if the mail address is not authenticated, simply checking the absolute value of the return from `count_nicks_with_email()` against the limit; creating the link does not in itself grant any additional privileges to the user, and can at most be used to "hide" from other users while maintaining current privileges.) If these checks pass, a new `NickInfo` structure is created, passing `NULL` as the `nickgroup_ret` parameter to `makenick()` to indicate that a new nickname group is not required; updates the `NickInfo` structure with the calling user's data; stores the nickname group ID in the new nickname's `nickgroup` field; and appends the new nickname to the nickname group's `nicks[]` array.

`UNLINK` acts similarly to the Services administrator command `DROPNICK` for a single nickname in a group. However, since `UNLINK` is not limited to Services administrators, care must be taken that an unprivileged client is not allowed to delete nicknames from other nickname groups; this check is made by ensuring that (1) the target nickname has a valid associated `NickGroupInfo` structure and (2) the nickname group IDs of the two nickname groups are equal, and disallowing the command otherwise if the `FORCE` option is not given. (The check is made on the `FORCE` option, disallowed for unprivileged clients, rather than on the client's privilege level in order to prevent accidental deletion of others' nicknames by Services administrators.) If the command is allowed, the routine then deletes the nickname by calling `delnick()`; if the nickname group's main nickname is the one being unlinked, `delnick()` automatically adjusts the `mainnick` field to the next nickname in the `nicks[]` array (or the previous nickname, if the deleted nickname was the last one in the array).

Back to top

#### 7-3-5. E-mail address authentication

While it has long been possible to associate an E-mail address with a registered nickname, there was traditionally no way to ensure that the address given was in fact a valid one belonging to the nickname owner. Since Services 5.0, such functionality has been provided by the `nickserv/mail-auth` module, defined in `mail-auth.c`. As described in the user's manual, E-mail address authentication works by assigning a random "authentication code" to the nickname, then sending an E-mail message to the registered address containing that code; the owner is not allowed to identify to the nickname until the code has been entered, ensuring that the address is one which the owner has (or at least had, at the time the message was sent) access to.

Internally, this processing is managed with the `authcode`, `authset`, and `authreason` fields of the `NickGroupInfo` structure. When an event occurs requiring E-mail address authentication, the module generates a random 9-digit numeric code (100000000 through 999999999 inclusive—codes with leading zeroes are not used in order to avoid confusion), stores the code in the nickname group's `authcode` field, and calls the mail subsystem's `sendmail()` routine to send a message to the nickname group's registered E-mail address (see section 8-3 for information about how mail is sent). Additionally, the current timestamp is stored in the `authset` field, and the reason for setting the code (one of the `NICKAUTH_*` constants) is stored in the `authreason` field. (In early versions of the module, the reason was stored in two bits of the authentication code; this method was later rejected, however, as being too kludgey and inflexible as well as leaking information.) The presence of a nonzero value in the `authcode` field indicates that the nickname group is awaiting authentication, and a nickname identification callback function ensures that clients are not allowed to identify for such nicknames (nor does `validate_user()` allow automatic identification if an authentication code is present), thus effectively preventing their use. By issuing an AUTH command with the correct code, a nickname owner can clear the nickname group's `authcode` field, allowing identification to the nickname(s) once more.

The module begins with several utility functions:

`void make_auth(NickGroupInfo *ngi, int16 reason)`
  Generates a random authentication code, and sets the nickname group's `authcode`, `authset`, and `authreason` fields appropriately. The `reason` parameter is copied directly into the `authreason` field, without checks on its value.
`void clear_auth(NickGroupInfo *ngi)`
  Clears the given nickname group's authentication code (if any), as well as all related nickname group data fields (including the previous E-mail address).
`int send_auth(User *u, NickGroupInfo *ngi, const char *nick, int what)`
  Sends an E-mail to the given nickname group's owner containing the nickname's current authentication code. `u` is the `User` structure for the client whose command caused the message to be sent; `nick` is the specific nickname for which the command was issued; and `what` is one of the `IS_*` constants defined for the routine (internally a language string index for the mail body or -1 for the special case of `SETAUTH`). The routine itself is defined as `send_auth_`, taking a `line` parameter indicating where in the source the routine was called from; this is filled in automatically by the `send_auth()` macro. In order to inform the calling client of the success or failure of sending the message, `send_auth()` creates a `sendauth_data` structure for each message sent, used in the two routines listed below.
`void send_auth_callback(int status, void *data)`
  The callback function used for `sendmail()` when called from `send_auth()`. This routine uses the `sendauth_data` structure for the sent message (passed as the `data` parameter) to send a reply to the client that issued the original command, and also to clear the nickname group's `last_sendauth` field if the command used was `SENDAUTH` and the message could not be sent. The structure is then removed from the global list and freed.
`int sendauth_userdel(User *user, const char *reason, int is_kill)`
  Used as a callback function for the core's "`user delete`" callback. Iterates through the `sendauth_data` list, clearing the `User` pointer of any entries for the user being removed; this causes `send_auth_callback()` to skip sending a reply when the mail sending completes.

These routines are followed by the command handlers for the commands supported by the module: `AUTH`, `SENDAUTH`, `REAUTH`, `RESTOREMAIL`, and the Services administrator commands `SETAUTH`, `GETAUTH`, and `CLEARAUTH`. Of these, the only fairly complex one is the `AUTH` handler, `do_auth()`, as it must watch for attempts to guess the authentication code. (Invalid `AUTH` commands are treated the same as bad passwords to `IDENTIFY` or other commands, by calling `bad_password()` and incrementing the nickname group's `bad_auths` field, which itself can generate a warning via `WALLOPS`.)

Finally, the module includes four callback functions. Two of these, `do_registered()` and `do_set_email()`, hook into the NickServ callbacks for the `REGISTER` and `SET EMAIL` commands, respectively, dropping the client's identified status and generating and sending an authentication code. (Changes to the E-mail address made by Services administrators are not subject to authentication, however.) These are followed by the `IDENTIFY` command callback function `do_identify()`, which disallows `IDENTIFY` for nicknames with pending authentication codes, and the expiration check callback function `do_check_expire()`, which drops a newly-registered nickname after `NSNoAuthExpire` seconds (if set) if not authenticated, and also clears any pending `REAUTH` after the same amount of time (the user can subsequently use a second `REAUTH` if necessary).

Since the ability to send E-mail is essential for this module to work, the `init_module()` function checks for the presence of the `mail/main` module, refusing to load if it is not available.

Back to top

### 7-4. ChanServ

ChanServ is the most complex of the standard Services pseudoclients, owing to the variety of operations that can be performed on channels. There is no easy way to split those operations up into separate modules (except by individual command, an avenue which has not been pursued), and as a result, the core ChanServ module is itself the largest module in Services.

As with NickServ, the core ChanServ module, `chanserv/main`, is split up over several source files. These are discussed in section 7-4-1 and its subsections, with the exception of the `access.c` source file, which is discussed in section 7-4-2 along with the access list manipulation submodules, `chanserv/access-levels` and `chanserv/access-xop`.

Back to top

#### 7-4-1. ChanServ core functionality

The core ChanServ module, `chanserv/main`, is built from several source files, along much the same lines as the core NickServ module: `main.c`, containing the central module code and most command handlers; `access.c`, for handling channel access lists; `check.c`, for checking the status of a channel against registered data and making appropriate changes; `set.c`, implementing the `SET` command; and `util.c`, defining various ChanServ utility functions.

Back to top

##### 7-4-1-1. Channel data structures

As with NickServ, ChanServ splits its declarations into two header files: `chanserv.h`, containing structures and routine declarations intended to be exported to other modules, and `cs-local.h`, for internal use by ChanServ only. (There is also a separate header file, `access.h`, specifically for channel access list definitions; this is discussed in section 7-4-2-1.)

The channel data structure, `ChannelInfo`, is naturally exported. As ChanServ does not have the concept of "links" or "channel groups" that NickServ uses with nicknames, all data for a channel is stored in this single structure. `ChannelInfo` does, however, include three substructures, defined before it in `chanserv.h`:

`ChanAccess`
  Contains the data for an entry on a channel's access list. Access list entries are stored by nickname group ID, rather than by nickname, both to optimize checking an access list for a particular client and to eliminate the possibility of two or more entries matching a single client. (This is why only registered nicknames are allowed on channel access lists, and why channel access list entries are always displayed using the nickname group's main nickname, regardless of the nickname actually added to the list.) Rather than resizing the array every time a change is made to the list, deleted entries are left in memory with a nickname group ID of zero, and subsequent adds reuse these entries before attempting to expand the array. The `channel` field is used to link the record with its associated channel when loading and saving data. There are also a number of access-level related constants declared below this structure, such as the maximum and minimum access levels and the equivalent access levels for the `XOP` commands.
`AutoKick`
  Contains the data for an entry on a channel's autokick list. As with access list entries, deleted entries are left in the list rather than resizing the array (a `NULL` value for the mask string indicates an unused entry), and the `channel` field is used when loading and saving data to link the record with its associated channel.
`ModeLock`
  Contains a channel's mode lock data, including the set of modes locked on and off along with parameters for modes that require them; with the exception of the presence of two mode sets rather than one (modes can be locked on, locked off, or neither), the structure's contents are the same as the fields used in channel data structures (`Channel` records) to record the channel's current mode. The mode sets are normally maintained as bitmasks, but when used in the `convert-db` tool, they are defined as strings instead to allow lossless conversion to XML without needing to know the specific set of modes supported by the program that created the database. Unlike the `ChanAccess` and `AutoKick` structures, there is only one `ModeLock` structure per channel, and the data in the structure is saved along with the channel record itself, so there is no need for a separate channel field.

`chanserv.h` also defines constants for each of the channel privilege levels (indices into the `levels[]` array of the `ChannelInfo` structure). As noted in the comment above the list of definitions, changing the values of any of the constants will cause malfunctions when using the `database/version4` module, since that module simply reads in the list of levels as a block. (This is why the index 18, formerly used for `CA_AUTOOWNER`, is unused—to avoid problems with databases in which that index is used.)

The `ChannelInfo` structure itself contains the following fields:

`ChannelInfo *next, *prev`
  Used to link records together in the internal hash table.
`int usecount`
  The record's usage count (number of gets minus number of puts).
`char name[CHANMAX]`
  The channel's name. Capitalization is as used when the channel was registered, and does not change due to later actions. The buffer size, `CHANMAX`, is defined in the global header file `defs.h`.
`uint32 founder`
`uint32 successor`
  The nickname group ID of the channel's founder and successor, respectively. If the channel does not have a successor set, the `successor` field will be zero.
`Password founderpass`
  The founder password for the channel.
`char *desc`
  The channel's description, as specified at registration time or with a later `SET DESC` command.
`char *url`
  The URL associated with the channel, as set with the `SET URL` command. `NULL` if no URL has been set.
`char *email`
  The E-mail address associated with the channel, as set with the `SET EMAIL` command. `NULL` if no E-mail address has been set.
`char *entry_message`
  The channel's entry message (the message sent as a `NOTICE` to clients entering the channel), as set with the `SET ENTRYMSG` command. `NULL` if no entry message has been set.
`time_t time_registered`
  The timestamp at which the channel was registered.
`time_t last_used`
  The timestamp at which the channel was last used (see section 3-2-3 of the user's manual for details of how the last-used time is set).
`char *last_topic`
`char last_topic_setter[NICKMAX]`
`time_t last_topic_time`
  The topic most recently set on the channel, along with the nickname of the client that set the topic and the timestamp at which it was set. If no topic has been set on the channel, `last_topic` will be `NULL`, and the other two fields will be undefined.
`int32 flags`
  A bitmask containing zero or more of the following channel flags:
  - `CF_KEEPTOPIC`: ChanServ should restore the channel's previous topic each time the channel is created on the IRC network (`SET KEEPTOPIC`).
  - `CF_SECUREOPS`: Clients without a positive access level on the channel should be prevented from getting ops (`SET SECUREOPS`).
  - `CF_PRIVATE`: The channel is hidden from the `LIST` command output, except when used by Services administrators (`SET PRIVATE`).
  - `CF_TOPICLOCK`: ChanServ should prevent the topic from being changed except by the `SET TOPIC` command (`SET TOPICLOCK`).
  - `CF_RESTRICTED`: ChanServ should automatically kick and ban any clients without a positive access level that attempt to join the channel (`SET RESTRICTED`).
  - `CF_LEAVEOPS`: ChanServ should not remove server-generated ops for the first client to join a channel, even if that client would not normally be auto-opped (`SET LEAVEOPS`).
  - `CF_SECURE`: When checking a client's channel access level, require the client to have identified to NickServ regardless of the setting of the nickname's `SECURE` option (`SET SECURE`).
  - `CF_VERBOTEN`: The channel is forbidden (`FORBID`).
  - `CF_NOEXPIRE`: The channel does not expire (`SET NOEXPIRE`).
  - `CF_OPNOTICE`: ChanServ should send a notice to the channel whenever any of the `OP`, `VOICE`, or related commands are used (`SET OPNOTICE`).
  - `CF_ENFORCE`: ChanServ should prevent clients from removing automatically-set channel user modes such as auto-ops (`SET ENFORCE`).
  - `CF_HIDE_EMAIL`: The channel's E-mail address is hidden from the `INFO` command output, except when used by Services administrators (`SET HIDE EMAIL`).
  - `CF_HIDE_TOPIC`: The channel's current or last topic is hidden from the `INFO` command output, except when used by Services administrators (`SET HIDE TOPIC`).
  - `CF_HIDE_MLOCK`: The channel's mode lock is hidden from the `INFO` command output, except when used by Services administrators (`SET HIDE MLOCK`).
  - `CF_SUSPENDED`: The channel is suspended (`SUSPEND`).
  - `CF_MEMO_RESTRICTED`: Only users with the `MEMO` privilege are permitted to send memos to the channel (`SET MEMO-RESTRICTED`).
  The flag values 0x00000100 and 0x00000400 are unused to avoid difficulties with databases from earlier versions of Services which used these values.
`char suspend_who[NICKMAX]`
`char *suspend_reason`
`time_t suspend_time`
`time_t suspend_expires`
  Suspension data for the channel, if the `CF_SUSPENDED` flag is set. Used in the same fashion as the same-named fields in the `NickGroupInfo` structure (see section 7-3-1-1).
`int16 levels[CA_SIZE]`
  The channel access levels corresponding to each of the channel privileges (`CA_INVITE`, `CA_AKICK`, and so on). A value of `ACCLEV_DEFAULT` indicates that the corresponding privilege should use the default access level defined in `access.c`; a value of `ACCLEV_INVALID` indicates that the corresponding privilege is disabled entirely, except with respect to the channel founder.
`ChanAccess *access`
`int16 access_count`
  A variable-length array containing the channel's access list.
`AutoKick *akick`
`int16 akick_count`
  A variable-length array containing the channel's autokick list.
`ModeLock mlock`
  The channel's mode lock data.
`Channel *c`
  Not saved to persistent storage. Points to the `Channel` structure for the channel if it is currently in use.
`int bad_passwords`
  Not saved to persistent storage. The number of times an incorrect password has been given for a channel command (such as `IDENTIFY`) since the last correct password. Used to warn about attempts to crack the password.

The `cs-local.h` header file includes three definitions used internally by ChanServ:

`MAX_MLOCK_PARAMS` (constant)
  Sets the maximum number of command parameters that the `SET MLOCK` command will process. This constant is used to avoid the overhead of dynamically allocating an argument array for every invocation of the command; any parameters passed beyond this limit will be silently ignored. The default value, 256, is more parameters than are can be passed in an RFC-standard 512-byte line (even if every parameter is one character, subtracting out the "`SET MLOCK`" leaves space for only 251 parameters, not considering the trailing CR/LF and other IRC protocol overhead).
`ChanOpt` (structure)
  Used to define channel option data, for use by the `SET` and `INFO` commands. The structure has the following fields:
  - `name`: The name of the option, as a string.
  - `flag`: The corresponding `ChannelInfo.flags` flag value.
  - `namestr`: The option's descriptive name (for use in `INFO` output), as a language string index. If -1, the option will not be included in `INFO` output.
  - `onstr`, `offstr`: Response messages for turning the option on and off via `SET`, as language string indices.
  - `syntaxstr`: The syntax message for setting the option, as a language string index.
`RET_*` (constants)
  Return values from access list modification routines. See section 7-4-2-1 for details.

One other point of note in `cs-local.h` is the renaming of several functions in `set.c` and `util.c` using `#define` directives, such as renaming `init_set()` to `init_set_cs()`. This is to avoid conflicts with NickServ, which includes functions of the same name in its own `set.c` and `util.c`. While the functions involved are not used outside of ChanServ, they must be declared external for the multi-file link to succeed; as a result, if the functions are not renamed, linking the final program when using static modules will result in a symbol name clash when the symbols from both modules are processed. This problem does not occur when using dynamic modules, since the presence of conflicting symbols in dynamic modules is not itself an error, and no attempt is made to reference either set of symbols from any other module (the references within the respective modules are resolved at module link time).

Back to top

##### 7-4-1-2. Overall module structure

The main source file for ChanServ, `main.c`, follows the same pattern as its NickServ counterpart; see section 7-3-1-2 for a more extensive description.

One of the first things defined in `main.c` is the `chanopts[]` table, an array of `ChanOpt` structures (see above) describing on/off options available on channels from a user's perspective. This table is used primarily for the `SET` command (thus only flags which have a corresponding `SET` command are listed), and secondarily for outputting the channel's option set in response to the `INFO` command. Note that the three `HIDE` options are not listed here, as they are handled specially for the `SET` command and not listed in the `INFO` output.

In addition to the standard command list in the `cmds[]` array, two separate arrays are defined, for the `HALFOP`/`DEHALFOP` and `PROTECT`/`DEPROTECT` command pairs. These commands can only be used if the IRC server protocol supports the corresponding feature, so the module initialization routine checks the protocol's feature flags and conditionally registers these two pairs of commands.

ChanServ includes a significant number of callback functions for various IRC events: in addition to watching for newly-created channels and users joining channels, it must also monitor changes of status such as channel modes and channel topics to ensure that they remain consistent with the registered settings and to record changes, as well as take notice of nickname-related events that can affect channels. Of these, the `do_nickgroup_delete()` callback function, called when a nickname group is deleted, is easily the most complex. Since channel founders must be registered nicknames, the disappearance of a nickname group means that any channels with that group as founder will no longer have a valid founder group ID. If the channel has a successor set, the successor may be able to assume foundership of the channel—but the code must be careful that this does not cause the successor to exceed his registered channel limit, or users could circumvent the limit by registering multiple nicknames, setting one as founder and another as successor, and deliberately dropping the founder nickname. In addition, if the channel was suspended, it is changed to a forbidden channel to prevent users from getting around a suspension by dropping and re-registering their nickname. Because of these various potentialities, the callback function always logs any actions it takes in response to the deletion of a nickname group.

Of the command routines, the basic commands (`HELP`, `REGISTER`, `IDENTIFY`, `DROP`, `DROPCHAN`, `INFO`, `LIST`) are very similar to their NickServ counterparts, and are not discussed in detail here. The two ChanServ-specific commands whose handlers are fairly complex are `AKICK` and the `OP`/`VOICE` command set.

The `AKICK` command handler `do_akick()`, despite its length (including a separate helper routine used with `LIST` and `VIEW`), is not dissimilar to the OperServ autokill and S-line commands, or the NickServ `ACCESS` and `AJOIN` handlers. The only significant difference is the presence of the `ENFORCE` subcommand; this is implemented by calling `check_kick()`, the routine used to check whether newly-joined clients are allowed to join a channel (see section 7-4-1-3) for each client on the channel, causing clients which match an entry on the autokill list to be kickbanned.

The `OP` and `VOICE` family of commands all perform a common function—adding or removing a channel user mode—and for this reason, all eight commands are handled by a single routine, `do_opvoice()`, with handlers for each command that call the common routine with the appropriate command name to indicate the mode of operation. The data used by the common routine is stored in `opvoice_data[]`, an array of structures with the following fields:

- `cmd`: The command name.
- `add`: Nonzero if the command adds a mode, zero if it removes a mode.
- `mode`: The mode character added or removed.
- `target_acc`: A channel privilege index (`CA_*` constant); if the target client is in this privilege class, the command will be refused.
- `success_msg`: The language string index for the message to be sent when the command succeeds.
- `already_msg`: The language string index for the message to be sent when the client already has the mode added (or lacks the mode removed) by the command.
- `failure_msg`: The language string index for the message to be sent when the command is rejected.

When called, `do_opvoice()` first extracts the data for the command from the `opvoice_data[]` table, and sets an additional variable, `target_nextacc`, used for mode-removal commands to set an upper bound on the target client access level check; this is used to, for example, allow `DEVOICE` on an auto-op client (since the client can just give themselves voice status again if necessary). The routine then loops through all target nicknames given with the command; a `do/while` loop is used so that if no nicknames are given at all, the code will still be run once (in this case the client that gave the command is used as the target). For each target client, the standard permission and channel status checks are performed, and then the routine determines whether to allow the command:

1. If the target client is the client giving the command, the command is allowed.
2. If the command removes a mode (`DEOP`, `DEVOICE`, etc.) and the channel does not have the `ENFORCE` option set, the command is allowed.
3. If a channel privilege check (`target_acc`) is not specified for the command, the command is allowed.
4. If the the target client is not in the privilege class specified for the command's privilege check, the command is allowed.
5. If an upper limit for the privilege check (`target_nextacc`) is set for the command and the target client is in that privilege class, the command is allowed.
6. Otherwise, the command is refused.

Once the command has been allowed, the routine determines which mode flags need to be set or cleared for the target client. (For extensibility, the code allows for more than one flag to be set or cleared for a single command, though the data table only allows one mode character.) If there are no modes to be set or cleared, a notice to that effect is sent to the caller; otherwise, the necessary mode changes are performed, a notice of the mode change is sent to the channel if the `OPNOTICE` option is enabled, and a success notice is sent to the caller. In addition, if the command was an `OP` command, the channel's last-used time is updated as for auto-ops.

Back to top

##### 7-4-1-3. Channel status checking and modification

ChanServ's routines for checking and adjusting channel status are located in the source file `check.c`. There are six routines exported from this file; two, `init_check()` and `exit_check()`, are initialization and cleanup routines called by the module initialization and cleanup code, respectively. The remaining routines are:

`void check_modes(Channel *c)`
  Checks the given channel's modes, making any changes necessary. For registered channels, the "registered" mode (if any) is always added, and other modes are set or cleared according to the mode lock; for unregistered channels, the "registered" mode is always cleared. This routine also checks for the "bouncy modes" phenomenon in tandem with the channel `MODE` message handle (see section 2-6-3).
`void check_chan_user_modes(const char *source, struct c_userlist *u, Channel *c, int32 oldmodes)`
  Checks the channel user modes of the given client (`u`) on the given channel, making any changes necessary. The set of "necessary" changes depends not only on the client's current modes, but also on the source of the `MODE` message that caused the change (passing an empty string for the `source` parameter will cause such checks to be skipped). `oldmodes` is the client's previous set of modes, or -1 for a client joining a channel. The sequence of operations is fairly complicated:
  - If the channel is not registered (or forbidden), no changes are made.
  - If `source` is Services' server name or the ChanServ or OperServ pseudoclient nickname, no changes are made (under the assumption that anything done by Services has been otherwise checked).
  - If `source` is the client whose modes are being checked, then no changes are made unless the user is either not opped or is about to be deopped, in which case the mode changes made by the client are reversed. However, this check is not performed for IRC operators (since some IRC servers allow operators to set arbitrary modes regardless of chanop status), and on servers supporting halfops, the mode change is not reversed if the user has halfops and is only changing the halfop or voice modes.
  - If the mode change is the opping by a server of the first client to join a channel and the channel does not have the `LEAVEOPS` option set, the client's channel access level is checked against the auto-op privilege level. If the client has auto-op privileges, then the channel's last-used time is updated as for ordinary auto-op processing (see below); otherwise, a "channel is registered" notice is sent to the client and the client is deopped (in that order, so that a human user will see the reason for the deop before the mode change itself).
  - The "`check_chan_user_modes`" callback is called, allowing protocol modules to handle modes not recognized by the standard processing.
  - The client's new modes, based on channel privilege level, are calculated by calling `check_access_cumode()` (see section 7-4-2-1).
  - If the client just joined the channel, the mode change was done by a server, or the `ENFORCE` option is set on the channel, all missing automatic modes are added. (This has the effect of allowing automatic modes to be removed from clients if `ENFORCE` is not set.) In addition, if the mode change included a `+o`, the channel's last-used time is updated.
  - If the client is not an IRC operator, any necessary mode removals are performed.

  The mode changes in these last two steps are performed by a helper routine, `local_set_cumodes()`, which in turn calls `set_cmode()` for each mode in the given set.

`int check_kick(User *user, const char *chan, int on_join)`
  Checks whether a client is permitted to be on a channel; if so, returns zero, otherwise kickbans the client and returns nonzero. This routine is normally called when a client joins a channel, before the actual join processing, but setting the `on_join` parameter to zero allows this routine to be called for clients already in the channel as well, such as for the `AKICK ENFORCE` command. A client can be denied access to a channel for any number of reasons, checked in the following order:
  - If the channel name is the single character "`#"`" and the `CSForbidShortChannel` configuration option is set, the client is kickbanned.
  - If the client is a Services administrator, the client is allowed.
  - If the "`check_kick`" callback returns 1, the client is kickbanned; if it returns 2, the client is allowed.
  - If the client is an IRC operator, the client is allowed.
  - If the channel already exists with an IRC-operators-only mode, the client is kickbanned. (Ordinarily, the IRC server takes care of such processing, but this code is included to handle desynchs and other network problems.)
  - If the channel is not registered, then the client is kickbanned if the `CSRegisteredOnly` configuration option is set, and allowed otherwise.
  - If the channel is forbidden or suspended, the client is kickbanned.
  - If the channel's mode lock has an IRC-operators-only mode set, the client is kickbanned.
  - If the channel's mode lock includes a registered-nicknames-only mode and the client's nickname is not registered, the client is kickbanned. (However, this check is skipped if the `CSSkipModeRCeck` configuration option is set.)
  - If the client's `nick!user@host` string matches an autokick mask, the client is kickbanned.
  - If the client matches the `NOJOIN` privilege on the channel, the client is kickbanned. The client is also kickbanned if it would match the `NOJOIN` privilege when identified to its nickname; however, this check is skipped if less time than specified in the `CSRestrictDelay` configuration option has passed since Services startup.
  - Otherwise, the client is allowed.

  If the client is to be kickbanned, the routine first checks whether kicking the client would cause the channel to become empty (and thus be deleted, nullifying the effecet of any ban); if so, a `JOIN` message is sent to the network to cause ChanServ to join the channel, and a timeout for `CSInhabit` seconds is added to cause ChanServ to leave the channel after that time. Following this, the routine ensures that the ban mask is properly formatted (containing a nickname as well as user and host), then clears any ban exceptions matching the user and adds the ban mask if it is not already present. Once the ban is present, the client is then kicked from the channel, and removed from the internal data structures if necessary.

  Implementation note: As mentioned in Appendix D of the user manual, when ChanServ temporarily enters a channel for a kickban, it is not added to the internal channel data; as a result, a subsequent `UNBAN` or `INVITE` on the channel will return a "channel does not exist" error. It would probably be better to add ChanServ to the channel's client list like any ordinary client.

`int check_topiclock(Channel *c, time_t topic_time)`
  Called on a channel topic change (`topic_time` is the timestamp associated with the topic change) to restore the topic to its previous value if the channel's `TOPICLOCK` option is set. Returns nonzero if the topic is changed by the routine, otherwise zero.

Back to top

##### 7-4-1-4. The `SET` and `UNSET` commands

The handlers for the `SET` and `UNSET` commands are located in the `set.c` source file; as for NickServ, they function like miniature versions of the main `chanserv()` routine. One noteworthy difference is that the on/off options (other than the three `HIDE` options) are all handled by a single routine, `do_set_boolean()`; the main `SET` handler, `do_set()`, looks up the option name in the `chanopts[]` table defined in `main.c` (checking privileges for the `NOEXPIRE` option), then calls `do_set_boolean()`, which uses the data from the `ChanOpt` structure to set channel flags and send responses to the calling client.

Other noteworthy option handlers are:

`do_set_founder()`
`do_set_successor()`
  Both `SET FOUNDER` and `SET SUCCESSOR` follow the same general pattern: the given nickname is looked up to retrieve its nickname group ID, the ID is checked to ensure that both founder and successor are not set to the same nickname group, and an informational message is logged to record the change. The major difference is that `SET FOUNDER` checks to ensure that the new founder has not reached the channel registration limit, while `SET SUCCESSOR` makes no such check. (Even if it did, the check would only make sense at the time the `SET SUCCESSOR` command was given, and would not reflect any future channel registrations or drops by the successor. An alternative possibility would be to have successor channels count against the channel limit as well, as mentioned in section 11-1.)
`do_set_mlock()`
  The `SET MLOCK` handler is fairly complex, as it must parse the mode string and parameters to ensure that users cannot inadvertently (or maliciously) cause an invalid `MODE` message to be sent to the IRC network. The routine parses the mode string character by character; if a mode is found that conflicts with an earlier setting in the string, the later occurrence takes precedence. Additionally, in order to simplify cleanup in case a problem is found, the new mode lock is accumulated in a temporary `ModeLock` structure, which is copied into the channel's data when the routine successfully completes.

  In order to support additional modes provided by particular IRC protocols, `do_set_mlock()` defines a callback, "`SET MLOCK`", which is called once for every mode in the mode string; it is also called once after all modes have been processed, to allow for a final validity check (for example, to check for modes that require other modes to be set, as the Unreal protocol module does).

Back to top

##### 7-4-1-5. ChanServ utility routines

ChanServ's utility functions are defined in `util.c`. As with NickServ, the preprocessor symbol `STANDALONE_CHANSERV` can be defined before including `util.c` in another file; this causes the routines `new_channelinfo()`, `free_channelinfo()`, and `reset_levels` to be defined as `static`, and eliminates all other code. `new_channelinfo()` and `free_channelinfo()` are used to allocate and free resources for a `ChannelInfo` structure, like their nickname counterparts; `reset_levels()` resets the privilege levels for a channel (the `levels[]` array) to default values.

Aside from these three functions (all exported from ChanServ), `util.c` defines the following routines, along with the initialization and cleanup routines `init_util()` and `exit_util()`:

`int check_channel_limit(const NickGroupInfo *ngi, int *max_ret)`
  Compares the given nickname group's registered channel count with the limit applied to that nickname, returning -1 if the limit has yet to be reached, 0 if the limit has been reached, and 1 if the limit has been exceeded (much like string comparison functions). Also stores the registered channel limit in the variable pointed to by `max_ret` if `max_ret` is not `NULL`. This routine is exported.
`ChannelInfo *makechan(const char *chan)`
  Creates a new `ChannelInfo` structure for the given channel name, adds it to the database, and returns it.
`int delchan(ChannelInfo *ci)`
  Removes the given channel from the database, returning nonzero on success, zero on failure.
`void count_chan(ChannelInfo *ci)`
  Updates the `NickGroupInfo` record for the given channel's founder to indicate that that nickname group owns the channel, incrementing the owned-channel count.
`void uncount_chan(ChannelInfo *ci)`
  Removes the given channel from its founder's owned-channel list and decrements the owned-channel count.
`int is_founder(const User *user, const ChannelInfo *ci)`
  Returns whether the given user has founder access to the given channel, whether due to being the actual channel founder or to identifying for the channel with its founder password.
`int is_identified(const User *user, const ChannelInfo *ci)`
  Returns whether the given user has identified for the channel with its founder password. A subset of `is_founder()`.
`void restore_topic(Channel *c)`
  Restores the saved topic on a newly-created channel if the channel is registered and its `KEEPTOPIC` option is set.
`void record_topic(ChannelInfo *ci, const char *topic, const char *setter, time_t topic_time)`
  Records the given topic in the given channel's data structure.
`void suspend_channel(ChannelInfo *ci, const char *reason, const char *who, const time_t expires)`
  Suspends the given channel, copying the parameters `reason`, `who`, and `expires` into the suspension data fields. (If `expires` is zero, then the suspension will not expire.)
`void unsuspend_channel(ChannelInfo *ci, int set_time)`
  Cancels the suspension on the given channel. If `set_time` is nonzero, the last-used time of the channel will be updated according to `CSSuspendGrace` to prevent the channel from expiring for that length of time (if `CSSuspendGrace` or `CSExpire` are not set, or if the channel already has enough time before expiration, the last-used time will not be changed).
`void chan_bad_password(User *u, ChannelInfo *ci)`
  Records a bad password attempt for the given channel, sending out a `WALLOPS` if the number of consecutive bad password attempts for the channel reaches the limit specified by the `BadPassWarning` configuration option (if set).
`ChanOpt *chanopt_from_name(const char *optname)`
  Returns the `ChanOpt` corresponding to the given (case-insensitive) option name, or `NULL` if no matching option is found.
`char *chanopts_to_string(const ChannelInfo *ci, const NickGroupInfo *ngi)`
  Returns a string describing the set of options active on the given channel in human-readable form. `ngi` indicates the client to which the string will be sent and is used in `getstring()` calls. The returned string is stored in a static buffer, which will be overwritten by the next call to this routine.

Back to top

#### 7-4-2. Channel access list handling

As discussed in the user's manual, user privileges on channels are maintained via channel access lists. Channel access list handling in ChanServ is split into three files. One, `access.c`, contains common routines and privilege level definitions, and is included in the `chanserv/main` module; the other two, `access-levels.c` and `access-xop.c`, are independent modules which provide two different ways of manipulating access lists. A separate header file, `access.h`, contains definitions related to channel access lists.

Back to top

##### 7-4-2-1. Access list basics

The `access.c` source file, included as part of the main ChanServ module, serves two main purposes; to define the set of privileges associated with channels, and to provide utility routines for performing common operations related to channel access lists. It also makes use of the `access.h` header file for certain structure and constant definitions.

The list of channel privileges is defined in the `levelinfo[]` array at the top of the file; each element in the array is a `LevelInfo` structure that describes one privilege. (The word "level" in the identifiers comes from the command, `LEVELS`, used to modify the settings on a per-channel basis; that command name was originally used for the meaning of "setting the levels at which privileges are given".)

The word "privilege" itself is something of a misnomer, as it includes two "negative privileges", `CA_AUTODEOP` and `CA_NOJOIN`. While these can be used in the same manner as ordinary privileges, their primary function is in conjunction with the `SECUREOPS` and `RESTRICTED` channel options; these cause the respective privilege levels to be treated as zero, preventing users not on the channel access list to be auto-deopped or blocked from entering the channel. However, these two privileges are not visible to users, so "privilege" is considered clear enough to use in the documentation

The `LevelInfo` structure, defined in `access.h`, contains the following fields:

`int what`
  The `CA_*` constant used for this privilege.
`int defval`
  The default channel access level corresponding to this privilege.
`const char *name`
  The user-visible name for this privilege, used in the `LEVELS` command. An empty string makes the privilege invisible to users.
`int desc`
  The message string index giving the privilege's description.
`int action`
  The "meaning" of the privilege: the action to be performed for clients with the appropriate access level. One of the following flags, any of which may be combined (OR'd) with `CL_LESSEQUAL` to make the privilege's associated level a maximum rather than a minimum:
  - `CL_SET_MODE`: Sets channel user modes on the corresponding client.
  - `CL_CLEAR_MODE`: Clears channel user modes from the corresponding client.
  - `CL_ALLOW_CMD`: Allows a command or set of commands to be used.
  - `CL_OTHER`: Handled separately (or a no-op).
`union {...} target`
  Data used in implementing the privilege. The union has two members:
  - `struct {...} cumode`: Used for `CL_SET_MODE` and `CL_CLEAR_MODE`. Includes three fields:
    - `const char *modes`: The string of mode(s) to set on the client.
    - `int cont`: Used to "chain" privileges together, so that only the first applicable mode set is used (used for `AUTOOP`, `AUTOHALFOP`, and `AUTOVOICE`).
    - `int32 flags`: The mode flags equivalent to `modes`. Set at module initialization time (this field can be left uninitialized).
  - `struct {...} cmd`: Used for `CL_ALLOW_CMD`. Includes two string fields:
    - `const char *main`: The relevant command name.
    - `const char *sub`: The relevant subcommand for the given command, or `NULL` if none.

In addition to the `levelinfo[]` table itself, exported for use by other modules (particularly the `http/dbaccess` module, described in section 8-2-7), the file's initialization routine `init_access()` copies relevant parts of the table into two local arrays indexed by privilege (`CA_*` value): `def_levels[]`, containing each entry's default access level (the `defval` field), and `lev_is_max[]`, containing a boolean indication of whether the privilege's access level is a maximum (whether the `action` field has `CL_LESSEQUAL` set).

The main portion of `access.c` defines the following utility functions for use by the main ChanServ module and the two access list manipulation modules. Several of the routines are exported for use by external modules as well.

`int get_ci_level(const ChannelInfo *ci, int what)`
  Exported routine. Returns the given channel's level for the given privilege, translating `ACCLEV_DEFAULT` in the channel's `levels[]` into the appropriate default value. Returns `ACCLEV_INVALID` (and logs an error message) on invalid parameters.
`int check_access(const User *user, const ChannelInfo *ci, int what)`
  Exported routine. Returns whether the given client has the given privilege (`what`) on the given channel. Due to an unfortunate coincidence of terms, this routine can seem confusing to call for the `CA_NOJOIN` "privilege": the return value will be 1 if the client does not "have access to" the channel, i.e. matches the `CA_NOJOIN` privilege.
`int check_access_if_idented(const User *user, const ChannelInfo *ci, int what)`
  Exported routine. Like `check_access()`, but returns what the result would be if the client was identified for its nickname. (If the nickname is not registered, the return value will be the same as for `check_access()`.
`int check_access_cmd(const User *user, const ChannelInfo *ci, const char *command, const char *subcommand)`
  Exported routine. Returns whether the client is allowed to use the given command on the given channel. If the check is being made for a specific subcommand, that subcommand is specified in `subcommand`; otherwise, `subcommand` is `NULL`.
`int get_access(const User *user, const ChannelInfo *ci)`
  Returns the given client's access level on the given channel, taking into account whether the client has identified for its nickname with respect to the nickname and channel `SECURE` options.
`static int get_access_if_idented(const User *user, const ChannelInfo *ci)`
  Like `get_access()`, but returns the access level associated with the client's nickname regardless of whether the client has identified or not.
`int check_access_cumode(const User *user, const ChannelInfo *ci, int32 newmodes, int32 changemask)`
  Checks the channel user modes of a client on a channel, returning a bitmask of modes that are to be changed (in other words, the bitwise exclusive-or of the current and resulting mode sets). `newmodes` is the client's current set of modes on the channel; `changemask` indicates which modes changed due to the action that caused this function to be called.
`int access_add(ChannelInfo *ci, const char *nick, int level, int uacc)`
`int access_del(ChannelInfo *ci, const char *nick, int uacc)`
  Adds (`access_add()`) or deletes (`access_del()`) an entry to or from the given channel's access list for the nickname group to which `nick` belongs. `level` is the access level for the new entry, and `uacc` is the access level of the client making the change. Both routines return one of the following result codes, defined in `cs-local.h`:
  - `RET_ADDED` (`access_add()` only): The target nickname did not previously exist on the channel's access list, and was successfully added.
  - `RET_CHANGED` (`access_add()` only): The target nickname previously had a different access level on the channel, and the access level was successfully changed.
  - `RET_UNCHANGED` (`access_add()` only): The target nickname already had the desired access level on the channel.
  - `RET_DELETED` (`access_del()` only): The target nickname was successfully deleted from the channel's access list.
  - `RET_LISTED` (used by other modules): The target nickname was listed.
  - `RET_PERMISSION`: The calling user does not have permission to make the requested change.
  - `RET_NOSUCHNICK`: The given nickname is not registered.
  - `RET_NICKFORBID`: The given nickname is forbidden.
  - `RET_LISTFULL` (`access_add()` only): The channel's access list is full and the given nickname is not already on the list.
  - `RET_NOENTRY` (`access_del()` only): The given nickname does not exist on the channel's access list.
  - `RET_INTERR`: An internal error occurred.
  Note that successful return codes are positive, while failure return codes are negative (zero is not used as a return code).

`access.c` also includes initialization code in the `init_access()` routine, called from the `chanserv/main` module's `init_module()`. This routine initializes the `def_levels[]` and `lev_is_max[]` arrays, as well as the `flags` field of the `target.cumode` structure for privileges that set or clear modes; it also disables (by removing from the table) any privileges for features not supported by the IRC protocol in use. Implementation note: This relies on the current design of Services, in which the protocol module will never be changed while the program is running. If this design is changed, the code will need to be updated to leave the appropriate entries in the table, perhaps by adding a "disabled" flag or field to each entry.

There is also a corresponding `exit_access()` routine; it does nothing, but is included for completeness.

Back to top

##### 7-4-2-2. Manipulation via `ACCESS` and `LEVELS`

The `chanserv/access-levels` module is one of two modules for manipulating channel access lists. It provides direct access to both the channel access list itself, via the `ACCESS` command, and channels' privilege level settings, via the `LEVELS` command.

Since the functions available in both commands change depending on runtime parameters, their help messages are handled by a "`HELP`" callback function, `do_help()`. In addition to description of the two commands, a third help option, `LEVELS DESC` provides descriptions of the available channel privileges; unlike most other help messages, the text is generated on the fly from the `levelinfo[]` array and corresponding description strings.

The `ACCESS` command handler, `do_access()`, calls one of several subroutines to perform each of the available actions, depending on the subcommand given. Two of these, `do_access_add()` and `do_access_del()`, simply call the `access_add()` and `access_del()` routines mentioned in section 7-4-2-1, sending appropriate result messages to the calling client depending on the return codes from those functions. `do_access_list()` and `do_access_listlevel()` select access entries for listing based on the given parameters, then call `access_list()`, defined below the `ACCESS` subcommand handlers, to actually send the list text to the calling client. (The `sent_header` parameter to `access_list()` is used to to record whether a list header message has been sent.) The final subcommand handler, `do_access_count()`, simply counts up the number of (active) access entries in the list and sends that in a response message to the calling client.

The `LEVELS` command also has several subcommands, though these are all handled within the command handler `do_levels()`. The command implementation itself is fairly straightforward, with `SET` and `DISABLE` searching the `levelinfo[]` array for the named privilege, and `LIST` iterating through that array to display the current level for each privilege.

Back to top

##### 7-4-2-3. Manipulation via `XOP`

The `chanserv/access-xop` module, defined in `access-xop.c`, provides the `XOP` command set for managing channel access lists. While these commands present the access list as several distinct lists (SOP, AOP, and so on), they are in fact only different views of the same channel access list, each containing nicknames with a particular predefined access level. This has the following side effects:

- A particular nickname cannot be present on two or more lists simultaneously (this would require having two entries on the access list for the same nickname). Attempting to add a nickname that is already on a different list is treated as an access level change.
- Changes to the access list made by the `ACCESS` command will also be reflected in the relevant XOP lists. If the `ACCESS` command is used to add a nickname at a level not associated with any `XOP` command, that nickname will be invisible from every `XOP` list, even if it would have privileges associated with one or more of the lists. Implementation note: One could conceivably change the `XOP LIST` commands to list all entriess in intermediate or extreme ranges; for example, nicknames with an access level between `ACCLEV_AOP` and `ACCLEV_SOP` could be shown on the AOP list, and nicknames with an access level below `ACCLEV_NOP` could be shown on the NOP list.
- If the channel's privilege levels are changed (such as with the `LEVELS` command), the `XOP` commands may no longer perform as their help messages describe, because the XOP lists are associated with hardcoded access levels rather than specific privileges. Implementation note: Attempting to base the lists on privilege levels could have unusual results if, for example, the auto-op privilege was lowered below auto-voice.

The commands themselves are handled by a single central routine, `handle_xop()`, which is called from each command's particular handler with the appropriate access level. `handle_xop()` works much like `do_access()` from `access-levels.c` (in fact, it is a modified copy of `do_access()`, and its subroutines are likewise derived from the handlers for the `ACCESS` subcommands). Response messages include the list name (SOP, AOP, etc.) as a parameter in the message, and this name is determined based on the command's access level using the `XOP_LISTNAME` macro at the top of the file.

Back to top

### 7-5. MemoServ

The MemoServ pseudoclient serves as an adjunct to NickServ, allowing short messages (memos) to be sent between users and storing those messages for the recipient. Like other pseudoclients, the majority of MemoServ's functionality is implemented in the `memoserv/main` module, described below in section 7-5-1; additional modules allow users to maintain "ignore" lists (`memoserv/ignore`, section 7-5-2) and have memos forwarded via E-mail (`memoserv/forward`, section 7-5-3).

Back to top

#### 7-5-1. MemoServ core functionality

Unlike OperServ, NickServ, and ChanServ, MemoServ only provides additional functionality to users (nicknames and, to an extent, channels) already registered with Services. For this reason, the main MemoServ code is comparatively simple, and the `memoserv/main` module is implemented by a single source file, `main.c`. There is also a header file, `memoserv.h`, containing structures and definitions used by MemoServ.

Back to top

##### 7-5-1-1. Memo data structures

The structure used to store memos are defined in the header file `memoserv.h`. Each memo is stored in a structure called (appropriately enough) `Memo`, and the set of memos belonging to a particular nickname group is stored in a `MemoInfo` structure. The `Memo` structure contains the following fields:

`uint32 number`
  The index number associated with this memo, for use with MemoServ commands such as `READ` and `DEL`. Note that while the array of memos in a `MemoInfo` structure is kept free of holes caused by memo deletions, memo index numbers do not change except as a result of the `RENUMBER` command, so there is not a one-to-one mapping between the two.
`int16 flags`
  Flags associated with the memo. Zero or more of the following constants, OR'd together:
  - `MF_UNREAD`: The memo has not yet been read.
  - `MF_EXPIREOK`: The memo is allowed to expire. Implementation note: The sense of this flag is opposite that of MemoServ's design, which expires any memos except those explicitly locked with the `SAVE` command. This was done to keep compatibility with memos sent using old versions of Services, in which memos did not expire; rather than explicitly adding a "locked" flag to every memo when loading databases from such a version, the flag takes advantage of the fact that the corresponding bit in such memos is zero. This way, for a memo to expire, the flag must be explicitly enabled by the new version when the memo is sent.
`time_t time`
  The timestamp when the memo was sent.
`time_t firstread`
  The time at which the memo was first read by its recipient. This is stored to ensure that an unread memo does not expire immediately after it is first read (the `MSExpireDelay` configuration option controls how long MemoServ will wait after this timestamp before expiring the memo).
`char sender[NICKMAX]`
  The nickname of the client that sent the memo.
`char *channel`
  For memos sent to channels, the name of the channel to which the memo was sent. `NULL` for other memos.
`char *text`
  The text of the memo.

The `MemoInfo` structure contains:

`Memo *memos`
`int16 memos_count`
  A variable-length array containing the memos associated with this structure.
`int16 memomax`
  The maximum number of memos this nickname group is allowed to have stored. Either a nonnegative literal value (zero is allowed, and means that the nickname group cannot receive any memos at all), or one of the following constants:
  - `MEMOMAX_UNLIMITED`: There is no limit on the number of nicknames that can be stored.
  - `MEMOMAX_DEFAULT`: The default limit, set by the `MSMaxMemos` configuration option, is used. (If `MSMaxMemos` is changed, the new limit will be automatically applied.)
  The constant `MEMOMAX_MAX` is also provided to give the largest possible maximum value (a side-effect of storing the value in 16 bits).

Each `MemoInfo` structure is stored as part of the corresponding nickname group's `NickGroupInfo` structure (see section 7-3-1-1). In previous versions, channels had associated memo lists as well, but this feature was removed for version 5.1 in favor of the current system of distributing channel memos to privileged users.

There are two more constants in `memoserv.h`: `MS_RECEIVE_PRI_CHECK` and `MS_RECEIVE_PRI_DELIVER`. These are callback priorities that can be used with `add_callback_pri()` to allow a "`receive memo`" callback function to ensure it is called before functions which try to deliver the memo. Both the `memoserv/ignore` and `memoserv/forward` modules take advantage of these constants.

Back to top

##### 7-5-1-2. The `memoserv/main` module

The `memoserv/main` module is defined in the file `main.c`, and aside from the lack of any auxiliary source files, its structure is more or less the same as that of the other pseudoclients' core modules.

Since, as mentioned above, the `MemoInfo` structures containing memo data are stored as part of the corresponding `NickGroupInfo` structures, special handling is required when moving the data to or from persistent storage. MemoServ uses iterator functions for the `MemoInfo` table that in turn iterate through `NickGroupInfo` structures, and sets up a dummy record containing each record's associated nickname group ID, much like the `nickserv/access` module (see section 7-3-2). For individial `Memo` records, an additional iterator is used to loop through each memo in every `MemoInfo` structure.

Most MemoServ actions are implemented by separate routines in the "MemoServ private routines" section of the file, rather than directly in the command handlers. These functions are:

- `send_memo()` and `send_chan_memo()`, which send memos to users and channels respectively;
- `list_memo()`, which sends the calling client a one-line description of a memo, and `list_memo_callback()`, a callback function to do the same thing;
- `read_memo()` and `read_memo_callback`, which display the text of a memo;
- `save_memo()` and `save_memo_callback`, which mark memos as non-expiring; and
- `del_memo()` and `del_memo_callback()`, which delete memos.

Other utility routines include:

`void check_memos(User *u)`
  Checks whether the given client's nickname group has any unread memos, sending an appropriate message to the client if so (and if the `NF_MEMO_SIGNON` flag is set for the nickname group). Called by the "`user create`" callback function `do_user_create()` if the client is recognized.
`void expire_memos(MemoInfo *mi)`
  Deletes all memos in the given `MemoInfo` structure that are eligible for expiration.
`MemoInfo *get_memoinfo(const char *name, NickGroupInfo **owner_ret, int *error_ret`
  Returns the `MemoInfo` structure corresponding to the given nickname, or `NULL` on error. On success, `*owner_ret` is set to point to the `NickGroupInfo` structure for the corresponding nickname group; on error, `*error_ret` is set to one of the following error codes:
  - GMI_INTERR: An internal error occurred.
  - GMI_FORBIDDEN: The given nickname is forbidden.
  - GMI_SUSPENDED: The given nickname is suspended.
  - GMI_NOTFOUND: The given nickname is not registered.
  Implementation note: This function's primary purpose of handling both nicknames and channel names transparently was obsoleted with the redesign of channel memo handling; however, the function still serves the purpose of checking for unregistered, forbidden, or suspended nicknames, eliminating the necessity to include such checks directly in every command handler.

The command handlers are for the most part simple, only needing to parse parameters and call the appropriate utility routine. The two commands which are implemented directly, `SET` and `INFO`, are mostly branching trees for the various nickname options and statuses.

Back to top

#### 7-5-2. Memo ignore lists

The `memoserv/ignore` module was added to allow users a way to block memos from unwanted senders such as spammers. The module is implemented by the source file `ignore.c`.

For the most part, `ignore.c` works in the same way as the `nickserv/access` and `nickserv/autojoin` modules. The one routine unique to `memoserv/ignore` is the `check_if_ignored()` routine, added as a callback function to the core MemoServ module's "`receive memo`" callback at priority `MS_RECEIVE_PRI_CHECK`. The routine runs through the nickname group's ignore list twoce: the first time, it treats each entry as a `Nick!user@host` mask which is compared against that of the memo sender, while the second time, it treats each entry as a nickname, and the memo is blocked if that nickname's nickname group ID matches that of the sender (thus proventing malicious clients from evading the block by simply changing to another linked nick). If thd memo is blocked, the routine returns the language string index `MEMO_C_GETS_NO_MEMOS` (the same as is used if the target has a memo limit of zero), to prevent leaking information about the ignore list contents.

Back to top

#### 7-5-3. Memo forwarding

The `memoserv/forward` module, defined in the source file `forward.c`, provides an interface between memos in Services and an external mail system. As such, one of its prerequisite modules (other than `memoserv/main`, of course) is `nickserv/mail-auth`, to reduce the possibility of Services being abused to send mail to arbitrary addresses.

The module includes two methods of forwarding memos: manual and automatic. The former, manual forwarding, is done through the `FORWARD` command, implemented by the `do_forward()` routine and its subroutines `fwd_memo()` and `fwd_memo_callback()`. As with other commands that allow users to send mail via Services, the command handler includes a check that the command is not used more frequently than the configuration option `MSForwardDelay` specifies. If this and the other checks pass, the command handler takes one of two actions, depending on its parameter. If "`ALL`" was given, then `fwd_memo()` is called for each memo in the calling client's `MemoInfo` structure; otherwise, the parameter is passed to `process_numlist()` to call the `fwd_memo_callback()` callback function (which in turn calls `fwd_memo()`) for each memo specified in the index list.

`fwd_memo()` itself does not perform the actual sending of the mail message; rather, it accumulates message body text in its parameters `char **p_body` and `int *p_bodylen` (both of which are modified as a result of the routine). This allows the caller to combine multiple memos into a single message without knowing ahead of time how many or which memos are to be sent.

The second method of forwarding, automatic forwarding on receipt, is implemented by the `do_receive_memo()` function, attached to the MemoServ "`receive memo`" callback, along with the `do_set_forward()` option handler (attached to the "`SET`" callback) to set forwarding options for a nickname group. Of the forwarding options, `ON` (flag `NF_MEMO_FWD`) and `OFF` (no flags set) are fairly obvious. For `COPY` (flags `NF_MEMO_FWD` and `NF_MEMO_FWDCOPY`), however, it is worth noting that (as also mentioned in the user's manual and help messages) memos will be rejected when the recipient has a full list of memos. This is partly a result of the callback implementation, in the sense that the check for a full memo list is made before the callback is ever called; but it also ensures that every memo received by the user is either processed completely (both forwarded and saved) or not at all. This can be important if, for example, a user uses `SET FORWARD COPY` to save copies of memos to an archival E-mail address, but only uses Services to actually read the memos. If a memo that would overflow the list was nonetheless forwarded and treated as "sent", the sender would be left wondering why the recipient was not responding to the purportedly delivered memo.

Back to top

### 7-6. StatServ

The StatServ pseudoclient is intended to record and provide network statistics; however, mostly due to lack of developer interest, it has floundered. The module is defined in the `modules/statserv` directory by a single source file, `main.c`, and an accompanying header file, `statserv.h`.

Back to top

#### 7-6-1. StatServ data structures

The current implementation of StatServ only records data for servers. This data is stored in a `ServerStats` structure, defined in `statserv.h` and containing the following fields:

`ServerStats *next, *prev`
  Used to maintain the linked list of `ServerStats` structures.
`char *name`
  The name of the server to which this structure applies.
`time_t t_join`
  The timestamp at which the server most recently joined the network. Zero if the server is not currently connected.
`time_t t_quit`
  The timestamp at which the server last disconnected. Zero if Services has never seen the server disconnect.
`char *quit_message`
  The quit message used the last time the server disconnected. `NULL` if Services has never seen the server disconnect.
`int usercnt, opercnt`
  The current number of clients and IRC operators on the server.

As the data has no direct references to other data stored persistently, saving the `ServerStats` records is straightforward.

There are also commented-out definitions for `MinMax` and `MinMaxHistory` structures, and `MinMaxHistory` fields in `ServerStats`; these were originally intended for keeping a history of client and operator counts on each server, but this functionality was never implemented.

Back to top

#### 7-6-2. The StatServ module

The module itself, defined in `main.c`, is likewise quite simple. It follows the general layout of other pseudoclient core modules, though it only has two commands (other than `HELP`) and a few callback routines.

The two commands, `SERVERS` and `USERS`, are handled by `do_servers()` and `do_users()` respectively. The latter needs no additional explanation, as it only sends the calling client the user and operator counts it maintains internally (via the client-related callbacks mentioned below). `do_servers()` is only slightly more complex, handling four distinct subcommands. Three of these (`STATS`, `LIST`, and `VIEW`) extract information from the `ServerStats` structures and send them to the calling client; the last, `DELETE`, allows a structure to be deleted, and is protected by an `is_services_admin()` check in the `if` chain.

In order to keep track of statistics, StatServ naturally relies on callbacks. To watch for connecting and disconnecting servers, StatServ adds callback functions to the core's "`server create`" and "`server delete`" callbacks; these functions update the relevant `ServerStats` structures as servers join and leave the network, with `stats_do_server()` (the "`server create`" handler) creating a new `ServerStats` structure if it sees a server connect that is not recorded in the `ServerStats` structure table.

StatServ also uses the "`user create`", "`user delete`", and "`user MODE`" callbacks to keep track of the number of clients and IRC operators on the network, stored in the file-scope variables `usercnt` and `opcnt` respectively. Note that this overlaps slightly with the maximum client count maintained by OperServ; a good argument could be made for moving this functionality to StatServ as well, but it has been left in OperServ for historical reasons (the maximum client count monitoring functionality has been part of OperServ since the earliest versions of Services, long before StatServ existed).

Back to top

### 7-7. Miscellaneous pseudoclients

Aside from the standard pseudoclients listed above, there are two additional pseudoclients, HelpServ and DevNull, provided in case they can be of use. These pseudoclients are disabled in the default Services configuration. These modules are so simple as to not warrant their own subdirectories, and are stored in the `modules/misc` directory.

Back to top

#### 7-7-1. HelpServ

The HelpServ pseudoclient, defined in `modules/misc/helpserv.c`, uses the parameters given in each `PRIVMSG` it receives to form a pathname for a text file, which (if it exists) is then sent to the calling client as `NOTICE`s. The pathname is formed by concatenating the path given by the `HelpDir` module configuration setting (the example configuration file uses `helpfiles`, relative to the Services data directory) with each of its space-separated parameters, inserting a path separator ("`/`") between each element. To allow the parameter to be case-insensitive even when using case-sensitive filesystems, all uppercase characters in the parameters are lowercased.

Since this involves access to a file specified by an arbitrary user-specified string, the routine must take care not to allow access to inappropriate files. This is done by replacing all slashes and periods in the string with underscores, to prevent a malicious user from specifying a parameter like "`../../../../../etc/passwd`". (If there are explicit symbolic links to parent directories, of course, HelpServ will happily follow them.) Implementation note: As a general rule, when sanitizing user input like this it is better to make a set of explicitly allowed characters and delete or convert anything outside that set. However, pathnames on Unix systems are simple and well defined: disregarding the effects of symbolic links, only a slash can allow access to file entries outside of the current directory, and "`.`" and "`..`" are the only entries that are automatically created within each directory, so the set of allowed characters is essentially "everything except `/` and `.`".

In the earliest versions of Services, HelpServ was a standard pseudoclient, and other pseudoclients made use of its functionality to display their own help messages, which were stored as text files under the `data/helpfiles` directory. However, the help messages were later moved to string data stored directly in the executable file, and then to the current model of language files, diluting the usefulness of HelpServ itself. The module has nonetheless been left in Services in case it is useful for things such as providing network information.

Back to top

#### 7-7-2. DevNull

The DevNull pseudoclient, defined in `modules/misc/devnull.c`, is an extremely simple pseudoclient which, like its Unix namesake `/dev/null`, simply discards any `PRIVMSG`s sent to it. It is not particularly useful in the ordinary course of events, but the author has made use of it as a default `/query` target to prevent messages from going to unintended users.

Back to top

Previous section: Database handling | Table of Contents | Next section: Other modules