2019-01-23 09:30:51 +01:00

3816 lines
198 KiB
HTML

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11-strict.dtd">
<!-- Annoyingly, <u> has been removed, so replacing it with <span style="text-decoration: underline">, as in 2.html -->
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Style-Type" content="text/css"/>
<style type="text/css">@import "style.css";</style>
<title>IRC Services Technical Reference Manual - 7. Services pseudoclients</title>
</head>
<body>
<h1 class="title" id="top">IRC Services Technical Reference Manual</h1>
<h2 class="section-title">7. Services pseudoclients</h2>
<p class="section-toc">
7-1. <a href="#s1">Basic features of a pseudoclient</a>
<br/>7-2. <a href="#s2">OperServ</a>
<br/>&nbsp;&nbsp;&nbsp;&nbsp;7-2-1. <a href="#s2-1">OperServ core functionality</a>
<br/>&nbsp;&nbsp;&nbsp;&nbsp;7-2-2. <a href="#s2-2">Usermask-related functions</a>
<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;7-2-2-1. <a href="#s2-2-1">Common mask data support</a>
<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;7-2-2-2. <a href="#s2-2-2">Autokills</a>
<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;7-2-2-3. <a href="#s2-2-3">S-lines</a>
<br/>&nbsp;&nbsp;&nbsp;&nbsp;7-2-3. <a href="#s2-3">Session limiting</a>
<br/>&nbsp;&nbsp;&nbsp;&nbsp;7-2-4. <a href="#s2-4">News</a>
<br/>7-3. <a href="#s3">NickServ</a>
<br/>&nbsp;&nbsp;&nbsp;&nbsp;7-3-1. <a href="#s3-1">NickServ core functionality</a>
<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;7-3-1-1. <a href="#s3-1-1">Nickname data structures and utility macros</a>
<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;7-3-1-2. <a href="#s3-1-2">Overall module structure</a>
<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;7-3-1-3. <a href="#s3-1-3">The <tt>SET</tt> and <tt>UNSET</tt> commands</a>
<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;7-3-1-4. <a href="#s3-1-4">NickServ utility routines</a>
<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;7-3-1-5. <a href="#s3-1-5">Nickname colliding</a>
<br/>&nbsp;&nbsp;&nbsp;&nbsp;7-3-2. <a href="#s3-2">Nickname access lists</a>
<br/>&nbsp;&nbsp;&nbsp;&nbsp;7-3-3. <a href="#s3-3">Nickname auto-join lists</a>
<br/>&nbsp;&nbsp;&nbsp;&nbsp;7-3-4. <a href="#s3-4">Linking and nickname groups</a>
<br/>&nbsp;&nbsp;&nbsp;&nbsp;7-3-5. <a href="#s3-5">E-mail address authentication</a>
<br/>7-4. <a href="#s4">ChanServ</a>
<br/>&nbsp;&nbsp;&nbsp;&nbsp;7-4-1. <a href="#s4-1">ChanServ core functionality</a>
<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;7-4-1-1. <a href="#s4-1-1">Channel data structures</a>
<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;7-4-1-2. <a href="#s4-1-2">Overall module structure</a>
<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;7-4-1-3. <a href="#s4-1-3">Channel status checking and modification</a>
<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;7-4-1-4. <a href="#s4-1-4">The <tt>SET</tt> and <tt>UNSET</tt> commands</a>
<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;7-4-1-5. <a href="#s4-1-5">ChanServ utility routines</a>
<br/>&nbsp;&nbsp;&nbsp;&nbsp;7-4-2. <a href="#s4-2">Channel access list handling</a>
<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;7-4-2-1. <a href="#s4-2-1">Access list basics</a>
<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;7-4-2-2. <a href="#s4-2-2">Manipulation via <tt>ACCESS</tt> and <tt>LEVELS</tt></a>
<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;7-4-2-3. <a href="#s4-2-3">Manipulation via <tt>XOP</tt></a>
<br/>7-5. <a href="#s5">MemoServ</a>
<br/>&nbsp;&nbsp;&nbsp;&nbsp;7-5-1. <a href="#s5-1">MemoServ core functionality</a>
<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;7-5-1-1. <a href="#s5-1-1">Memo data structures</a>
<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;7-5-1-2. <a href="#s5-1-2">The <tt>memoserv/main</tt> module</a>
<br/>&nbsp;&nbsp;&nbsp;&nbsp;7-5-2. <a href="#s5-2">Memo ignore lists</a>
<br/>&nbsp;&nbsp;&nbsp;&nbsp;7-5-3. <a href="#s5-3">Memo forwarding</a>
<br/>7-6. <a href="#s6">StatServ</a>
<br/>&nbsp;&nbsp;&nbsp;&nbsp;7-6-1. <a href="#s6-1">StatServ data structures</a>
<br/>&nbsp;&nbsp;&nbsp;&nbsp;7-6-2. <a href="#s6-2">The StatServ module</a>
<br/>7-7. <a href="#s7">Miscellaneous pseudoclients</a>
<br/>&nbsp;&nbsp;&nbsp;&nbsp;7-7-1. <a href="#s7-1">HelpServ</a>
<br/>&nbsp;&nbsp;&nbsp;&nbsp;7-7-2. <a href="#s7-2">DevNull</a>
</p>
<p class="backlink"><a href="6.html">Previous section: Database handling</a> |
<a href="index.html">Table of Contents</a> |
<a href="8.html">Next section: Other modules</a></p>
<!------------------------------------------------------------------------>
<hr/>
<h3 class="subsection-title" id="s1">7-1. Basic features of a pseudoclient</h3>
<p>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:</p>
<ul>
<li class="spaced">Register one or more nicknames via the
"<tt>introduce_user</tt>" callback, through which the pseudoclient
communicates with IRC clients; see below for details.</li>
<li class="spaced">Receive commands from IRC clients via <tt>PRIVMSG</tt>
messages. (According to RFC 1459, pseudoclients <i>must not</i>
respond to <tt>NOTICE</tt> messages, in order to prevent infinite
loops in which two pseudoclients repeatedly respond to each others'
notices.)</li>
<li class="spaced">Communicate to IRC clients via <tt>NOTICE</tt> 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
<tt>PRIVMSG</tt> for communication with users, apparently because
some IRC programs do not show <tt>NOTICE</tt> messages to users,
but such requests have been intentionally disregarded for the above
reason.)</li>
<li class="spaced">Provide a set of commands which users can use to invoke
the pseudoclient's functions. (The miscellaneous modules described
in <a href="#s7">section 7-7</a> 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&mdash;note
that the tab character is <i>not</i> treated as a separator).</li>
</ul>
<p>The "<tt>introduce_user</tt>" callback mentioned above is called by the
same-named function, <tt>introduce_user()</tt>, to request that each module
introduce its nickname(s) to the IRC network by calling
<tt>send_pseudo_nick()</tt> (in <tt>send.c</tt>). The callback takes a
single <tt>const char&nbsp;*</tt> parameter, which is <tt>NULL</tt> when
the callback is called at startup, or the nickname seen in a <tt>KILL</tt>
message when one is received; accordingly, pseudoclients should introduce
their nicknames only when the parameter either is <tt>NULL</tt> or matches
(case-insensitively, as determined by <tt>irc_stricmp()</tt>) the nickname
to be introduced. <tt>send_pseudo_nick()</tt> 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:</p>
<ul>
<li><tt><b>PSEUDO_OPER</b></tt>: The client requires IRC operator
privileges. (This does not necessarily guarantee that the client
will be given the <tt>+o</tt> user mode; some IRC servers allow any
Services pseudoclient to use IRC operator functions, and
<tt>+o</tt> is omitted with such servers.)</li>
<li><tt><b>PSEUDO_INVIS</b></tt>: The client should be marked invisible
(user mode <tt>+i</tt>).</li>
</ul>
<p>Command processing is typically performed by hooking into the core's
"<tt>m_privmsg</tt>" 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 "<tt>receive message</tt>" callback as well; this should be
avoided when possible, however, as it circumvents the standard message
processing and can result in network desynchronization.</p>
<p>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 (<tt><span style="text-decoration: underline"><i>type</i></span></tt> is the record data
type, <tt><span style="text-decoration: underline"><i>name</i></span></tt> is a distinguishing name generally
derived from <tt><span style="text-decoration: underline"><i>type</i></span></tt>, and <tt><span style="text-decoration: underline"><i>keytype</i></span></tt>
is the data type of the key field):</p>
<dl>
<dt><tt><span style="text-decoration: underline"><i>type</i></span> *<b>add_<span style="text-decoration: underline"><i>name</i></span></b>(<span style="text-decoration: underline"><i>type</i></span> *<i>record</i>)</tt></dt>
<dd>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).</dd>
<dt><tt>void <b>del_<span style="text-decoration: underline"><i>name</i></span></b>(<span style="text-decoration: underline"><i>type</i></span> *<i>record</i>)</tt></dt>
<dd>Deletes the given record from the database. <tt><i>record</i></tt>
is assumed to be valid, <i>i.e.</i> a pointer returned by a
previous call to another database function.</dd>
<dt><tt><span style="text-decoration: underline"><i>type</i></span> *<b>get_<span style="text-decoration: underline"><i>name</i></span></b>(<span style="text-decoration: underline"><i>keytype</i></span> *<i>key</i>)</tt></dt>
<dd>Returns the record for the given key, or <tt>NULL</tt> if the key
is not found in the database.</dd>
<dt><tt>void <b>put_<span style="text-decoration: underline"><i>name</i></span></b>(<span style="text-decoration: underline"><i>type</i></span> *<i>record</i>)</tt></dt>
<dd>Indicates that the given record is no longer in use. Each call to
a database's <tt>get()</tt> function must be followed by a
<tt>put()</tt> call for the same record, unless the record is
deleted first. <i>Implementation note: A <tt>put()</tt> 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 <tt>put()</tt> calls in all necessary cases and no
others; in fact, it appears that <tt>put_nickgroupinfo()</tt> (at
least) is called more often than it should be. It may have been
better to drop <tt>put()</tt> entirely and use other methods of
checking whether records are in use before expiring them.</i></dd>
<dt><tt><span style="text-decoration: underline"><i>type</i></span> *<b>first_<span style="text-decoration: underline"><i>name</i></span></b>()</tt>
<br/><tt><span style="text-decoration: underline"><i>type</i></span> *<b>next_<span style="text-decoration: underline"><i>name</i></span></b>()</tt></dt>
<dd>Iterates through the database. <tt>first()</tt> returns the first
record in the database, and subsequent <tt>next()</tt> calls return
each successive record, finally returning <tt>NULL</tt> when all
records have been iterated through (if there are no records at all,
both <tt>first()</tt> and <tt>next()</tt> return <tt>NULL</tt>).
The order is database-dependent, but no record will be returned
more than once from the time <tt>first()</tt> is called to the time
<tt>next()</tt> returns <tt>NULL</tt>. It is unspecified whether
records added while iterating through the database will be
included in the iteration. Note that these functions are
<i>not</i> considered a "get" for the purposes of the
<tt>put()</tt> function; thus a call to <tt>put()</tt> is not
required for simple iteration, but if any other database function
is to be called before the following <tt>next()</tt>, then an
explicit call to <tt>get()</tt> (and a matching call to
<tt>put()</tt>) must be made to prevent the record in use from
being deleted.</dd>
</dl>
<p>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
<a href="../3.html">section 3 of the user's manual</a>.</p>
<p class="backlink"><a href="#top">Back to top</a></p>
<!------------------------------------------------------------------------>
<hr/>
<h3 class="subsection-title" id="s2">7-2. OperServ</h3>
<p>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.</p>
<p>As with most of the Services pseudoclients, OperServ is composed of one
core or "main" module, <tt>operserv/main</tt>, and several optional modules
providing additional functionality. These are each discussed in separate
sections below.</p>
<p class="backlink"><a href="#top">Back to top</a></p>
<h4 class="subsubsection-title" id="s2-1">7-2-1. OperServ core functionality</h4>
<p>The core functionality of OperServ is contained in the
<tt>modules/operserv/main.c</tt> source file, compiled into the
<tt>operserv/main</tt> 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
<tt>modules/operserv/operserv.h</tt>.</p>
<p><tt>main.c</tt> 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 "<tt>introduce_user</tt>", "<tt>m_privmsg</tt>",
and "<tt>m_whois</tt>" callback functions), and finally the actual command
routines, along with any utility routines needed.</p>
<p>For OperServ, the first item of note is the list of several commands and
command routines inside <tt>#ifdef DEBUG_COMMANDS</tt>. These are, as the
conditional name suggests, commands used for debugging Services, and are
only available to the Services super-user; the commands are described in
detail below.</p>
<p>OperServ stores several values to persistent storage, including the
maximum client count, the time at which that maximum was reached, and the
super-user (<tt>SU</tt> command) password. This data is stored by
aggregating the data into a single structure, <tt>operserv_data</tt>, and
storing that structure as a single database "record" in a table named
"<tt>oper</tt>". Two exported functions, <tt>get_operserv_data()</tt> and
<tt>put_operserv_data()</tt>, are also provided to allow external modules,
in particular the XML import and export modules (see
<a href="8.html#s4">section 8-4</a>), to access the data as well.</p>
<p>In order to check a client's Services privilege level (Services
operator, Services administrator, or Services super-user), OperServ requires
access to the nickname data, in which each registered nickname group's
privilege level is stored (the <tt>os_priv</tt> 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 (<tt>get_nickinfo()</tt>,
<tt>put_nickinfo()</tt>, <tt>_get_ngi()</tt>, <tt>put_nickgroupinfo()</tt>,
<tt>first_nickgroupinfo()</tt>, and <tt>next_nickgroupinfo()</tt>), 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).</p>
<p>The main processing routine itself, <tt>operserv()</tt>, is registered
as a callback function for the "<tt>m_privmsg</tt>" callback, called for
each <tt>PRIVMSG</tt> 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
<tt>SU</tt> and <tt>SET SUPASS</tt> commands are replaced with a dummy
string to avoid leaving the super-user password in the log file. (OperServ
has no way to detect if one of these commands is misspelled, so for
example, a mistaken <tt>SET SUPSAS</tt> will be logged in full, including
the password.) Next, the command name is extracted from the message
using the <tt>strtok()</tt> function; this also prepares <tt>strtok()</tt>
for the command handler to use in extracting the command parameters (see
below). After handling CTCP <tt>PING</tt> messages separately, OperServ
calls a "<tt>command</tt>" 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).</p>
<p>Following this and other callback functions are the privilege check
functions; <tt>is_services_root()</tt>, <tt>is_services_admin()</tt>, and
<tt>is_services_oper()</tt>, which check whether a client has Services
super-user, 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 <tt>is_services_root()</tt>
function relies on the <tt>ServicesRoot</tt> configuration setting, along
with the <tt>UF_SERVROOT</tt> flag in the <tt>User</tt> structure
indicating clients which have successfully used the <tt>SU</tt> command;
lower privilege levels check the <tt>os_priv</tt> field of the nickname
group data structure (see <a href="#s3-1">section 7-3-1</a>) 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
<tt>has_priv</tt> member of a <tt>Command</tt> structure. There is another
routine, <tt>nick_is_services_admin()</tt>, 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.</p>
<p>The command routines themselves are fairly straightforward. One thing
to note is that the routines all obtain parameters via
<tt>strtok(NULL,...)</tt> and <tt>strtok_remaining()</tt>; this relies on
the fact that <tt>operserv()</tt> leaves the message string in the
<tt>strtok()</tt> buffer after stripping the command name, so that each
routine can parse the command's parameters as appropriate for that command.
<tt>strtok_remaining()</tt> is used when the full remaining string is
desired, such as when sending a global message; this function is preferred
to <tt>strtok(NULL,"")</tt> because the latter can leave leading or
trailing whitespace in the result.</p>
<p>The processing for the <tt>HELP</tt> command, in <tt>do_help()</tt>, 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 <tt>COMMANDS</tt>
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 <tt>help_cmd()</tt>
routine in the Services core (see <a href="2.html#s10">section 2-10</a>)
sends a help message as defined by the <tt>Command</tt> structure.</p>
<p>The debug commands, defined toward the bottom of <tt>main.c</tt>, are as
follows:</p>
<dl>
<dt><tt><b>LISTSERVERS</b></tt> (<tt>send_server_list()</tt>)</dt>
<dd>Sends a <tt>NOTICE</tt> for each server in the server list giving
the contents of the <tt>Server</tt> structure.</dd>
<dt><tt><b>LISTCHANS</b></tt> (<tt>send_channel_list()</tt>)</dt>
<dd>Sends two <tt>NOTICE</tt>s for each channel in the channel list.
The first <tt>NOTICE</tt> gives the channel name, creation time,
modes (as a string), limit, key ("<tt>-</tt>") 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.</dd>
<dt><tt><b>LISTCHAN</b> <i>channel</i></tt> (<tt>send_channel_info()</tt>)</dt>
<dd>Sends a <tt>NOTICE</tt> for each client on the given channel with
the client's channel user modes (as a hexadecimal value) and
nickname.</dd>
<dt><tt><b>LISTUSERS</b></tt> (<tt>send_user_list()</tt>)</dt>
<dd>Sends a <tt>NOTICE</tt> for each client on the network, giving
the client's nickname and usermask, the "fake host" or "<tt>-</tt>"
if none, the IP address or "<tt>-</tt>" if none, user modes as a
string, signon timestamp (from the remote server), servicestamp,
server name, nickname status flags or "<tt>-</tt>" if the nickname
is not registered, ignore value, and real name field.</dd>
<dt><tt><b>LISTUSER</b> <i>nickname</i></tt> (<tt>send_user_info()</tt>)</dt>
<dd>Sends three <tt>NOTICE</tt>s describing the state of the given
client. The first is identical to the line that <tt>LISTUSERS</tt>
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.</dd>
<dt><tt><b>LISTTIMERS</b></tt> (<tt>send_timeout_list()</tt>, in
<tt>timeout.c</tt>)</dt>
<dd>Sends a <tt>NOTICE</tt> giving the current time, followed by a
<tt>NOTICE</tt> for each timeout currently in the list giving the
timeout pointer, timestamp, function pointer, and function
argument. This routine is defined in <tt>timeout.c</tt> because
the internal timeout fields are hidden from other source
files.</dd>
<dt><tt><b>MATCHWILD</b> <i>pattern</i> <i>string</i> </tt> (<tt>do_matchwild()</tt>)</dt>
<dd>Sends a <tt>NOTICE</tt> giving the result of calling
<tt>match_wild</tt> with the given parameters.</dd>
<dt><tt><b>SETCMODE [<i>channel</i> <i>modes</i> <i>mode-params</i>...]</b></tt> (<tt>do_setcmode()</tt>)</dt>
<dd>Calls <tt>set_cmode()</tt> with the given parameters, using
<tt>ServerName</tt> as the message sender. If no parameters are
given, calls <tt>set_cmode(NULL,NULL)</tt> to flush out all
pending mode changes. Note that the number of mode parameters
(including the mode string itself) is limited by the
<tt>SETCMODE_NPARAMS</tt> macro, defined to 10 in the source
code.</dd>
<dt><tt><b>MONITOR-IGNORE</b> [<i>nickname</i>]</tt> (<tt>do_monitor_ignore()</tt>)</dt>
<dd>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 <tt>TimeoutCheck</tt> configuration
variable is set to 10 (milliseconds) when a nickname is given,
potentially causing a reduction in performance; the old value is
<i>not</i> restored when the command is given without a
nickname.</dd>
<dt><tt><b>GETSTRING</b> <i>language</i> <i>string</i></tt> (<tt>do_getstring()</tt>)</dt>
<dd>Sends a <tt>NOTICE</tt> containing the text corresponding to the
given string in the given language. Both <tt><i>language</i></tt>
and <tt><i>string</i></tt> can be given as either names or raw
numbers.</dd>
<dt><tt><b>SETSTRING</b> <i>language</i> <i>string</i> [<i>text</i>]</tt> (<tt>do_setstring()</tt>)</dt>
<dd>Calls <tt>setstring()</tt> to set the given string in the given
language to the given text. If no text is given, the string
becomes empty.</dd>
<dt><tt><b>MAPSTRING</b> <i>old</i> <i>new</i></tt> (<tt>do_mapstring()</tt>)</dt>
<dd>Calls <tt>mapstring()</tt> to map one string to another.
<tt><i>old</i></tt> and <tt><i>new</i></tt> are string names or
numbers.</dd>
<dt><tt><b>ADDSTRING</b> <i>name</i></tt> (<tt>do_addstring()</tt>)</dt>
<dd>Calls <tt>addstring()</tt> to add a string with the given name to
the language table. On success, sends a <tt>NOTICE</tt> with the
new string number.</dd>
</dl>
<p class="backlink"><a href="#top">Back to top</a></p>
<h4 class="subsubsection-title" id="s2-2">7-2-2. Usermask-related functions</h4>
<h5 class="subsubsubsection-title" id="s2-2-1">7-2-2-1. Common mask data support</h5>
<p>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 <tt>MaskData</tt> structure and its generic processing
routines included in the core OperServ module, located in
<tt>maskdata.c</tt> and <tt>maskdata.h</tt>. 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 <tt>mask</tt>, <tt>reason</tt>,
<tt>who</tt>, <tt>time</tt>, <tt>expires</tt>, and <tt>lastused</tt>
fields of the structure, respectively. The remaining fields (other than
the record management fields <tt>next</tt>, <tt>prev</tt>, and
<tt>usecount</tt>) are: <tt>type</tt>, containing an 8-bit value
identifying the type of mask; <tt>num</tt>, giving the user-visible index
number for session exception records (see <a href="#s2-3">section
7-2-3</a>); and <tt>limit</tt>, used for the session limit in session
exception records.</p>
<p>As mentioned above, each <tt>MaskData</tt> record has an associated
type; more accurately, there are multiple sets of <tt>MaskData</tt>
records, one set for each type (the <tt>type</tt> 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 <tt>MD_*</tt> constants in <tt>maskdata.h</tt>, are:</p>
<ul>
<li><b><tt>MD_AKILL</tt>:</b> An autokill record.</li>
<li><b><tt>MD_EXCLUDE</tt>:</b> An autokill exclusion record.</li>
<li><b><tt>MD_EXCEPTION</tt>:</b> A session exception record.</li>
<li><b><tt>MD_SGLINE</tt>:</b> An SGline record.</li>
<li><b><tt>MD_SQLINE</tt>:</b> An SQline record.</li>
<li><b><tt>MD_SZLINE</tt>:</b> An SZline record.</li>
</ul>
<p>It is worth noting that (as also mentioned in <a href="#s2-2-3">section
7-2-2-3</a> below) the values chosen for <tt>MD_SGLINE</tt>,
<tt>MD_SQLINE</tt>, and <tt>MD_SZLINE</tt> are the ASCII values of the
characters <tt>G</tt>, <tt>Q</tt>, and <tt>Z</tt> 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 "<tt>S%cLINE</tt>" in the format string, where the <tt>%c</tt> is
replaced by the type value.)</p>
<p>One other structure, <tt>MaskDataCmdInfo</tt>, can be found in
<tt>maskdata.h</tt>; this structure collects information particular to a
single <tt>MaskData</tt> 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: <tt>name</tt>, the command name (such as "<tt>AKILL</tt>"
or "<tt>SGLINE</tt>"); <tt>type</tt>, the record type to be used; and
<tt>def_expiry_ptr</tt>, 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 <tt>NULL</tt> if not needed):</p>
<dl>
<dt><tt>void (*<b>mangle_mask</b>)(char *<i>mask</i>)</tt></dt>
<dd>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.</dd>
<dt><tt>int (*<b>check_add_mask</b>)(const User *<i>u</i>, uint8 <i>type</i>, char *<i>mask</i>, time_t *<i>expiry_ptr</i>)</tt></dt>
<dd>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.</dd>
<dt><tt>void (*<b>do_add_mask</b>)(const User *<i>u</i>, uint8 <i>type</i>, MaskData *<i>md</i>)</tt></dt>
<dd>Performs any extra actions necessary when a mask is added to the
database, such as sending that mask to the network.</dd>
<dt><tt>void (*<b>do_del_mask</b>)(const User *<i>u</i>, uint8 <i>type</i>, MaskData *<i>md</i>)</tt></dt>
<dd>Performs any extra actions necessary when a mask is removed from
the database.</dd>
<dt><tt>int (*<b>do_unknown_cmd</b>)(const User *<i>u</i>, const char *<i>cmd</i>, char *<i>mask</i>)</tt></dt>
<dd>Processes an unknown subcommand for the associated command,
returning nonzero if the subcommand was handled, zero otherwise.</dd>
</dl>
<p>The function which implements the mask-related command support is
<tt>do_maskdata_cmd()</tt>, defined below the database support functions in
<tt>maskdata.c</tt>. This function behaves in the same way as a standard
pseudoclient command handler, taking a single <tt>User&nbsp;*</tt>
parameter giving the client that sent the command, and retrieving command
parameters via <tt>strtok(NULL,...)</tt>. After obtaining the subcommand
name and its parameters (with double-quote processing for masks), the
routine checks for the known subcommands <tt>ADD</tt>, <tt>DEL</tt>,
<tt>CLEAR</tt>, <tt>LIST</tt>, <tt>VIEW</tt>, <tt>CHECK</tt>, and
<tt>COUNT</tt>, processing each appropriately. If the subcommand is not
one of these, it is passed to the command's <tt>do_unknown_cmd()</tt>
function; if that function returns zero or does not exist, an error is
sent to the client.</p>
<p>The subcommands themselves are implemented by local routines:
<tt>do_maskdata_add()</tt>, <tt>do_maskdata_del()</tt>, 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
<tt>MaskDataCmdInfo</tt> structure to send replies to the client that
issued the command.</p>
<p>There are also three utility functions defined after the command
handler routines:</p>
<dl>
<dt><tt>void *<b>new_maskdata</b>()</tt></dt>
<dd>Allocates, initializes, and returns a new <tt>MaskData</tt>
structure. (The return value is <tt>void&nbsp;*</tt> to avoid a
type warning in the database table declaration.)</dd>
<dt><tt>void <b>free_maskdata</b>(void *<i>record</i>)</tt></dt>
<dd>Frees the <tt>MaskData</tt> structure pointed to by
<tt><i>record</i></tt>, Does nothing if <tt><i>record</i></tt> is
<tt>NULL</tt>.</dd>
<dt><tt>char *<b>make_reason</b>(const char *<i>format</i>, const MaskData *<i>data</i>)</tt></dt>
<dd>Returns the reason string for the given mask and format string.
If <tt><i>format</i></tt> contains a "<tt>%s</tt>", it will be
replaced by the mask's reason string in the return value;
otherwise, the returned string is identical to
<tt><i>format</i></tt>. The result string is stored in a static
buffer, which is overwritten by subsequent calls to
<tt>make_reason()</tt>.</dd>
</dl>
<p>With respect to the database management routines, <tt>MaskData</tt>
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 <tt>next</tt> and <tt>prev</tt>
fields are not ordinarily required for array handling, but the code stores
the (integer) array index value in the <tt>next</tt> field via a cast to
<tt>void&nbsp;*</tt>, 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 <tt>num</tt> field is used for a different
purpose&mdash;index numbers for session exceptions&mdash;and is not
available). Aside from the standard database operations, which take a
<tt>uint8 <i>type</i></tt> parameter in addition to the mask record itself,
the following functions are included:</p>
<dl>
<dt><tt>MaskData *<b>get_matching_maskdata</b>(uint8 <i>type</i>, const char *<i>str</i>)</tt></dt>
<dd>Searches for and returns (in the same manner as
<tt>get_maskdata()</tt>, including expiration checks) a mask which
matches the wildcard pattern given by <tt><i>str</i></tt>. If more
than one mask matches, an arbitrary one is returned.</dd>
<dt><tt>MaskData *<b>get_exception_by_num</b>(int <i>num</i>)</tt></dt>
<dd>Retrieves a mask of the <tt>MD_EXCEPTION</tt> (session exception)
type by its index number.</dd>
<dt><tt>MaskData *<b>move_exception</b>(MaskData *<i>except</i>, int <i>newnum</i>)</tt></dt>
<dd>Changes the index number of the given <tt>MD_EXCEPTION</tt> mask.
(Internally, this reorders the mask array so that entries remain in
order by index number.)</dd>
</dl>
<p class="backlink"><a href="#top">Back to top</a></p>
<h5 class="subsubsubsection-title" id="s2-2-2">7-2-2-2. Autokills</h5>
<p>As mentioned above, one of the main uses of this mask data code is the
autokill module, <tt>operserv/akill</tt>, defined in <tt>akill.c</tt> and
<tt>akill.h</tt>. While support for the OperServ <tt>AKILL</tt> and
<tt>EXCLUDE</tt> commands simply makes use of the aforementioned
<tt>do_maskdata_cmd()</tt> function, handling for the actual autokills and
autokill exclusions themselves is defined within the autokill module. This
includes:</p>
<ul>
<li class="spaced"><tt>send_akill()</tt> and <tt>cancel_akill()</tt>, and
their autokill exclusion companions <tt>send_exclude()</tt> and
<tt>cancel_exclude()</tt>, which (via callbacks hooked into by
protocol modules) take care of adding and removing autokills and
exclusions on the network. Note that <tt>cancel_akill()</tt> and
<tt>cancel_exclude()</tt> destroy their <tt>char&nbsp;*</tt>
parameters, but as they are only called when deleting a record,
this is not a problem.</li>
<li class="spaced"><tt>do_user_check()</tt>, which hooks into the
"<tt>user check</tt>" callback to check whether a newly connecting
client matches any active autokills or exclusions and take
appropriate action.</li>
<li class="spaced"><tt>create_akill()</tt>, 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.)</li>
<li class="spaced">The data structure and helper functions for
<tt>do_maskdata_cmd()</tt>. In particular, <tt>check_add_akill</tt>
uses simple heuristics to check for masks that are so general that
they would match any conceivable combination of username and
hostname (for example, "<tt>*@*</tt>" or "<tt>?*@*?.*?*</tt>") and
disallow such masks, in order to avoid situations where no one
could connect to the network because every client matched the
autokill.</li>
<li class="spaced">The <tt>AKILLCHAN</tt> command implementation,
<tt>do_akillchan()</tt>. 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 <tt>KILL</tt> 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
<tt>KILL</tt> 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
<tt>User</tt> 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&mdash;hence the vitriolic comment
about that mistake).</li>
<li class="spaced">The "<tt>connect</tt>" callback function, which sends
all autokills to the network on initial connection.</li>
<li class="spaced">The "<tt>expire_maskdata</tt>" timeout function, which
checks for autokill expiration. In order to prevent flooding of
IRC operators, for example when autokills set by an
<tt>AKILLCHAN</tt> command expire, expiration announcements via
<tt>WALLOPS</tt> are (if enabled) only sent at the rate of one per
second, and any further expirations are merged into a single
"<i>nnn</i> more autokills have expired" message sent after all
expirations are complete.</li>
<li class="spaced">The OperServ "<tt>HELP</tt>" callback function, which
is required to display the <tt>AKILL</tt> help message correctly.</li>
<li class="spaced">The OperServ "<tt>STATS ALL</tt>" callback function,
used to calculate and display autokill memory usage in response to
a <tt>STATS ALL</tt> command.</li>
</ul>
<p>One item of interest in the module setup code at the bottom of the
source file is the handling of the <tt>EXCLUDE</tt> command.
<tt>EXCLUDE</tt> can be enabled or disabled via a configuration file
option (<tt>EnableExclude</tt>), for reasons discussed below. Rather than
create two command tables, one with <tt>EXCLUDE</tt> and one without, the
code simply modifies the <tt>EXCLUDE</tt> 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.)</p>
<p>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 <tt>*@*.example.com</tt> and an exclusion for
<tt>*@oper.example.com</tt>. As long as the only users in the
<tt>example.com</tt> domain to connect are from the
<tt>oper.example.com</tt> host, the exclusion will function as expected.
However, as soon as a user from another <tt>example.com</tt> host connects,
the autokill will be triggered and sent out to the network&mdash;with the
unintended result that even users from <tt>oper.example.com</tt> are
prevented from connecting, since Services has no way to inform other
servers on the network about the autokill exclusion.</p>
<p>In order to avoid this problem, the <tt>operserv/akill</tt> 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 <tt>KILL</tt>
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
<tt>EnableExclude</tt> configuration option was added, allowing such
networks to choose between taking advantage of the protocol's autokill
feature and using exclusions with autokills.</p>
<p class="backlink"><a href="#top">Back to top</a></p>
<h5 class="subsubsubsection-title" id="s2-2-3">7-2-2-3. S-lines</h5>
<p>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 <tt>operserv/sline</tt> module, defined in <tt>sline.c</tt> and
<tt>sline.h</tt>.</p>
<p>The S-line module is very similar to the autokill module, both
functionally and internally (in fact, <tt>sline.c</tt> started out as a
copy of <tt>akill.c</tt>). 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 <tt>MaskData</tt> support
functions&mdash;this is the reason for the warning comment in
<tt>maskdata.h</tt> about changing the type values&mdash;and including a
<tt>%c</tt> token in relevant messages which is replaced by the mask type.
This enables a single message, such as "<tt>%d masks on the S%cLINE
list.</tt>", 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.</p>
<p>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 <tt>check_sqline()</tt>, called from the
callback functions <tt>do_user_check()</tt> and
<tt>do_user_nickchange_after()</tt>, because there are several possible
ways of handling a match:</p>
<ul>
<li class="spaced">If the client is an IRC operator and the
<tt>SQlineIgnoreOpers</tt> configuration option is enabled, then
SQlines are not checked at all. (The <tt><i>new_oper</i></tt>
parameter is required because, when called from
<tt>do_user_check()</tt>, the <tt>User</tt> record will not yet
have been created.)</li>
<li class="spaced">If the SQline is for a guest nick, the client is left
alone, though the SQline itself is sent to the network.</li>
<li class="spaced">If the <tt>SQlineKill</tt> configuration option is
<i>not</i> 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.</li>
<li class="spaced">Otherwise, the client is killed, and the SQline is sent
to the network.</li>
</ul>
<p class="backlink"><a href="#top">Back to top</a></p>
<h4 class="subsubsection-title" id="s2-3">7-2-3. Session limiting</h4>
<p>The last method of client control, session limiting, is implemented by
the <tt>operserv/sessions</tt> module, defined in <tt>sessions.c</tt>. The
module consists of two major parts: the actual session maintenance and
limiting code, and session exception mask management via the
<tt>EXCEPTION</tt> command.</p>
<p>The session maintenance functionality is contained within the
<tt>add_session()</tt> and <tt>del_session()</tt> 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 <tt>Session</tt> structures,
one per host. When <tt>add_session</tt> is called, it looks up the
<tt>Session</tt> 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,
<tt>del_session()</tt> decrements the host's client count, deleting the
<tt>Session</tt> structure when the count reaches zero.</p>
<p>The "limiting" part of session limiting is a check in
<tt>add_session()</tt> 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
<tt>DefSessionLimit</tt> 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.</p>
<p>The module also provides a <tt>SESSION</tt> 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
<tt>SESSION</tt> command is available even if actual limiting of sessions
is disabled (by setting <tt>DefSessionLimit</tt> to zero).</p>
<p>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
<tt>MaskData</tt> structures with the <tt>MD_EXCEPTION</tt> 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.</p>
<p>Session exceptions are maintained using the <tt>EXCEPTION</tt> command.
The processing for this command, in <tt>do_exception()</tt> and its
subroutines, is very similar to other mask-type commands like
<tt>AKILL</tt>; however, session exceptions require a limit value in
addition to the mask, and it was considered simpler to keep the handling
for <tt>EXCEPTION</tt> separate rather than modify
<tt>do_maskdata_cmd()</tt> to handle both command formats. The
<tt>EXCEPTION</tt> command also includes a <tt>MOVE</tt> 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).</p>
<p>A careful look at the initialization code will show that the
"<tt>user check</tt>" callback function, <tt>check_sessions()</tt> (which
in turn calls <tt>add_session()</tt>), 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 <a href="4.html#s5-2">section
4-5-2</a> (and is a poor design choice for the reasons described there).</p>
<p class="backlink"><a href="#top">Back to top</a></p>
<h4 class="subsubsection-title" id="s2-4">7-2-4. News</h4>
<p>The last OperServ submodule is the <tt>operserv/news</tt> module,
defined in <tt>news.c</tt> and <tt>news.h</tt>. This is a fairly simple
module, containing routines to handle news item storage and implement the
<tt>LOGONNEWS</tt> and <tt>OPERNEWS</tt> commands.</p>
<p>News items are stored in a single variable-length array of
<tt>News</tt> structures, <tt>newslist</tt>. The <tt>News</tt> structure
contains, aside from the text of the news item itself, the news type
(<tt>NEWS_LOGON</tt> or <tt>NEWS_OPER</tt>), 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 <tt>MaskData</tt> structures,
<tt>next</tt> and <tt>prev</tt> fields are included only to mirror other
structures, and the array index is stored in the <tt>next</tt> field.</p>
<p>The two news commands, <tt>LOGONNEWS</tt> and <tt>OPERNEWS</tt>, share
the same code, <tt>do_news()</tt>; this routine is called by the actual
command handlers with one of the news type codes, either
<tt>NEWS_LOGON</tt> or <tt>NEWS_OPER</tt>, and accesses an array of
language string indices (<tt>msgarray[]</tt>) to return proper messages
for each command, similar to <tt>do_maskdata_cmd()</tt>. Unlike most other
commands (but like the <tt>OPER</tt> and <tt>ADMIN</tt> commands in the
OperServ core), the news commands include a <tt>LIST</tt> command available
to all IRC operators, and other subcommands (<tt>ADD</tt> and <tt>DEL</tt>)
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.</p>
<p class="backlink"><a href="#top">Back to top</a></p>
<!------------------------------------------------------------------------>
<hr/>
<h3 class="subsection-title" id="s3">7-3. NickServ</h3>
<p>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.</p>
<p class="backlink"><a href="#top">Back to top</a></p>
<h4 class="subsubsection-title" id="s3-1">7-3-1. NickServ core functionality</h4>
<p>The core NickServ functionality is implemented by the
<tt>nickserv/main</tt> module. Alongside the primary module source file,
<tt>main.c</tt>, the module makes use of three additional source files:
<tt>collide.c</tt>, handling the disconnection or forced removal of clients
using unauthorized nicknames; <tt>set.c</tt>, implementing the <tt>SET</tt>
command and its subcommands; and <tt>util.c</tt>, containing various
utility routines used by NickServ.</p>
<p>NickServ makes use of two distinct header files. One,
<tt>nickserv.h</tt>, defines the data structures used for storing nickname
information (<tt>NickInfo</tt> and <tt>NickGroupInfo</tt>, described below)
along with declarations of exported routines and macros used with nickname
records. The other, <tt>ns-local.h</tt>, contains declarations of routines
used within NickServ, necessarily declared <tt>extern</tt> because they
reside in separate source files but not intended to be used outside the
NickServ modules.</p>
<p class="backlink"><a href="#top">Back to top</a></p>
<h5 class="subsubsubsection-title" id="s3-1-1">7-3-1-1. Nickname data structures and utility macros</h5>
<p>Two separate structures are used to store nickname data; this is to
facilitate the implementation of nickname links, as described in
<a href="#s3-4">section 7-3-4</a>. One structure, <tt>NickInfo</tt>,
contains data that is distinct for each individual nickname; the other,
<tt>NickGroupInfo</tt>, contains data that is shared among each group of
linked nicknames.</p>
<p>The <tt>NickInfo</tt> structure includes the following data:</p>
<dl>
<dt><tt>NickInfo *<b>next</b>, *<b>prev</b></tt></dt>
<dd>Used to link records together in the internal hash table.</dd>
<dt><tt>int <b>usecount</b></tt></dt>
<dd>The record's usage count (number of gets minus number of puts).
<i>(Implementation note: As noted in <a href="#s1">section 7-1</a>,
this field currently serves no actual purpose.)</i></dd>
<dt><tt>char <b>nick</b>[NICKMAX]</tt></dt>
<dd>The actual nickname. Capitalization is as used when the nickname
was registered, and does not change due to later actions. The
buffer size, <tt>NICKMAX</tt>, is defined in the global header
file <tt>defs.h</tt>.</dd>
<dt><tt>int16 <b>status</b></tt></dt>
<dd>The nickname's status. This is a combination of zero or more of
the following flags:
<ul>
<li><b><tt>NS_VERBOTEN</tt>:</b> The nickname is a forbidden
nickname set with the <tt>FORBID</tt> 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.)</li>
<li><b><tt>NS_NOEXPIRE</tt>:</b> The nickname is not to be expired
regardless of how long it remains unused (<tt>SET
NOEXPIRE</tt>).</li>
<li><b><tt>NS_KILL_HELD</tt>:</b> 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.</li>
<li><b><tt>NS_GUESTED</tt>:</b> An IRC message has been sent to
change the nickname of the client using the nickname, but
the nickname change has not yet occurred.</li>
</ul>
Of these flags, <tt>NS_VERBOTEN</tt> and <tt>NS_NOEXPIRE</tt> are
"permanent" flags (collected in the <tt>NS_PERMANENT</tt> mask),
which are retained across restarts of Services, while
<tt>NS_KILL_HELD</tt> and <tt>NS_GUESTED</tt> are "temporary" flags
(collected in the <tt>NS_TEMPORARY</tt> 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.</dd>
<dt><tt>char *<b>last_usermask</b></tt></dt>
<dd>The last <tt><i>user</i>@<i>host</i></tt> mask used by the owner
of the nickname (<i>i.e.,</i> a client authorized to use the
nickname). If the owner is currently online, that client's
<tt><i>user</i>@<i>host</i></tt> mask is used. On IRC networks
where a "fake hostname" is available, that hostname is used
instead of the client's actual hostname.</dd>
<dt><tt>char *<b>last_realmask</b></tt></dt>
<dd>Like <tt>last_usermask</tt>, but uses the real hostname instead of
any "fake hostname". On networks without such a fake hostname,
this field is identical to <tt>last_usermask</tt>.</dd>
<dt><tt>char *<b>last_realname</b></tt></dt>
<dd>The last "real name" string used by the owner of the nickname.</dd>
<dt><tt>char *<b>last_quit</b></tt></dt>
<dd>The message used the last time the owner quit IRC. <tt>NULL</tt>
if not available, such as for newly-registered nicknames.</dd>
<dt><tt>time_t <b>time_registered</b></tt></dt>
<dd>The timestamp when the nickname was registered.</dd>
<dt><tt>time_t <b>last_seen</b></tt></dt>
<dd>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.</dd>
<dt><tt>uint32 <b>nickgroup</b></tt></dt>
<dd>The ID of the nickname group with which this nickname is associated
(see below).</dd>
<dt><tt>uint32 <b>id_stamp</b></tt></dt>
<dd>The servicestamp of the client that last identified for the
nickname. Used to retain identification status across Services
restarts.</dd>
<dt><tt>User *<b>user</b></tt></dt>
<dd><i>Not saved to persistent storage.</i> A pointer to the
<tt>User</tt> structure for the client currently using the
nickname, or <tt>NULL</tt> if the nickname is not currently in
used.</dd>
<dt><tt>int16 <b>authstat</b></tt></dt>
<dd><i>Not saved to persistent storage.</i> Zero or more of the
following flags, indicating the nickname's authentication status:
<ul>
<li><b><tt>NA_IDENTIFIED</tt>:</b> The current user of the nickname
has identified by password as the nickname's owner.
Mutually exclusive with <tt>NA_IDENT_NOEMAIL</tt>.</li>
<li><b><tt>NA_IDENT_NOMAIL</tt>:</b> 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
<tt>NA_IDENTIFIED</tt>.</li>
<li><b><tt>NA_RECOGNIZED</tt>:</b> The current user of the nickname
is recognized via the nickname access list (see
<a href="#s3-2">section 7-3-2</a>).</li>
</ul></dd>
<dt><tt>int <b>bad_passwords</b></tt></dt>
<dd><i>Not saved to persistent storage.</i> 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.</dd>
</dl>
<p>The <tt>NickGroupInfo</tt> structure includes the following data:</p>
<dl>
<dt><tt>NickGroupInfo *<b>next</b>, *<b>prev</b></tt></dt>
<dd>Used to link records together in the internal hash table.</dd>
<dt><tt>int <b>usecount</b></tt></dt>
<dd>The record's usage count (number of gets minus number of puts).
<i>(Implementation note: As noted in <a href="#s1">section 7-1</a>,
this field currently serves no actual purpose.)</i></dd>
<dt><tt>uint32 <b>id</b></tt></dt>
<dd>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 <tt>NickInfo</tt> records to indicate the lack of an
associated nickname group, as for forbidden nicknames).</dd>
<dt><tt>nickname_t *<b>nicks</b></tt>
<br/><tt>uint16 <b>nicks_count</b></tt></dt>
<dd><i>Not saved to persistent storage.</i> 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.</dd>
<dt><tt>uint16 <b>mainnick</b></tt></dt>
<dd>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 <tt>nicks[]</tt> array.</dd>
<dt><tt>Password <b>pass</b></tt></dt>
<dd>The password used for identification for the nickname group.</dd>
<dt><tt>char *<b>url</b></tt></dt>
<dd>A URL associated with the nickname group. Can be arbitrarily set
by the owner.</dd>
<dt><tt>char *<b>email</b></tt></dt>
<dd>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 <tt>NSRequireEmail</tt> configuration option.</dd>
<dt><tt>char *<b>last_email</b></tt></dt>
<dd>When mail-based authentication (see <a href="#s3-5">section
7-3-5</a>) is in use, this field is set to the previous contents
of the <tt>email</tt> field when the owner changes the E-mail
address, and is cleared when the new address is authenticated; this
allows the <tt>RESTOREMAIL</tt> command to function.</dd>
<dt><tt>char *<b>info</b></tt></dt>
<dd>A free-form text string associated with the nickname group. Can be
arbitrarily set by the owner.</dd>
<dt><tt>int32 <b>flags</b></tt></dt>
<dd>A bitmask containing zero or more of the following nickname group
flags:
<ul>
<li><b><tt>NF_KILLPROTECT</tt>:</b> NickServ should prevent other
clients from using the nickname by either killing them or
changing their nicknames, depending on the IRC protocol in
use (<tt>SET KILL ON/QUICK/IMMED</tt>).</li>
<li><b><tt>NF_SECURE</tt>:</b> NickServ should require password
identification for the nickname even if the client is on
the nickname's access list (<tt>SET SECURE</tt>).</li>
<li><b><tt>NF_MEMO_HARDMAX</tt>:</b> The client is not permitted to
change the nickname's memo limit (MemoServ <tt>SET LIMIT
HARD</tt>).</li>
<li><b><tt>NF_MEMO_SIGNON</tt>:</b> MemoServ should inform the
client of new memos at signon (MemoServ <tt>SET NOTIFY
ON/LOGON</tt>).</li>
<li><b><tt>NF_MEMO_RECEIVE</tt>:</b> MemoServ should inform the
client of new memos when they are sent (MemoServ <tt>SET
NOTIFY ON/NEW</tt>).</li>
<li><b><tt>NF_PRIVATE</tt>:</b> The nickname is hidden from the
<tt>LIST</tt> and <tt>LISTEMAIL</tt> command output, except
when used by Services administrators (<tt>SET
PRIVATE</tt>).</li>
<li><b><tt>NF_HIDE_EMAIL</tt>:</b> The nickname's E-mail address is
hidden from the <tt>INFO</tt> and <tt>LISTEMAIL</tt>
command output, except when used by Services administrators
(<tt>SET HIDE EMAIL</tt>).</li>
<li><b><tt>NF_HIDE_MASK</tt>:</b> The nickname's
<tt><i>user</i>@<i>host</i></tt> mask is hidden from the
<tt>INFO</tt> and <tt>LIST</tt> command output, except when
used by Services administrators (<tt>SET HIDE
USERMASK</tt>).</li>
<li><b><tt>NF_HIDE_QUIT</tt>:</b> The nickname's last quit message
is hidden from the <tt>INFO</tt> command output, except
when used by Services administrators (<tt>SET HIDE
QUIT</tt>).</li>
<li><b><tt>NF_KILL_QUICK</tt>:</b> NickServ should allow only 20
seconds instead of 60 for an unauthorized client to change
nickname (<tt>SET KILL QUICK/IMMED</tt>).</li>
<li><b><tt>NF_KILL_IMMED</tt>:</b> NickServ should kill or
nickchange unauthorized clients immediately with no grace
period (<tt>SET KILL IMMED</tt>).</li>
<li><b><tt>NF_MEMO_FWD</tt>:</b> MemoServ should forward received
memos to the nickname's E-mail address (MemoServ <tt>SET
FORWARD ON/COPY</tt>).</li>
<li><b><tt>NF_MEMO_FWDCOPY</tt>:</b> MemoServ should save copies of
forwarded memos (MemoServ <tt>SET FORWARD COPY</tt>).</li>
<li><b><tt>NF_SUSPENDED</tt>:</b> The nickname group is suspended
(<tt>SUSPEND</tt>).</li>
<li><b><tt>NF_NOOP</tt>:</b> ChanServ should prevent the nickname
from being added to channel access lists (<tt>SET
NOOP</tt>).</li>
</ul>
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
(<tt>NF_NOGROUP</tt>) when loading databases from earlier versions
of Services, to indicate that a nickname group does not yet have an
ID value assigned.</dd>
<dt><tt>int16 <b>os_priv</b></tt></dt>
<dd>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:
<ul>
<li><b><tt>NP_SERVOPER</tt>:</b> Services operator privilege.</li>
<li><b><tt>NP_SERVADMIN</tt>:</b> Services administrator
privilege.</li>
</ul>
Other values are treated by the OperServ privilege checking code
(see <a href="#s2-1">section 7-2-1</a>) as having the next lowest
recognized value.</dd>
<dt><tt>int32 <b>authcode</b></tt></dt>
<dd>The authentication code set for the nickname group, or zero if no
code is set. See <a href="#s3-5">section 7-3-5</a>.</dd>
<dt><tt>time_t <b>authset</b></tt></dt>
<dd>The timestamp when the nickname group's authentication code was
set (meaningless if no code is set).</dd>
<dt><tt>int16 <b>authreason</b></tt></dt>
<dd>The reason the nickname group's current authentication code was
set (meaningless if no code is set). One of the following
constants:
<ul>
<li><b><tt>NICKAUTH_REGISTER</tt>:</b> The nickname was newly
registered, and the E-mail address provided in the
<tt>REGISTER</tt> command requires authentication.</li>
<li><b><tt>NICKAUTH_SET_EMAIL</tt>:</b> The nickname group's
E-mail address was changed with the <tt>SET EMAIL</tt>
command, and the new address requires authentication.</li>
<li><b><tt>NICKAUTH_SETAUTH</tt>:</b> Authentication is required as
the result of a Services administrator using the
<tt>SETAUTH</tt> command.</li>
<li><b><tt>NICKAUTH_REAUTH</tt>:</b> Authentication is required as
the result of the nickname owner using the <tt>REAUTH</tt>
command.</li>
</ul></dd>
<dt><tt>char <b>suspend_who</b>[NICKMAX]</tt></dt>
<dd>The nickname of the client that suspended the nickname group
(meaningless if the <tt>NF_SUSPENDED</tt> flag is not set).</dd>
<dt><tt>char *<b>suspend_reason</b></tt></dt>
<dd>The reason the nickname group was suspended (meaningless if the
<tt>NF_SUSPENDED</tt> flag is not set).</dd>
<dt><tt>time_t <b>suspend_time</b></tt></dt>
<dd>The timestamp when the nickname group was suspended (meaningless if
the <tt>NF_SUSPENDED</tt> flag is not set).</dd>
<dt><tt>time_t <b>suspend_expires</b></tt></dt>
<dd>The timestamp at which the nickname group's suspension expires
(meaningless if the <tt>NF_SUSPENDED</tt> flag is not set).</dd>
<dt><tt>int16 <b>language</b></tt></dt>
<dd>The language preferred for messages sent to the nickname group.
One of the <tt>LANG_*</tt> constants in the Services core's
<tt>language.h</tt>.</dd>
<dt><tt>int16 <b>timezone</b></tt></dt>
<dd>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 <tt>TIMEZONE_DEFAULT</tt> to
use the Services process' default.</dd>
<dt><tt>int16 <b>channelmax</b></tt></dt>
<dd>The maximum number of channels the nickname group is allowed to
register, <tt>CHANMAX_UNLIMITED</tt> for no limit, or
<tt>CHANMAX_DEFAULT</tt> for the default limit (set by the
ChanServ <tt>CSMaxReg</tt> configuration setting).</dd>
<dt><tt>char **<b>access</b></tt>
<br/><tt>int <b>access_count</b></tt></dt>
<dd>The nickname group's access list (see <a href="#s3-2">section
7-3-2</a>).</dd>
<dt><tt>char **<b>ajoin</b></tt>
<br/><tt>int <b>ajoin_count</b></tt></dt>
<dd>The nickname group's auto-join list (see <a href="#s3-3">section
7-3-3</a>).</dd>
<dt><tt>char **<b>ignore</b></tt>
<br/><tt>int <b>ignore_count</b></tt></dt>
<dd>The nickname group's memo ignore list (see <a href="#s5-2">section
7-5-2</a>).</dd>
<dt><tt>MemoInfo <b>memos</b></tt></dt>
<dd>The nickname group's stored memos (see <a href="#s5-1">section
7-5-1</a>).</dd>
<dt><tt>channame_t *<b>channels</b></tt>
<br/><tt>int <b>channels_count</b></tt></dt>
<dd><i>Not saved to persistent storage.</i> The names of the channels
currently registered by this nickname group.</dd>
<dt><tt>User **<b>id_users</b></tt>
<br/><tt>int <b>id_users_count</b></tt></dt>
<dd><i>Not saved to persistent storage.</i> Pointers to <tt>User</tt>
structures for clients which have identified for this nickname
group.</dd>
<dt><tt>time_t <b>last_sendauth</b></tt></dt>
<dd><i>Not saved to persistent storage.</i> The timestamp when the
<tt>SENDAUTH</tt> command was last used for this nickname group
(see <a href="#s3-5">section 7-3-5</a>).</dd>
<dt><tt>int <b>bad_auths</b></tt></dt>
<dd><i>Not saved to persistent storage.</i> The number of times the
<tt>AUTH</tt> command has been used with a bad authentication code
for this nickname group (see <a href="#s3-5">section 7-3-5</a>).</dd>
</dl>
<p>In addition, <tt>nickserv.h</tt> declares the following convenience
functions and macros:</p>
<dl>
<dt><tt>int <b>nick_recognized</b>(const NickInfo *<i>ni</i>)</tt>
<br/><tt>int <b>user_recognized</b>(const User *<i>u</i>)</tt></dt>
<dd>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
<tt>NickInfo</tt> or <tt>User</tt> structure. (Nickname
authorization flags are always cleared when a client disconnects
from the network, so the <tt>NickInfo</tt> form will always return
false if used on a nickname not currently in use.)</dd>
<dt><tt>int <b>nick_identified</b>(const NickInfo *<i>ni</i>)</tt>
<br/><tt>int <b>user_identified</b>(const User *<i>u</i>)</tt></dt>
<dd>Returns whether the given client has identified for its nickname.
The client can be specified by either <tt>NickInfo</tt> or
<tt>User</tt> structure.</dd>
<dt><tt>int <b>nick_id_or_rec</b>(const NickInfo *<i>ni</i>)</tt>
<br/><tt>int <b>user_id_or_rec</b>(const User *<i>u</i>)</tt></dt>
<dd>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 <tt>NickInfo</tt> or <tt>User</tt> structure.</dd>
<dt><tt>int <b>nick_ident_nomail</b>(const NickInfo *<i>ni</i>)</tt>
<br/><tt>int <b>user_ident_nomail</b>(const User *<i>u</i>)</tt></dt>
<dd>Returns whether the <tt>NA_IDENT_NOMAIL</tt> flag is set for the
given client; <i>i.e.,</i> 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 <tt>NickInfo</tt> or
<tt>User</tt> structure.</dd>
<dt><tt>int <b>ngi_unauthed</b>(const NickGroupInfo *<i>ngi</i>)</tt></dt>
<dd>Returns whether the given nickname group (more accurately, the
nickname group's E-mail address) is unauthenticated. Note that use
of the <tt>REAUTH</tt> command does not cause the nickname group to
lose its authenticated status.</dd>
<dt><tt>int <b>valid_ngi</b>(const NickGroupInfo *<i>ngi</i>)</tt></dt>
<dd>Returns whether the given <tt>NickGroupInfo</tt> structure pointer
is valid; <i>i.e.,</i> evaluates to true when <tt><i>ngi</i></tt>
is neither <tt>NULL</tt> nor <tt>NICKGROUPINFO_INVALID</tt>. (The
latter value is used in <tt>User</tt> structures to indicate that
the client has an associated <tt>NickInfo</tt> structure but no
<tt>NickGroupInfo</tt> structure, as is the case for forbidden
nicknames.)</dd>
<dt><tt>const char *<b>ngi_mainnick</b>(const NickGroupInfo *<i>ngi</i>)</tt></dt>
<dd>Returns the given nickname group's main nickname.</dd>
<dt><tt>NickGroupInfo *<b>get_ngi</b>(const NickInfo *<i>ni</i>)</tt>
<br/><tt>NickGroupInfo *<b>get_ngi_id</b>(uint32 <i>id</i>)</tt></dt>
<dd>Retrieves the <tt>NickGroupInfo</tt> record corresponding to the
given <tt>NickInfo</tt> or ID value; if there is no corresponding
record, a warning message is logged and <tt>NULL</tt> is returned.
(Like any other database "get" routine, the structure must be "put"
with <tt>put_nickgroupinfo()</tt> when no longer needed.)</dd>
<dt><tt>int <b>check_ngi</b>(const NickInfo *<i>ni</i>)</tt>
<br/><tt>int <b>check_ngi_id</b>(uint32 <i>id</i>)</tt></dt>
<dd>Returns whether there is a corresponding <tt>NickGroupInfo</tt>
record for the given <tt>NickInfo</tt> or ID value, logging a
warning message if not. (The
<tt>put_<i>xxx</i>(get_<i>xxx</i>(...))</tt> is a common way of
checking for the existence of a record, since the
<tt>put_<i>xxx</i>()</tt> explicitly allow a <tt>NULL</tt>
parameter.)</dd>
</dl>
<p>The <tt>STANDALONE_NICKSERV</tt> define and (non-macro) utility
functions are discussed in <a href="#s3-1-4">section 7-3-1-4</a>.</p>
<p class="backlink"><a href="#top">Back to top</a></p>
<h5 class="subsubsubsection-title" id="s3-1-2">7-3-1-2. Overall module structure</h5>
<p>The overall structure of the NickServ module generally follows the same
pattern as the OperServ module: variable and command declarations, database
handling, <tt>PRIVMSG</tt> and other callbacks, and command routines.</p>
<p>The <tt>nickserv/main</tt> module uses two separate databases, one for
<tt>NickInfo</tt> records and one for <tt>NickGroupInfo</tt> records. The
database handling code is more or less straightforward, using <tt>hash.h</tt>
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 <tt>add_nickinfo_()</tt>, and the
actual functions (like <tt>add_nickinfo()</tt>) are wrapped around these.
NickServ exports all of the <tt>NickInfo</tt> and <tt>NickGroupInfo</tt>
database functions, as well as a "put" function for each record type.</p>
<p>When saving the databases to persistent storage, the <tt>nicks[]</tt>
array and <tt>mainnick</tt> field of <tt>NickGroupInfo</tt> records are not
saved directly; rather, the main nickname itself is saved as a
<tt>NICKMAX</tt>-sized buffer, and the nickname group is initialized with
this nickname when first loaded (the array is subsequently filled in when
the relevant <tt>NickInfo</tt> records are loaded).</p>
<p>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, <tt>validate_user()</tt> (for a
client starting to use a nickname) and <tt>cancel_user()</tt> (for a client
no longer using a nickname), are located in <tt>util.c</tt>, discussed in
<a href="#s3-1-4">section 7-3-1-4</a>.</p>
<p>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:</p>
<dl>
<dt><b><tt>do_help()</tt></b></dt>
<dd>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
<tt>help_cmd()</tt> routine.</dd>
<dt><b><tt>do_register()</tt></b></dt>
<dd>Just as NickServ is the gateway to the rest of Services' functions,
the <tt>REGISTER</tt> 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 <tt>REGISTER</tt> handler must therefore be
particularly careful to guard against abuse, both to prevent
improper access to other Services commands and to prevent the
<tt>REGISTER</tt> command itself from being abused by arbitrary
clients. The command handler takes the following precautions
before allowing a nickname to be registered:
<ul>
<li class="spaced">Prevents the command from being used by any
particular client more than once every <tt>NSRegDelay</tt>
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 <a href="#s3-5">section
7-3-5</a>).</li>
<li class="spaced">Prevents the command from being used by a client
within <tt>NSInitialRegDelay</tt> seconds of connecting to
the network. This prevents automated clients from getting
around the <tt>NSRegDelay</tt> limitation by repeatedly
connecting, issuing a <tt>REGISTER</tt> 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.)</li>
<li class="spaced">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
<tt>do_reglink_check()</tt> earlier in <tt>main.c</tt>,
a callback function attached to the "<tt>REGISTER/LINK
check</tt>" callback; <tt>do_register()</tt> calls this
callback via the <tt>reglink_check()</tt> function in
<tt>util.c</tt>.)</li>
<li class="spaced">Ensures that the nickname is not already
registered. Ordinarily, if a nickname is registered then
the client's <tt>User</tt> structure will have a pointer to
the record in its <tt>ni</tt> field, but if the nickname is
missing a corresponding nickname group (a database error
unless the nickname is forbidden), the <tt>ni</tt> field
will be <tt>NULL</tt> and the <tt>ngi</tt> field will be
set to the constant <tt>NICKGROUPINFO_INVALID</tt>, so that
combination is checked for as well. Also, just in case,
<tt>do_register()</tt> performs a final check by accessing
the database directly, to ensure that the nickname is not
registered in duplicate.</li>
<li class="spaced">Checks that the E-mail address, if given, is (1)
syntactically valid and (2) not disallowed due to a
<tt>RejectEmail</tt> configuration directive.</li>
<li class="spaced">Prevents more than <tt>NSRegEmailMax</tt>
nicknames from being registered to the same address, again
to avoid undue load on Services from a registration flood.
This check calls <tt>count_nicks_with_email()</tt> 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.</li>
<li class="spaced">As an adjunct to the previous check (and
regardless of the setting of <tt>NSRegEmailMax</tt>), 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.</li>
</ul></dd>
<dt><b><tt>do_dropemail()</tt></b>
<br/><b><tt>do_dropemail_confirm()</tt></b></dt>
<dd>The <tt>DROPEMAIL</tt> and <tt>DROPEMAIL-CONFIRM</tt> 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 <tt>DROPEMAIL</tt> 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
(<tt>DROPEMAIL-CONFIRM</tt>) serves to confirm the action requested
by the first (<tt>DROPEMAIL</tt>). In order for this to be
effective, the <tt>DROPEMAIL-CONFIRM</tt> handler must know which
commands have been sent by whom, so that clients cannot send
arbitrary <tt>DROPEMAIL-CONFIRM</tt> commands to get around the
confirmation check. This is accomplished through the file-local
<tt>dropemail_buffer[]</tt> array, which holds the most recently
issued, unconfirmed <tt>DROPEMAIL</tt> commands. (A single state
record stored in the <tt>User</tt> 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 <tt>DROPEMAIL</tt>
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
<tt>DROPEMAIL-CONFIRM</tt> 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 <tt>NSDropEmailExpire</tt> option), and
performs the actual nickname deletion.</dd>
<dt><tt><b>do_info()</b></tt></dt>
<dd>The <tt>INFO</tt> command has the ability to show extended
information about a nickname with the option <tt>ALL</tt> (only
available to the nickname owner or Services administrators).
However, not all nicknames have any additional information to be
displayed. To prevent the "use <tt>ALL</tt> for more information"
message from being appended if there is not actually anything else
to show, the <tt>INFO</tt> command handler uses a method inspired
by super-user privilege checks in the Linux kernel, which keeps
track of whether a process has taken advantage of those privileges.
When the macro <tt>CHECK_SHOW_ALL</tt> is included in a conditional
test, it will evaluate to true when the <tt>ALL</tt> option is
present (and the client has permission to use it), but a separate
flag variable, <tt>used_all</tt>, will also be set regardless of
the presence of <tt>ALL</tt>; the routine can then determine
whether there were any items that would have been displayed if
<tt>ALL</tt> 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 <tt>used_all</tt> from being set for an item
that will not actually be displayed due to a subsequent test.</dd>
<dt><tt><b>do_set()</b></tt>
<br/><tt><b>do_unset()</b></tt></dt>
<dd>As the <tt>SET</tt> and <tt>UNSET</tt> commands (<tt>SET</tt> in
particular) have a large number of options, they are defined in a
separate source file, <tt>set.c</tt>. See
<a href="#s3-1-3">section 7-3-1-3</a> for details.</dd>
</dl>
<p>NickServ also includes a debug command enabled by the
<tt>DEBUG_COMMANDS</tt> preprocessor symbol: <tt>LISTNICK</tt>, which
displays the <tt>NickInfo</tt> and (if present) <tt>NickGroupInfo</tt> data
for a given nickname.</p>
<p>In addition to the ordinary module setup code, the
<tt>nickserv/main</tt> module supports two command-line options. One,
<tt>-encrypt-all</tt>, is recognized by the Services core; NickServ's
<tt>init_module()</tt> routine checks the corresponding global flag,
<tt>encrypt_all</tt> and, if it is set, encrypts all nicknames using the
encryption type specified by the core's <tt>EncryptionType</tt> setting.
The other option, <tt>-clear-nick-email</tt>, is NickServ-specific, and is
handled by <tt>do_command_line()</tt>, a callback function for the core's
"<tt>command line</tt>" callback; when the option is encountered, the
callback function clears the E-mail address from all nickname groups.</p>
<p>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,
<tt>do_reconfigure()</tt>) call <tt>mapstring()</tt> to adjust the messages
appropriately. The commands <tt>REGISTER</tt>,
<tt>DROPEMAIL</tt>/<tt>DROPEMAIL-CONFIRM</tt>, and <tt>GETPASS</tt> can
also be disabled by configuration options; the commands are disabled by
setting the <tt>name</tt> field of the corresponding <tt>Command</tt>
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.</p>
<p class="backlink"><a href="#top">Back to top</a></p>
<h5 class="subsubsubsection-title" id="s3-1-3">7-3-1-3. The <tt>SET</tt> and <tt>UNSET</tt> commands</h5>
<p>The handlers for the <tt>SET</tt> and <tt>UNSET</tt> commands,
<tt>do_set()</tt> and <tt>do_unset()</tt> in <tt>set.c</tt>, work much like
miniature versions of the top-level NickServ message handler
<tt>nickserv()</tt>, in that they check which option name was used with the
command and call an appropriate subroutine to do the actual work. However,
the <tt>do_set()</tt> routine parses the option parameters itself, rather
than leave such parsing to the individual routines (<tt>UNSET</tt> does not
take any additional parameters, so no such parsing is needed); for this
reason, the setup code in <tt>do_set()</tt> is more complex than that in
<tt>nickserv()</tt>, since the <tt>INFO</tt> option treats the entire line
as a single parameter, <tt>HIDE</tt> takes two single-word parameters
separated by a space, and the other options take a single one-word
parameter.</p>
<p>Additionally, both <tt>SET</tt> and <tt>UNSET</tt> can be used by
Services administrators to set options for other users' nicknames. For
this reason, the individual option-setting routines take both a
<tt>User&nbsp;*</tt> and a <tt>NickInfo&nbsp;*</tt> parameter, where the
<tt>NickInfo&nbsp;*</tt> 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
<tt>ni</tt> field of the <tt>User</tt> structure). <i>Implementation note:
This raises an interesting problem&mdash;how does an option's handler
routine tell the difference between <tt>SET <i>option</i></tt>, <tt>SET
!MyNick <i>option</i></tt>, and <tt>SET !OtherNick <i>option</i></tt> when
sending result messages? The simple answer is that it doesn't: all option
handlers use the "your nick" message style, as mentioned in
<a href="../d.html">Appendix D of the user's manual</a>. If implemented,
it would probably be reasonable to ignore the distinction between the
<tt>SET <i>option</i></tt> and <tt>SET !MyNick <i>option</i></tt> cases,
and simply judge which message to use by comparing the <tt>NickInfo</tt>
parameter with the <tt>ni</tt> field of the client's <tt>User</tt>
structure.</i></p>
<p>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 <tt>NickInfo</tt> structure or its associated <tt>NickGroupInfo</tt>
structure. Routines which deserve special mention are:</p>
<dl>
<dt><b><tt>do_set_password()</tt></b></dt>
<dd>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
<tt>NSSecureAdmins</tt> option, and disallows the change if the
target is a (different) Services administrator and the command
sender is not the Services super-user.</dd>
<dt><b><tt>do_set_email()</tt></b></dt>
<dd>This routine makes several checks mostly related to mail
authentication before allowing the E-mail address to be changed:
<ul>
<li>The address must be a valid E-mail address.</li>
<li>The address must not be rejected by a <tt>RejectEmail</tt>
configuration directive.</li>
<li>The address must not be in use by a nickname awaiting mail
authentication (as with <tt>REGISTER</tt>).</li>
<li>The number of nicknames currently using the address must be
less than <tt>NSRegEmailMax</tt>, 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 <tt>NSRegEmailMax</tt>. 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.)</li>
<li>The time since the last successful <tt>SET EMAIL</tt> must be
at least <tt>NSSetEmailDelay</tt> seconds, if set.</li>
</ul>
If the above checks all pass, the change is performed, and if the
client used to have the <tt>NA_IDENT_NOMAIL</tt> status and an
E-mail address was set, the status is changed to
<tt>NA_IDENTIFIED</tt>. The routine also features its own
callback, "<tt>SET EMAIL</tt>", used by the mail authentication
code. Note that this routine does <i>not</i> check the
<tt>NSRequireEmail</tt> configuration option, and assumes that if
it is passed a <tt>NULL</tt> value, indicating that the address
should be unset, then that is valid. (In fact, <tt>do_unset()</tt>
checks <tt>NSRequireEmail</tt> before calling
<tt>do_set_email()</tt>.)</dd>
<dt><b><tt>do_set_timezone()</tt></b></dt>
<dd><tt>SET TIMEZONE</tt> 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+/-<i>n</i>" and
"UTC+/-<i>n</i>", which are treated as literal offsets) are parsed
using the <tt>timezones[]</tt> table defined just above the
<tt>do_set_timezone()</tt> 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
<tt>strftime_lang()</tt> 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 <tt>strftime_lang()</tt> by that amount
(multiplied by 60, since the <tt>timezone</tt> 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).</dd>
</dl>
<p class="backlink"><a href="#top">Back to top</a></p>
<h5 class="subsubsubsection-title" id="s3-1-4">7-3-1-4. NickServ utility routines</h5>
<p>Most of the utility routines used by NickServ are collected in the file
<tt>util.c</tt>. 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 <tt>#include</tt>'d in an external source
file to provide definitions of the four routines <tt>new_nickinfo()</tt>,
<tt>free_nickinfo()</tt>, <tt>new_nickgroupinfo()</tt>, and
<tt>free_nickgroupinfo()</tt>, so that such files do not have to define
similar routines themselves. This latter mode is activated by defining
the <tt>STANDALONE_NICKSERV</tt> preprocessor symbol, as documented in the
comments at the top of <tt>util.c</tt>. In this case, only the four
routines mentioned above are defined, with the rest of the file commented
out using <tt>#ifndef</tt>; additionally, the <tt>new_nickgroupinfo()</tt>
routine does not check for the presence of the nickname group IDs it
generates, as it cannot assume that <tt>get_nickgroupinfo()</tt> is
available.</p>
<p>With respect to its primary use as part of NickServ, <tt>util.c</tt>
defines the following routines:</p>
<dl>
<dt><tt>NickInfo *<b>new_nickinfo</b>()</tt></dt>
<dd>Returns a pointer to a newly-allocated and initialized
<tt>NickInfo</tt> structure. (For creating a new record in the
database, <tt>makenick()</tt> is preferred; see below.)</dd>
<dt><tt>void <b>free_nickinfo</b>(NickInfo *<i>ni</i>)</tt></dt>
<dd>Frees the given <tt>NickInfo</tt> structure and all associated
data. (See <tt>delnick()</tt> below for removing a nickname
record from the database.)</dd>
<dt><tt>NickGroupInfo *<b>new_nickgroupinfo</b>(const char *<i>seed</i>)</tt></dt>
<dd>Returns a pointer to a newly-allocated and initialized
<tt>NickGroupInfo</tt> structure. If <tt><i>seed</i></tt> is not
<tt>NULL</tt>, 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 <tt>NEWNICKGROUP_TRIES</tt> 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. <tt>NEWNICKGROUP_TRIES</tt> is defined in
<tt>ns-local.h</tt>.) If <tt><i>seed</i></tt> is <tt>NULL</tt>,
then the new nickname group's ID is left at zero.</dd>
<dt><tt>void <b>free_nickgroupinfo</b>(NickGroupInfo *<i>ngi</i>)</tt></dt>
<dd>Frees the given <tt>NickGroupInfo</tt> structure and all associated
data. (See <tt>delgroup()</tt> below for removing a nickname group
and all its nicknames from the database.)</dd>
<dt><tt>NickGroupInfo *<b>_get_ngi</b>(NickInfo *<i>ni</i>,
const char *<i>file</i>, int <i>line</i>)</tt>
<br/><tt>NickGroupInfo *<b>_get_ngi_id</b>(uint32 <i>id</i>,
const char *<i>file</i>, int <i>line</i>)</tt></dt>
<dd>Implement the <tt>get_ngi()</tt> and <tt>get_ngi_id()</tt> macros,
respectively. <tt><i>file</i></tt> and <tt><i>line</i></tt> are
the source file and line from which the function was called, and
are filled in by the corresponding macro with <tt>__FILE__</tt> and
<tt>__LINE__</tt>.</dd>
<dt><tt>int <b>has_identified_nick</b>(const User *<i>u</i>, uint32 <i>group</i>)</tt></dt>
<dd>Returns whether the given client has identified for the nickname
group indicated by <tt><i>group</i></tt>.</dd>
<dt><tt>int <b>reglink_check</b>(User *<i>u</i>, const char *<i>nick</i>,
char *<i>password</i>, char *<i>email</i>)</tt></dt>
<dd>Calls the "<tt>REGISTER/LINK check</tt>" callback and returns its
result. (A utility function is used rather than directly calling
<tt>call_callback_4()</tt> because the <tt>nickserv/link</tt>
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).</dd>
<dt><tt>void <b>update_userinfo</b>(const User *<i>u</i>)</tt></dt>
<dd>Updates the user information for the client's nickname. The
<tt>NickInfo</tt> fields <tt>last_usermask</tt>,
<tt>last_realmask</tt>, and <tt>last_realname</tt> are set from
the corresponding fields of the <tt>User</tt> structure, and the
<tt>last_seen</tt> field is set to the current time.
<tt><i>u</i>-&gt;ni</tt> is assumed to be non-<tt>NULL</tt>.</dd>
<dt><tt>int <b>validate_user</b>(User *<i>u</i>)</tt></dt>
<dd>Sets the <tt>ni</tt> and <tt>ngi</tt> fields of the <tt>User</tt>
structure to point to the <tt>NickInfo</tt> and associated
<tt>NickGroupInfo</tt>, if any, for the client's nickname (if an
error occurs looking up the nickname group, <tt><i>u</i>-&gt;ni</tt>
is set to <tt>NULL</tt> and <tt><i>u</i>-&gt;ngi</tt> is set to
<tt>NICKGROUP_INVALID</tt>); 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 <tt>NA_IDENTIFIED</tt> or <tt>NA_RECOGNIZED</tt>
access, otherwise zero.
<p>This routine, along with the <tt>REGISTER</tt> 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
(<tt>NA_IDENTIFIED</tt>) nickname access; while
<tt>has_identified_nick()</tt>, 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 <tt>set_identified()</tt>) 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
<tt>NoSplitRecovery</tt> configuration option for added security.</p>
<p>If the client is determined not to have identified for the
nickname previously, the routine continues, determining whether to
give <tt>NA_RECOGNIZED</tt> access. "Recognized" status is only
implemented by access lists (see <a href="#s3-2">section 7-3-2</a>),
and if the corresponding module is not loaded, the
<tt>NA_RECOGNIZED</tt> flag will never be set on any nickname,
except when set along with <tt>NA_IDENTIFIED</tt>. Likewise, if a
nickname's <tt>NF_SECURE</tt> flag is set, then
<tt>NA_RECOGNIZED</tt> will not be set (and zero will be returned
from the routine) even if the client is in fact recognized.</p>
<p>If the client is not identified or recognized for the nickname,
<tt>validate_user()</tt> checks whether the client should be
killed or nick-changed, setting an appropriate timeout or calling
the collide routines (see <a href="#s3-1-5">section 7-3-1-5</a>
below) depending on the nickname group settings. However, if the
client was recognized (which will only be true if the nickname has
the <tt>SECURE</tt> option set and thus <tt>NA_RECOGNIZED</tt> was
not set), the kill checks are not performed, allowing the client to
identify at its leisure.</p>
<p>Finally, the routine checks the nickname's expiration time, and
if it is due to expire "soon" (as defined by the
<tt>NSExpireWarning</tt> configuration option), a warning notice is
sent to the client.</p></dd>
<dt><tt>void <b>cancel_user</b>(User *<i>u</i>)</tt></dt>
<dd>Updates a client's nickname data when the client stops using the
nickname. The <tt>last_seen</tt> field is updated if the client
was either identified or recognized; the <tt>authstat</tt> field
is cleared along with temporary status flags in the <tt>status</tt>
field; an enforcer is introduced if the client was killed or
nick-changed (see <a href="#s3-1-5">section 7-3-1-5</a>); the
<tt>cancel user</tt>" callback is called; and any active nick
collide timeouts are removed. The <tt>ni</tt> and <tt>ngi</tt>
fields of the client's <tt>User</tt> structure are also reset to
<tt>NULL</tt>.</dd>
<dt><tt>void <b>set_identified</b>(User *<i>u</i>)</tt></dt>
<dd>Marks the given client as having identified for the nickname it is
currently using. In addition to setting the authentication status
to <tt>NA_IDENTIFIED&nbsp;| NA_RECOGNIZED</tt> and updating the
nickname's <tt>id_stamp</tt> field, the routine adds the nickname
group ID to the list of nickname groups for which the client has
identified, stored in the <tt>User</tt> structure and checked by
<tt>has_identified_nick()</tt>.</dd>
<dt><tt>NickInfo *<b>makenick</b>(const char *<i>nick</i>,
NickGroupInfo **<i>nickgroup_ret</i>)</tt></dt>
<dd>Creates a new <tt>NickInfo</tt> record with the given nickname,
adds it to the database, and returns a pointer to the new record.
If <tt><i>nickgroup_ret</i></tt> is not <tt>NULL</tt>, then a new
<tt>NickGroupInfo</tt> record is also created for the nickname,
the nickname's <tt>nickgroup</tt> field is set accordingly, and a
pointer to the <tt>NickGroupInfo</tt> record (which is also added
to the database) is stored in the variable pointed to by
<tt><i>nickgroup_ret</i></tt>. Returns <tt>NULL</tt> on error.</dd>
<dt><tt>int <b>delnick</b>(NickInfo *<i>ni</i>)</tt></dt>
<dd>Removes the given nickname from the database and frees all
resources used by the <tt>NickInfo</tt> 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.</dd>
<dt><tt>int <b>delgroup</b>(NickGroupInfo *<i>ngi</i>)</tt></dt>
<dd>Removes the given nickname group from the database, along with all
associated nicknames. Returns nonzero on success, zero on error.</dd>
<dt><tt>int <b>drop_nickgroup</b>(NickGroupInfo *<i>ngi</i>,
const User *<i>u</i>, const char *<i>dropemail</i>)</tt></dt>
<dd>Removes the given nickname group from the database, like
<tt>delgroup()</tt>, but first log information about the nicknames
to be deleted. <tt><i>u</i></tt> is the <tt>User</tt> structure
for the client that sent the command resulting in the deletion.
<tt><i>dropemail</i></tt> should be:
<ul>
<li><tt>NULL</tt> if the call is because of a <tt>DROP</tt> command
from the nickname owner;</li>
<li><tt>PTR_INVALID</tt> if the call is because of a <tt>DROPNICK</tt>
command from a Services administrator;</li>
<li>for a <tt>DROPEMAIL</tt> command, the parameter (address
wildcard) used with the command.</li>
</ul></dd>
<dt><tt>void <b>suspend_nick</b>(NickGroupInfo *<i>ngi</i>,
const char *<i>reason</i>, const char *<i>who</i>,
const time_t <i>expires</i>)</tt></dt>
<dd>Suspends the given nickname group, copying the parameters
<tt><i>reason</i></tt>, <tt><i>who</i></tt>, and
<tt><i>expires</i></tt> into the suspension data fields. (If
<tt><i>expires</i></tt> is zero, then the suspension will not
expire.)</dd>
<dt><tt>void <b>unsuspend_nick</b>(NickGroupInfo *<i>ngi</i>, int <i>set_time</i>)</tt></dt>
<dd>Cancels the suspension on the given nickname group. If
<tt><i>set_time</i></tt> is nonzero, the last-seen time of each
nickname in the group will be updated according to
<tt>NSSuspendGrace</tt> to prevent the nickname from expiring for
that length of time (if <tt>NSSuspendGrace</tt> or <tt>NSExpire</tt>
are not set, or if the nickname already has enough time before
expiration, the last-seen time will not be changed).</dd>
<dt><tt>int <b>nick_check_password</b>(User *<i>u</i>, NickInfo *<i>ni</i>,
const char *<i>password</i>, const char *<i>command</i>,
int <i>failure_msg</i>)</tt></dt>
<dd>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 <tt>WALLOPS</tt>
will also be sent for repeated bad password attempts on the same
nickname. <tt><i>u</i></tt> is the <tt>User</tt> structure for
the client that issued the command; <tt><i>ni</i></tt> is the
<tt>NickInfo</tt> structure for the nickname whose password is
being checked; <tt><i>password</i></tt> is the password given by
the client, <tt><i>command</i></tt> is the name of the command
being executed; and <tt><i>failure_msg</i></tt> is the index of the
message (language string) to be sent if an error occurs when
checking the password.</dd>
<dt><tt>int <b>count_nicks_with_email</b>(const char *<i>email</i>)</tt></dt>
<dd>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.</dd>
</dl>
<p><tt>util.c</tt> also defines initialization and cleanup routines,
<tt>init_util()</tt> and <tt>exit_util()</tt>, which take care of
registering and unregistering the callbacks used by various utility
functions. The routines are called as part of the <tt>nickserv/main</tt>
module iniitialization and cleanup.</p>
<p class="backlink"><a href="#top">Back to top</a></p>
<h5 class="subsubsubsection-title" id="s3-1-5">7-3-1-5. Nickname colliding</h5>
<p>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.</p>
<p>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.</p>
<p>Nickname colliding functionality is provided by the file
<tt>collide.c</tt>. This file provides two methods of colliding nicknames:
directly, or via timeouts. The nickname colliding code also has its own
initialization and cleanup functions (<tt>init_collide()</tt> and
<tt>exit_collide()</tt>), which are called from the <tt>nickserv/main</tt>
module initialization and cleanup routines.</p>
<p>The following routines are used when colliding nicknames directly:</p>
<dl>
<dt><tt>void <b>collide_nick</b>(NickInfo *<i>ni</i>, int <i>from_timeout</i>)</tt></dt>
<dd>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.
<tt><i>from_timeout</i></tt> is used internally with collide
timeouts, and should always be zero when called externally. This
routine automatically calls <tt>introduce_enforcer()</tt> after
the client has been killed or nick-changed (in the case of a
forced nickname change, the enforcer is introduced by
<tt>cancel_user()</tt> upon receipt of the <tt>NICK</tt> 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.</dd>
<dt><tt>void <b>introduce_enforcer</b>(NickInfo *<i>ni</i>)</tt></dt>
<dd>Introduces an enforcer pseudoclient for the given nickname, to
prevent other clients from using the nickname. This routine
automatically adds a timeout to call <tt>release_nick()</tt> after
<tt>NSReleaseTimeout</tt> seconds have passed.</dd>
<dt><tt>void <b>release_nick</b>(NickInfo *<i>ni</i>, int <i>from_timeout</i>)</tt></dt>
<dd>Removes the enforcer pseudoclient for the given nickname, allowing
other clients to use it again. As with <tt>collide_nick()</tt>,
<tt><i>from_timeout</i></tt> 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.</dd>
</dl>
<p>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, <tt>collide.c</tt> provides two wrapper routines around the
generic timeout functions:</p>
<dl>
<dt><tt>void <b>add_ns_timeout</b>(NickInfo *<i>ni</i>, int <i>type</i>,
time_t <i>delay</i>)</tt></dt>
<dd>Adds a timeout of the given type on the given nickname to occur in
<tt><i>delay</i></tt> seconds. <tt><i>type</i></tt> can be any of
the following:
<ul>
<li><b><tt>TO_COLLIDE</tt>:</b> A timeout for colliding a nickname
(<tt>collide_nick()</tt> will be called).</li>
<li><b><tt>TO_RELEASE</tt>:</b> A timeout for releasing the hold on
a nickname (<tt>release_nick()</tt> will be called).</li>
<li><b><tt>TO_SEND_433</tt>:</b> A timeout for sending the client
using the nickname a "433" error message. (433 is the code
for the <tt>ERR_NICKCOLLISION</tt> reply to a <tt>NICK</tt>
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.)</li>
</ul></dd>
<dt><tt>void <b>rem_ns_timeout</b>(NickInfo *<i>ni</i>, int <i>type</i>,
int <i>del_to</i>)</tt></dt>
<dd>Removes any timeout of the given type on the given nickname;
<tt><i>ni</i></tt> can be <tt>NULL</tt> to cause timeouts on all
nicknames (of the given type) to be removed. <tt><i>type</i></tt>
can be any of the type values used with <tt>add_ns_timeout()</tt>,
or -1 to remove timeouts of all types. <tt><i>del_to</i></tt>
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 <tt>Timeout</tt> structure as well).</dd>
</dl>
<p>Each of the three timeout types has its own timeout function:
<tt>timeout_collide()</tt>, <tt>timeout_release()</tt>, and
<tt>timeout_send_433()</tt>. The timeout functions check for any change
in status (such as identification for the nickname) before performing
their respective functions.</p>
<p class="backlink"><a href="#top">Back to top</a></p>
<h4 class="subsubsection-title" id="s3-2">7-3-2. Nickname access lists</h4>
<p>Nickname access lists are managed by the <tt>nickserv/access</tt>
module, defined in <tt>access.c</tt>. The module is quite simple,
consisting of a database table, two callback functions, and a NickServ
command (<tt>ACCESS</tt>). For simplicity, the module (along with other
NickServ submodules) assumes the presence of the <tt>nickserv/main</tt>
module rather than explicitly importing every required NickServ symbol,
although the module's initialization does look up the
<tt>nickserv/main</tt> module handle for use in adding the requisite
callbacks and the <tt>ACCESS</tt> command.</p>
<p>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 <tt>NickGroupInfo</tt> 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
<tt>{<i>nickgroup-ID</i>,<i>access-mask</i>}</tt> record format, and
providing a single record buffer. When loading data from persistent
storage, the table's <tt>newrec()</tt> function returns a pointer to this
buffer, taking advantage of the fact that only one record is loaded at a
time (see <a href="6.html#s2-1">section 6-2-1</a>); the <tt>insert()</tt>
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
<tt>freerec()</tt> routine frees the access mask string. When saving data,
the <tt>first()</tt> and <tt>next()</tt> routines call
<tt>first_nickgroupinfo()</tt> and <tt>next_nickgroupinfo()</tt> in turn,
looping through each access mask of each nickname group.</p>
<p>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 <tt>SELECT</tt>
statement in SQL), and since problems would ensue when trying to save the
data using the (deprecated, but still available) <tt>database/version4</tt>
module, the in-memory data structures were left as is. See the comments in
the <tt>autojoin.c</tt> source file for a more complete description.</p>
<p>Other than this, there is little of note in the module; the
<tt>do_access()</tt> 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 <tt>validate_user()</tt>.</p>
<p class="backlink"><a href="#top">Back to top</a></p>
<h4 class="subsubsection-title" id="s3-3">7-3-3. Nickname auto-join lists</h4>
<p>Nickname auto-join lists are managed by the <tt>nickserv/autojoin</tt>
module, defined in <tt>autojoin.c</tt>. Aside from the details of its
operation, this module is nearly identical to the <tt>nickserv/access</tt>
module, including the hack used for database loading and saving; see the
discussion of that module above (<a href="#s3-2">section 7-3-2</a>) for
details.</p>
<p class="backlink"><a href="#top">Back to top</a></p>
<h4 class="subsubsection-title" id="s3-4">7-3-4. Linking and nickname groups</h4>
<p>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 (<tt>NickInfo</tt> or <tt>NickGroupInfo</tt>);
however, the <tt>nickserv/link</tt> module, defined in <tt>link.c</tt>,
allows nicknames to be linked together by assigning the same nickname group
to both nicknames. This results in all information in the
<tt>NickGroupInfo</tt> structure being shared among all linked nicknames,
with only the data in the <tt>NickInfo</tt> structure kept separately for
each nickname.</p>
<p>The <tt>nickserv/link</tt> includes three commands: <tt>LINK</tt>,
<tt>UNLINK</tt>, and <tt>LISTLINKS</tt>, as well as one additional
<tt>SET</tt> option, <tt>SET MAINNICK</tt> (linked in through NickServ's
"<tt>SET</tt>" callback). Of these, <tt>LISTLINKS</tt> and <tt>SET
MAINNICK</tt> are straightforward: <tt>do_listlinks()</tt> simply echoes
the contents of the <tt>nicks[]</tt> array for the calling client's
nickname group (or the specified nickname's group for Services
operators), and <tt>do_set_mainnick()</tt> modifies the nickname
group's <tt>mainnick</tt> field based on the given nickname.</p>
<p>When the <tt>LINK</tt> command is given to link a new nickname to the
caller's nickname group, <tt>do_link()</tt> 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 <tt>NSRegEmailMax</tt>
limit, if set. (Note that <tt>LINK</tt> does <i>not</i> abort if the mail
address is not authenticated, simply checking the absolute value of the
return from <tt>count_nicks_with_email()</tt> 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 <tt>NickInfo</tt>
structure is created, passing <tt>NULL</tt> as the
<tt><i>nickgroup_ret</i></tt> parameter to <tt>makenick()</tt> to indicate
that a new nickname group is not required; updates the <tt>NickInfo</tt>
structure with the calling user's data; stores the nickname group ID in the
new nickname's <tt>nickgroup</tt> field; and appends the new nickname to
the nickname group's <tt>nicks[]</tt> array.</p>
<p><tt>UNLINK</tt> acts similarly to the Services administrator command
<tt>DROPNICK</tt> for a single nickname in a group. However, since
<tt>UNLINK</tt> 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 <tt>NickGroupInfo</tt> structure and (2)
the nickname group IDs of the two nickname groups are equal, and
disallowing the command otherwise if the <tt>FORCE</tt> option is not
given. (The check is made on the <tt>FORCE</tt> 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 <tt>delnick()</tt>; if the nickname group's main
nickname is the one being unlinked, <tt>delnick()</tt> automatically
adjusts the <tt>mainnick</tt> field to the next nickname in the
<tt>nicks[]</tt> array (or the previous nickname, if the deleted nickname
was the last one in the array).</p>
<p class="backlink"><a href="#top">Back to top</a></p>
<h4 class="subsubsection-title" id="s3-5">7-3-5. E-mail address authentication</h4>
<p>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
<tt>nickserv/mail-auth</tt> module, defined in <tt>mail-auth.c</tt>. 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.</p>
<p>Internally, this processing is managed with the <tt>authcode</tt>,
<tt>authset</tt>, and <tt>authreason</tt> fields of the
<tt>NickGroupInfo</tt> structure. When an event occurs requiring E-mail
address authentication, the module generates a random 9-digit numeric code
(100000000 through 999999999 inclusive&mdash;codes with leading zeroes are
not used in order to avoid confusion), stores the code in the nickname
group's <tt>authcode</tt> field, and calls the mail subsystem's
<tt>sendmail()</tt> routine to send a message to the nickname group's
registered E-mail address (see <a href="8.html#s3">section 8-3</a> for
information about how mail is sent). Additionally, the current timestamp
is stored in the <tt>authset</tt> field, and the reason for setting the
code (one of the <tt>NICKAUTH_*</tt> constants) is stored in the
<tt>authreason</tt> 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 <tt>authcode</tt>
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 <tt>validate_user()</tt>
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
<tt>authcode</tt> field, allowing identification to the nickname(s) once
more.</p>
<p>The module begins with several utility functions:</p>
<dl>
<dt><tt>void <b>make_auth</b>(NickGroupInfo *<i>ngi</i>, int16 <i>reason</i>)</tt></dt>
<dd>Generates a random authentication code, and sets the nickname
group's <tt>authcode</tt>, <tt>authset</tt>, and <tt>authreason</tt>
fields appropriately. The <tt><i>reason</i></tt> parameter is
copied directly into the <tt>authreason</tt> field, without checks
on its value.</dd>
<dt><tt>void <b>clear_auth</b>(NickGroupInfo *<i>ngi</i>)</tt></dt>
<dd>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).</dd>
<dt><tt>int <b>send_auth</b>(User *<i>u</i>, NickGroupInfo *<i>ngi</i>,
const char *<i>nick</i>, int <i>what</i>)</tt></dt>
<dd>Sends an E-mail to the given nickname group's owner containing the
nickname's current authentication code. <tt><i>u</i></tt> is the
<tt>User</tt> structure for the client whose command caused the
message to be sent; <tt><i>nick</i></tt> is the specific nickname
for which the command was issued; and <tt><i>what</i></tt> is
one of the <tt>IS_*</tt> constants defined for the routine
(internally a language string index for the mail body or -1 for the
special case of <tt>SETAUTH</tt>). The routine itself is defined
as <tt>send_auth_</tt>, taking a <tt><i>line</i></tt> parameter
indicating where in the source the routine was called from; this is
filled in automatically by the <tt>send_auth()</tt> macro. In
order to inform the calling client of the success or failure of
sending the message, <tt>send_auth()</tt> creates a
<tt>sendauth_data</tt> structure for each message sent, used in the
two routines listed below.</dd>
<dt><tt>void <b>send_auth_callback</b>(int <i>status</i>, void *<i>data</i>)</tt></dt>
<dd>The callback function used for <tt>sendmail()</tt> when called from
<tt>send_auth()</tt>. This routine uses the <tt>sendauth_data</tt>
structure for the sent message (passed as the <tt><i>data</i></tt>
parameter) to send a reply to the client that issued the original
command, and also to clear the nickname group's
<tt>last_sendauth</tt> field if the command used was
<tt>SENDAUTH</tt> and the message could not be sent. The structure
is then removed from the global list and freed.</dd>
<dt><tt>int <b>sendauth_userdel</b>(User *<i>user</i>,
const char *<i>reason</i>, int <i>is_kill</i>)</tt></dt>
<dd>Used as a callback function for the core's "<tt>user delete</tt>"
callback. Iterates through the <tt>sendauth_data</tt> list,
clearing the <tt>User</tt> pointer of any entries for the user
being removed; this causes <tt>send_auth_callback()</tt> to skip
sending a reply when the mail sending completes.</dd>
</dl>
<p>These routines are followed by the command handlers for the commands
supported by the module: <tt>AUTH</tt>, <tt>SENDAUTH</tt>, <tt>REAUTH</tt>,
<tt>RESTOREMAIL</tt>, and the Services administrator commands
<tt>SETAUTH</tt>, <tt>GETAUTH</tt>, and <tt>CLEARAUTH</tt>. Of these, the
only fairly complex one is the <tt>AUTH</tt> handler, <tt>do_auth()</tt>,
as it must watch for attempts to guess the authentication code. (Invalid
<tt>AUTH</tt> commands are treated the same as bad passwords to
<tt>IDENTIFY</tt> or other commands, by calling <tt>bad_password()</tt> and
incrementing the nickname group's <tt>bad_auths</tt> field, which itself
can generate a warning via <tt>WALLOPS</tt>.)</p>
<p>Finally, the module includes four callback functions. Two of these,
<tt>do_registered()</tt> and <tt>do_set_email()</tt>, hook into the
NickServ callbacks for the <tt>REGISTER</tt> and <tt>SET EMAIL</tt>
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 <tt>IDENTIFY</tt> command callback
function <tt>do_identify()</tt>, which disallows <tt>IDENTIFY</tt> for
nicknames with pending authentication codes, and the expiration check
callback function <tt>do_check_expire()</tt>, which drops a
newly-registered nickname after <tt>NSNoAuthExpire</tt> seconds (if set) if
not authenticated, and also clears any pending <tt>REAUTH</tt> after the
same amount of time (the user can subsequently use a second <tt>REAUTH</tt>
if necessary).</p>
<p>Since the ability to send E-mail is essential for this module to work,
the <tt>init_module()</tt> function checks for the presence of the
<tt>mail/main</tt> module, refusing to load if it is not available.</p>
<p class="backlink"><a href="#top">Back to top</a></p>
<!------------------------------------------------------------------------>
<hr/>
<h3 class="subsection-title" id="s4">7-4. ChanServ</h3>
<p>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.</p>
<p>As with NickServ, the core ChanServ module, <tt>chanserv/main</tt>, is
split up over several source files. These are discussed in
<a href="#s4-1">section 7-4-1</a> and its subsections, with the exception
of the <tt>access.c</tt> source file, which is discussed in
<a href="#s4-2">section 7-4-2</a> along with the access list manipulation
submodules, <tt>chanserv/access-levels</tt> and
<tt>chanserv/access-xop</tt>.</p>
<p class="backlink"><a href="#top">Back to top</a></p>
<h4 class="subsubsection-title" id="s4-1">7-4-1. ChanServ core functionality</h4>
<p>The core ChanServ module, <tt>chanserv/main</tt>, is built from several
source files, along much the same lines as the core NickServ module:
<tt>main.c</tt>, containing the central module code and most command
handlers; <tt>access.c</tt>, for handling channel access lists;
<tt>check.c</tt>, for checking the status of a channel against registered
data and making appropriate changes; <tt>set.c</tt>, implementing the
<tt>SET</tt> command; and <tt>util.c</tt>, defining various ChanServ
utility functions.</p>
<p class="backlink"><a href="#top">Back to top</a></p>
<h5 class="subsubsubsection-title" id="s4-1-1">7-4-1-1. Channel data structures</h5>
<p>As with NickServ, ChanServ splits its declarations into two header
files: <tt>chanserv.h</tt>, containing structures and routine declarations
intended to be exported to other modules, and <tt>cs-local.h</tt>, for
internal use by ChanServ only. (There is also a separate header file,
<tt>access.h</tt>, specifically for channel access list definitions; this
is discussed in <a href="#s4-2-1">section 7-4-2-1</a>.)</p>
<p>The channel data structure, <tt>ChannelInfo</tt>, 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. <tt>ChannelInfo</tt> does, however, include three
substructures, defined before it in <tt>chanserv.h</tt>:</p>
<dl>
<dt><tt>ChanAccess</tt></dt>
<dd>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 <tt>channel</tt> 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 <tt>XOP</tt>
commands.</dd>
<dt><tt>AutoKick</tt></dt>
<dd>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 <tt>NULL</tt> value for the mask
string indicates an unused entry), and the <tt>channel</tt> field
is used when loading and saving data to link the record with its
associated channel.</dd>
<dt><tt>ModeLock</tt></dt>
<dd>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 (<tt>Channel</tt> records) to record the channel's
current mode. The mode sets are normally maintained as bitmasks,
but when used in the <tt>convert-db</tt> 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 <tt>ChanAccess</tt> and
<tt>AutoKick</tt> structures, there is only one <tt>ModeLock</tt>
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.</dd>
</dl>
<p><tt>chanserv.h</tt> also defines constants for each of the channel
privilege levels (indices into the <tt>levels[]</tt> array of the
<tt>ChannelInfo</tt> 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 <tt>database/version4</tt> module, since that
module simply reads in the list of levels as a block. (This is why the
index 18, formerly used for <tt>CA_AUTOOWNER</tt>, is unused&mdash;to avoid
problems with databases in which that index is used.)</p>
<p>The <tt>ChannelInfo</tt> structure itself contains the following
fields:</p>
<dl>
<dt><tt>ChannelInfo *<b>next</b>, *<b>prev</b></tt></dt>
<dd>Used to link records together in the internal hash table.</dd>
<dt><tt>int <b>usecount</b></tt></dt>
<dd>The record's usage count (number of gets minus number of puts).
<i>(Implementation note: As noted in <a href="#s1">section 7-1</a>,
this field currently serves no actual purpose.)</i></dd>
<dt><tt>char <b>name</b>[CHANMAX]</tt></dt>
<dd>The channel's name. Capitalization is as used when the channel was
registered, and does not change due to later actions. The buffer
size, <tt>CHANMAX</tt>, is defined in the global header file
<tt>defs.h</tt>.</dd>
<dt><tt>uint32 <b>founder</b></tt>
<br/><tt>uint32 <b>successor</b></tt></dt>
<dd>The nickname group ID of the channel's founder and successor,
respectively. If the channel does not have a successor set, the
<tt>successor</tt> field will be zero.</dd>
<dt><tt>Password <b>founderpass</b></tt></dt>
<dd>The founder password for the channel.</dd>
<dt><tt>char *<b>desc</b></tt></dt>
<dd>The channel's description, as specified at registration time or
with a later <tt>SET DESC</tt> command.</dd>
<dt><tt>char *<b>url</b></tt></dt>
<dd>The URL associated with the channel, as set with the <tt>SET
URL</tt> command. <tt>NULL</tt> if no URL has been set.</dd>
<dt><tt>char *<b>email</b></tt></dt>
<dd>The E-mail address associated with the channel, as set with the
<tt>SET EMAIL</tt> command. <tt>NULL</tt> if no E-mail address
has been set.</dd>
<dt><tt>char *<b>entry_message</b></tt></dt>
<dd>The channel's entry message (the message sent as a <tt>NOTICE</tt>
to clients entering the channel), as set with the <tt>SET
ENTRYMSG</tt> command. <tt>NULL</tt> if no entry message has been
set.</dd>
<dt><tt>time_t <b>time_registered</b></tt></dt>
<dd>The timestamp at which the channel was registered.</dd>
<dt><tt>time_t <b>last_used</b></tt></dt>
<dd>The timestamp at which the channel was last used (see
<a href="../3.html#2-3">section 3-2-3 of the user's manual</a> for
details of how the last-used time is set).</dd>
<dt><tt>char *<b>last_topic</b></tt>
<br/><tt>char <b>last_topic_setter</b>[NICKMAX]</tt>
<br/><tt>time_t <b>last_topic_time</b></tt></dt>
<dd>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, <tt>last_topic</tt>
will be <tt>NULL</tt>, and the other two fields will be
undefined.</dd>
<dt><tt>int32 <b>flags</b></tt></dt>
<dd>A bitmask containing zero or more of the following channel flags:
<ul>
<li><b><tt>CF_KEEPTOPIC</tt>:</b> ChanServ should restore the
channel's previous topic each time the channel is created
on the IRC network (<tt>SET KEEPTOPIC</tt>).</li>
<li><b><tt>CF_SECUREOPS</tt>:</b> Clients without a positive access
level on the channel should be prevented from getting ops
(<tt>SET SECUREOPS</tt>).</li>
<li><b><tt>CF_PRIVATE</tt>:</b> The channel is hidden from the
<tt>LIST</tt> command output, except when used by Services
administrators (<tt>SET PRIVATE</tt>).</li>
<li><b><tt>CF_TOPICLOCK</tt>:</b> ChanServ should prevent the topic
from being changed except by the <tt>SET TOPIC</tt> command
(<tt>SET TOPICLOCK</tt>).</li>
<li><b><tt>CF_RESTRICTED</tt>:</b> ChanServ should automatically
kick and ban any clients without a positive access level
that attempt to join the channel (<tt>SET
RESTRICTED</tt>).</li>
<li><b><tt>CF_LEAVEOPS</tt>:</b> 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 (<tt>SET LEAVEOPS</tt>).</li>
<li><b><tt>CF_SECURE</tt>:</b> When checking a client's channel
access level, require the client to have identified to
NickServ regardless of the setting of the nickname's
<tt>SECURE</tt> option (<tt>SET SECURE</tt>).</li>
<li><b><tt>CF_VERBOTEN</tt>:</b> The channel is forbidden
(<tt>FORBID</tt>).</li>
<li><b><tt>CF_NOEXPIRE</tt>:</b> The channel does not expire
(<tt>SET NOEXPIRE</tt>).</li>
<li><b><tt>CF_OPNOTICE</tt>:</b> ChanServ should send a notice to
the channel whenever any of the <tt>OP</tt>, <tt>VOICE</tt>,
or related commands are used (<tt>SET OPNOTICE</tt>).</li>
<li><b><tt>CF_ENFORCE</tt>:</b> ChanServ should prevent clients
from removing automatically-set channel user modes such as
auto-ops (<tt>SET ENFORCE</tt>).</li>
<li><b><tt>CF_HIDE_EMAIL</tt>:</b> The channel's E-mail address is
hidden from the <tt>INFO</tt> command output, except when
used by Services administrators (<tt>SET HIDE
EMAIL</tt>).</li>
<li><b><tt>CF_HIDE_TOPIC</tt>:</b> The channel's current or last
topic is hidden from the <tt>INFO</tt> command output,
except when used by Services administrators (<tt>SET HIDE
TOPIC</tt>).</li>
<li><b><tt>CF_HIDE_MLOCK</tt>:</b> The channel's mode lock is
hidden from the <tt>INFO</tt> command output, except when
used by Services administrators (<tt>SET HIDE
MLOCK</tt>).</li>
<li><b><tt>CF_SUSPENDED</tt>:</b> The channel is suspended
(<tt>SUSPEND</tt>).</li>
<li><b><tt>CF_MEMO_RESTRICTED</tt>:</b> Only users with the
<tt>MEMO</tt> privilege are permitted to send memos to the
channel (<tt>SET MEMO-RESTRICTED</tt>).</li>
</ul>
The flag values 0x00000100 and 0x00000400 are unused to avoid
difficulties with databases from earlier versions of Services
which used these values.</dd>
<dt><tt>char <b>suspend_who</b>[NICKMAX]</tt>
<br/><tt>char *<b>suspend_reason</b></tt>
<br/><tt>time_t <b>suspend_time</b></tt>
<br/><tt>time_t <b>suspend_expires</b></tt></dt>
<dd>Suspension data for the channel, if the <tt>CF_SUSPENDED</tt> flag
is set. Used in the same fashion as the same-named fields in the
<tt>NickGroupInfo</tt> structure (see <a href="#s3-1-1">section
7-3-1-1</a>).</dd>
<dt><tt>int16 <b>levels</b>[CA_SIZE]</tt></dt>
<dd>The channel access levels corresponding to each of the channel
privileges (<tt>CA_INVITE</tt>, <tt>CA_AKICK</tt>, and so on).
A value of <tt>ACCLEV_DEFAULT</tt> indicates that the corresponding
privilege should use the default access level defined in
<tt>access.c</tt>; a value of <tt>ACCLEV_INVALID</tt> indicates
that the corresponding privilege is disabled entirely, except with
respect to the channel founder.</dd>
<dt><tt>ChanAccess *<b>access</b></tt>
<br/><tt>int16 <b>access_count</b></tt></dt>
<dd>A variable-length array containing the channel's access list.</dd>
<dt><tt>AutoKick *<b>akick</b></tt>
<br/><tt>int16 <b>akick_count</b></tt></dt>
<dd>A variable-length array containing the channel's autokick list.</dd>
<dt><tt>ModeLock <b>mlock</b></tt></dt>
<dd>The channel's mode lock data.</dd>
<dt><tt>Channel *<b>c</b></tt></dt>
<dd><i>Not saved to persistent storage.</i> Points to the
<tt>Channel</tt> structure for the channel if it is currently in
use.</dd>
<dt><tt>int <b>bad_passwords</b></tt></dt>
<dd><i>Not saved to persistent storage.</i> The number of times an
incorrect password has been given for a channel command (such as
<tt>IDENTIFY</tt>) since the last correct password. Used to warn
about attempts to crack the password.</dd>
</dl>
<p>The <tt>cs-local.h</tt> header file includes three definitions used
internally by ChanServ:</p>
<dl>
<dt><tt>MAX_MLOCK_PARAMS</tt> (constant)</dt>
<dd>Sets the maximum number of command parameters that the <tt>SET
MLOCK</tt> 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
"<tt>SET MLOCK</tt>" leaves space for only 251 parameters, not
considering the trailing CR/LF and other IRC protocol overhead).</dd>
<dt><tt>ChanOpt</tt> (structure)</dt>
<dd>Used to define channel option data, for use by the <tt>SET</tt> and
<tt>INFO</tt> commands. The structure has the following fields:
<ul>
<li><b><tt>name</tt>:</b> The name of the option, as a string.</li>
<li><b><tt>flag</tt>:</b> The corresponding <tt>ChannelInfo.flags</tt>
flag value.</li>
<li><b><tt>namestr</tt>:</b> The option's descriptive name (for use
in <tt>INFO</tt> output), as a language string index. If
-1, the option will not be included in <tt>INFO</tt>
output.</li>
<li><b><tt>onstr</tt>, <tt>offstr</tt></b>: Response messages for
turning the option on and off via <tt>SET</tt>, as language
string indices.</li>
<li><b><tt>syntaxstr</tt></b>: The syntax message for setting the
option, as a language string index.</li>
</ul></dd>
<dt><tt>RET_*</tt> (constants)</dt>
<dd>Return values from access list modification routines. See
<a href="#s4-2-1">section 7-4-2-1</a> for details.</dd>
</dl>
<p id="s4-1-1-rename">One other point of note in <tt>cs-local.h</tt> is the
renaming of several functions in <tt>set.c</tt> and <tt>util.c</tt> using
<tt>#define</tt> directives, such as renaming <tt>init_set()</tt> to
<tt>init_set_cs()</tt>. This is to avoid conflicts with NickServ, which
includes functions of the same name in its own <tt>set.c</tt> and
<tt>util.c</tt>. 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).</p>
<p class="backlink"><a href="#top">Back to top</a></p>
<h5 class="subsubsubsection-title" id="s4-1-2">7-4-1-2. Overall module structure</h5>
<p>The main source file for ChanServ, <tt>main.c</tt>, follows the same
pattern as its NickServ counterpart; see <a href="#s3-1-2">section
7-3-1-2</a> for a more extensive description.</p>
<p>One of the first things defined in <tt>main.c</tt> is the
<tt>chanopts[]</tt> table, an array of <tt>ChanOpt</tt> structures (see
above) describing on/off options available on channels from a user's
perspective. This table is used primarily for the <tt>SET</tt> command
(thus only flags which have a corresponding <tt>SET</tt> command are
listed), and secondarily for outputting the channel's option set in
response to the <tt>INFO</tt> command. Note that the three <tt>HIDE</tt>
options are not listed here, as they are handled specially for the
<tt>SET</tt> command and not listed in the <tt>INFO</tt> output.</p>
<p>In addition to the standard command list in the <tt>cmds[]</tt> array,
two separate arrays are defined, for the <tt>HALFOP</tt>/<tt>DEHALFOP</tt>
and <tt>PROTECT</tt>/<tt>DEPROTECT</tt> 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.</p>
<p>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
<tt>do_nickgroup_delete()</tt> 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&mdash;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.</p>
<p>Of the command routines, the basic commands (<tt>HELP</tt>,
<tt>REGISTER</tt>, <tt>IDENTIFY</tt>, <tt>DROP</tt>, <tt>DROPCHAN</tt>,
<tt>INFO</tt>, <tt>LIST</tt>) 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
<tt>AKICK</tt> and the <tt>OP</tt>/<tt>VOICE</tt> command set.</p>
<p>The <tt>AKICK</tt> command handler <tt>do_akick()</tt>, despite its
length (including a separate helper routine used with <tt>LIST</tt> and
<tt>VIEW</tt>), is not dissimilar to the OperServ autokill and S-line
commands, or the NickServ <tt>ACCESS</tt> and <tt>AJOIN</tt> handlers. The
only significant difference is the presence of the <tt>ENFORCE</tt>
subcommand; this is implemented by calling <tt>check_kick()</tt>, the
routine used to check whether newly-joined clients are allowed to join a
channel (see <a href="#s4-1-3">section 7-4-1-3</a>) for each client on the
channel, causing clients which match an entry on the autokill list to be
kickbanned.</p>
<p>The <tt>OP</tt> and <tt>VOICE</tt> family of commands all perform a
common function&mdash;adding or removing a channel user mode&mdash;and for
this reason, all eight commands are handled by a single routine,
<tt>do_opvoice()</tt>, 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
<tt>opvoice_data[]</tt>, an array of structures with the following
fields:</p>
<ul>
<li><b><tt>cmd</tt>:</b> The command name.</li>
<li><b><tt>add</tt>:</b> Nonzero if the command adds a mode, zero if it
removes a mode.</li>
<li><b><tt>mode</tt>:</b> The mode character added or removed.</li>
<li><b><tt>target_acc</tt>:</b> A channel privilege index (<tt>CA_*</tt>
constant); if the target client is in this privilege class, the
command will be refused.</li>
<li><b><tt>success_msg</tt>:</b> The language string index for the message
to be sent when the command succeeds.</li>
<li><b><tt>already_msg</tt>:</b> 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.</li>
<li><b><tt>failure_msg</tt>:</b> The language string index for the message
to be sent when the command is rejected.</li>
</ul>
<p>When called, <tt>do_opvoice()</tt> first extracts the data for the
command from the <tt>opvoice_data[]</tt> table, and sets an additional
variable, <tt>target_nextacc</tt>, used for mode-removal commands to set an
upper bound on the target client access level check; this is used to, for
example, allow <tt>DEVOICE</tt> 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
<tt>do/while</tt> 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:</p>
<ol>
<li>If the target client is the client giving the command, the command is
allowed.</li>
<li>If the command removes a mode (<tt>DEOP</tt>, <tt>DEVOICE</tt>, etc.)
and the channel does not have the <tt>ENFORCE</tt> option set, the
command is allowed.</li>
<li>If a channel privilege check (<tt>target_acc</tt>) is not specified for
the command, the command is allowed.</li>
<li>If the the target client is not in the privilege class specified for
the command's privilege check, the command is allowed.</li>
<li>If an upper limit for the privilege check (<tt>target_nextacc</tt>) is
set for the command and the target client is in that privilege
class, the command is allowed.</li>
<li>Otherwise, the command is refused.</li>
</ol>
<p>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 <tt>OPNOTICE</tt> option is
enabled, and a success notice is sent to the caller. In addition, if the
command was an <tt>OP</tt> command, the channel's last-used time is updated
as for auto-ops.</p>
<p class="backlink"><a href="#top">Back to top</a></p>
<h5 class="subsubsubsection-title" id="s4-1-3">7-4-1-3. Channel status checking and modification</h5>
<p>ChanServ's routines for checking and adjusting channel status are
located in the source file <tt>check.c</tt>. There are six routines
exported from this file; two, <tt>init_check()</tt> and <tt>exit_check()</tt>,
are initialization and cleanup routines called by the module initialization
and cleanup code, respectively. The remaining routines are:</p>
<dl>
<dt><tt>void <b>check_modes</b>(Channel *<i>c</i>)</tt></dt>
<dd>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 <tt>MODE</tt> message
handle (see <a href="2.html#s6-3">section 2-6-3</a>).</dd>
<dt><tt>void <b>check_chan_user_modes</b>(const char *<i>source</i>,
struct c_userlist *<i>u</i>, Channel *<i>c</i>, int32 <i>oldmodes</i>)</tt></dt>
<dd>Checks the channel user modes of the given client (<tt><i>u</i></tt>)
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 <tt>MODE</tt> message that caused the
change (passing an empty string for the <tt><i>source</i></tt>
parameter will cause such checks to be skipped).
<tt><i>oldmodes</i></tt> is the client's previous set of modes, or
-1 for a client joining a channel. The sequence of operations is
fairly complicated:
<ul>
<li class="spaced">If the channel is not registered (or forbidden),
no changes are made.</li>
<li class="spaced">If <tt><i>source</i></tt> 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).</li>
<li class="spaced">If <tt><i>source</i></tt> is the client whose
modes are being checked, then no changes are made
<i>unless</i> 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.</li>
<li class="spaced">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 <tt>LEAVEOPS</tt> 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).</li>
<li class="spaced">The "<tt>check_chan_user_modes</tt>" callback is
called, allowing protocol modules to handle modes not
recognized by the standard processing.</li>
<li class="spaced">The client's new modes, based on channel
privilege level, are calculated by calling
<tt>check_access_cumode()</tt> (see
<a href="#s4-2-1">section 7-4-2-1</a>).</li>
<li class="spaced">If the client just joined the channel, the mode
change was done by a server, or the <tt>ENFORCE</tt> 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 <tt>ENFORCE</tt> is not set.)
In addition, if the mode change included a <tt>+o</tt>, the
channel's last-used time is updated.</li>
<li class="spaced">If the client is not an IRC operator, any
necessary mode removals are performed.</li>
</ul>
<p>The mode changes in these last two steps are performed by a
helper routine, <tt>local_set_cumodes()</tt>, which in turn calls
<tt>set_cmode()</tt> for each mode in the given set.</p></dd>
<dt><tt>int <b>check_kick</b>(User *<i>user</i>, const char *<i>chan</i>,
int <i>on_join</i>)</tt></dt>
<dd>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
<tt><i>on_join</i></tt> parameter to zero allows this routine to be
called for clients already in the channel as well, such as for the
<tt>AKICK ENFORCE</tt> command. A client can be denied access to a
channel for any number of reasons, checked in the following order:
<ul>
<li class="spaced">If the channel name is the single character
"<tt>#"</tt>" and the <tt>CSForbidShortChannel</tt>
configuration option is set, the client is kickbanned.</li>
<li class="spaced">If the client is a Services administrator, the
client is allowed.</li>
<li class="spaced">If the "<tt>check_kick</tt>" callback returns 1,
the client is kickbanned; if it returns 2, the client is
allowed.</li>
<li class="spaced">If the client is an IRC operator, the client is
allowed.</li>
<li class="spaced">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.)</li>
<li class="spaced">If the channel is not registered, then the
client is kickbanned if the <tt>CSRegisteredOnly</tt>
configuration option is set, and allowed otherwise.</li>
<li class="spaced">If the channel is forbidden or suspended, the
client is kickbanned.</li>
<li class="spaced">If the channel's mode lock has an
IRC-operators-only mode set, the client is kickbanned.</li>
<li class="spaced">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 <tt>CSSkipModeRCeck</tt>
configuration option is set.)</li>
<li class="spaced">If the client's
<tt><i>nick</i>!<i>user</i>@<i>host</i></tt> string
matches an autokick mask, the client is kickbanned.</li>
<li class="spaced">If the client matches the <tt>NOJOIN</tt>
privilege on the channel, the client is kickbanned.
The client is also kickbanned if it would match the
<tt>NOJOIN</tt> privilege when identified to its nickname;
however, this check is skipped if less time than specified
in the <tt>CSRestrictDelay</tt> configuration option has
passed since Services startup.</li>
<li class="spaced">Otherwise, the client is allowed.</li>
</ul>
<p>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
<tt>JOIN</tt> message is sent to the network to cause ChanServ to
join the channel, and a timeout for <tt>CSInhabit</tt> 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.</p>
<p><i>Implementation note: As mentioned in
<a href="../d.html">Appendix D of the user manual</a>, when
ChanServ temporarily enters a channel for a kickban, it is not
added to the internal channel data; as a result, a subsequent
<tt>UNBAN</tt> or <tt>INVITE</tt> 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.</i></p>
</dd>
<dt><tt>int <b>check_topiclock</b>(Channel *<i>c</i>, time_t <i>topic_time</i>)</tt></dt>
<dd>Called on a channel topic change (<tt><i>topic_time</i></tt> is the
timestamp associated with the topic change) to restore the topic to
its previous value if the channel's <tt>TOPICLOCK</tt> option is
set. Returns nonzero if the topic is changed by the routine,
otherwise zero.</dd>
</dl>
<p class="backlink"><a href="#top">Back to top</a></p>
<h5 class="subsubsubsection-title" id="s4-1-4">7-4-1-4. The <tt>SET</tt> and <tt>UNSET</tt> commands</h5>
<p>The handlers for the <tt>SET</tt> and <tt>UNSET</tt> commands are
located in the <tt>set.c</tt> source file; as for NickServ, they function
like miniature versions of the main <tt>chanserv()</tt> routine. One
noteworthy difference is that the on/off options (other than the three
<tt>HIDE</tt> options) are all handled by a single routine,
<tt>do_set_boolean()</tt>; the main <tt>SET</tt> handler, <tt>do_set()</tt>,
looks up the option name in the <tt>chanopts[]</tt> table defined in
<tt>main.c</tt> (checking privileges for the <tt>NOEXPIRE</tt> option),
then calls <tt>do_set_boolean()</tt>, which uses the data from the
<tt>ChanOpt</tt> structure to set channel flags and send responses to the
calling client.</p>
<p>Other noteworthy option handlers are:</p>
<dl>
<dt><tt><b>do_set_founder()</b></tt>
<br/><tt><b>do_set_successor()</b></tt></dt>
<dd>Both <tt>SET FOUNDER</tt> and <tt>SET SUCCESSOR</tt> 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 <tt>SET FOUNDER</tt> checks to ensure that the
new founder has not reached the channel registration limit, while
<tt>SET SUCCESSOR</tt> makes no such check. (Even if it did, the
check would only make sense at the time the <tt>SET SUCCESSOR</tt>
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 <a href="11.html#s1">section
11-1</a>.)</dd>
<dt><tt><b>do_set_mlock()</b></tt></dt>
<dd>The <tt>SET MLOCK</tt> handler is fairly complex, as it must parse
the mode string and parameters to ensure that users cannot
inadvertently (or maliciously) cause an invalid <tt>MODE</tt>
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
<tt>ModeLock</tt> structure, which is copied into the channel's
data when the routine successfully completes.
<p>In order to support additional modes provided by particular IRC
protocols, <tt>do_set_mlock()</tt> defines a callback, "<tt>SET
MLOCK</tt>", 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).</p></dd>
</dl>
<p class="backlink"><a href="#top">Back to top</a></p>
<h5 class="subsubsubsection-title" id="s4-1-5">7-4-1-5. ChanServ utility routines</h5>
<p>ChanServ's utility functions are defined in <tt>util.c</tt>. As with
NickServ, the preprocessor symbol <tt>STANDALONE_CHANSERV</tt> can be
defined before including <tt>util.c</tt> in another file; this causes the
routines <tt>new_channelinfo()</tt>, <tt>free_channelinfo()</tt>, and
<tt>reset_levels</tt> to be defined as <tt>static</tt>, and eliminates all
other code. <tt>new_channelinfo()</tt> and <tt>free_channelinfo()</tt> are
used to allocate and free resources for a <tt>ChannelInfo</tt> structure,
like their nickname counterparts; <tt>reset_levels()</tt> resets the
privilege levels for a channel (the <tt>levels[]</tt> array) to default
values.</p>
<p>Aside from these three functions (all exported from ChanServ),
<tt>util.c</tt> defines the following routines, along with the
initialization and cleanup routines <tt>init_util()</tt> and
<tt>exit_util()</tt>:</p>
<dl>
<dt><tt>int <b>check_channel_limit</b>(const NickGroupInfo *<i>ngi</i>, int *<i>max_ret</i>)</tt></dt>
<dd>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 <tt><i>max_ret</i></tt> if <tt><i>max_ret</i></tt> is not
<tt>NULL</tt>. This routine is exported.</dd>
<dt><tt>ChannelInfo *<b>makechan</b>(const char *<i>chan</i>)</tt></dt>
<dd>Creates a new <tt>ChannelInfo</tt> structure for the given channel
name, adds it to the database, and returns it.</dd>
<dt><tt>int <b>delchan</b>(ChannelInfo *<i>ci</i>)</tt></dt>
<dd>Removes the given channel from the database, returning nonzero on
success, zero on failure.</dd>
<dt><tt>void <b>count_chan</b>(ChannelInfo *<i>ci</i>)</tt></dt>
<dd>Updates the <tt>NickGroupInfo</tt> record for the given channel's
founder to indicate that that nickname group owns the channel,
incrementing the owned-channel count.</dd>
<dt><tt>void <b>uncount_chan</b>(ChannelInfo *<i>ci</i>)</tt></dt>
<dd>Removes the given channel from its founder's owned-channel list
and decrements the owned-channel count.</dd>
<dt><tt>int <b>is_founder</b>(const User *<i>user</i>, const ChannelInfo *<i>ci</i>)</tt></dt>
<dd>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.</dd>
<dt><tt>int <b>is_identified</b>(const User *<i>user</i>, const ChannelInfo *<i>ci</i>)</tt></dt>
<dd>Returns whether the given user has identified for the channel with
its founder password. A subset of <tt>is_founder()</tt>.</dd>
<dt><tt>void <b>restore_topic</b>(Channel *<i>c</i>)</tt></dt>
<dd>Restores the saved topic on a newly-created channel if the
channel is registered and its <tt>KEEPTOPIC</tt> option is set.</dd>
<dt><tt>void <b>record_topic</b>(ChannelInfo *<i>ci</i>, const char *<i>topic</i>, const char *<i>setter</i>, time_t <i>topic_time</i>)</tt></dt>
<dd>Records the given topic in the given channel's data structure.</dd>
<dt><tt>void <b>suspend_channel</b>(ChannelInfo *<i>ci</i>, const char *<i>reason</i>, const char *<i>who</i>, const time_t <i>expires</i>)</tt></dt>
<dd>Suspends the given channel, copying the parameters
<tt><i>reason</i></tt>, <tt><i>who</i></tt>, and
<tt><i>expires</i></tt> into the suspension data fields. (If
<tt><i>expires</i></tt> is zero, then the suspension will not
expire.)</dd>
<dt><tt>void <b>unsuspend_channel</b>(ChannelInfo *<i>ci</i>, int <i>set_time</i>)</tt></dt>
<dd>Cancels the suspension on the given channel. If
<tt><i>set_time</i></tt> is nonzero, the last-used time of the
channel will be updated according to <tt>CSSuspendGrace</tt> to
prevent the channel from expiring for that length of time (if
<tt>CSSuspendGrace</tt> or <tt>CSExpire</tt> are not set, or if the
channel already has enough time before expiration, the last-used
time will not be changed).</dd>
<dt><tt>void <b>chan_bad_password</b>(User *<i>u</i>, ChannelInfo *<i>ci</i>)</tt></dt>
<dd>Records a bad password attempt for the given channel, sending out a
<tt>WALLOPS</tt> if the number of consecutive bad password attempts
for the channel reaches the limit specified by the
<tt>BadPassWarning</tt> configuration option (if set).</dd>
<dt><tt>ChanOpt *<b>chanopt_from_name</b>(const char *<i>optname</i>)</tt></dt>
<dd>Returns the <tt>ChanOpt</tt> corresponding to the given
(case-insensitive) option name, or <tt>NULL</tt> if no matching
option is found.</dd>
<dt><tt>char *<b>chanopts_to_string</b>(const ChannelInfo *<i>ci</i>, const NickGroupInfo *<i>ngi</i>)</tt></dt>
<dd>Returns a string describing the set of options active on the given
channel in human-readable form. <tt>ngi</tt> indicates the client
to which the string will be sent and is used in <tt>getstring()</tt>
calls. The returned string is stored in a static buffer, which
will be overwritten by the next call to this routine.</dd>
</dl>
<p class="backlink"><a href="#top">Back to top</a></p>
<h4 class="subsubsection-title" id="s4-2">7-4-2. Channel access list handling</h4>
<p>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, <tt>access.c</tt>, contains
common routines and privilege level definitions, and is included in the
<tt>chanserv/main</tt> module; the other two, <tt>access-levels.c</tt> and
<tt>access-xop.c</tt>, are independent modules which provide two different
ways of manipulating access lists. A separate header file,
<tt>access.h</tt>, contains definitions related to channel access lists.</p>
<p class="backlink"><a href="#top">Back to top</a></p>
<h5 class="subsubsubsection-title" id="s4-2-1">7-4-2-1. Access list basics</h5>
<p>The <tt>access.c</tt> 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 <tt>access.h</tt> header file for certain structure and constant
definitions.</p>
<p>The list of channel privileges is defined in the <tt>levelinfo[]</tt>
array at the top of the file; each element in the array is a
<tt>LevelInfo</tt> structure that describes one privilege. (The word
"level" in the identifiers comes from the command, <tt>LEVELS</tt>, used
to modify the settings on a per-channel basis; that command name was
originally used for the meaning of "setting the <i>levels</i> at which
privileges are given".)</p>
<p>The word "privilege" itself is something of a misnomer, as it includes
two "negative privileges", <tt>CA_AUTODEOP</tt> and <tt>CA_NOJOIN</tt>.
While these can be used in the same manner as ordinary privileges, their
primary function is in conjunction with the <tt>SECUREOPS</tt> and
<tt>RESTRICTED</tt> 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</p>
<p>The <tt>LevelInfo</tt> structure, defined in <tt>access.h</tt>, contains
the following fields:</p>
<dl>
<dt><tt>int <b>what</b></tt></dt>
<dd>The <tt>CA_*</tt> constant used for this privilege.</dd>
<dt><tt>int <b>defval</b></tt></dt>
<dd>The default channel access level corresponding to this
privilege.</dd>
<dt><tt>const char *<b>name</b></tt></dt>
<dd>The user-visible name for this privilege, used in the
<tt>LEVELS</tt> command. An empty string makes the privilege
invisible to users.</dd>
<dt><tt>int <b>desc</b></tt></dt>
<dd>The message string index giving the privilege's description.</dd>
<dt><tt>int <b>action</b></tt></dt>
<dd>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
<tt>CL_LESSEQUAL</tt> to make the privilege's associated level a
maximum rather than a minimum:
<ul>
<li><b><tt>CL_SET_MODE</tt>:</b> Sets channel user modes on the
corresponding client.</li>
<li><b><tt>CL_CLEAR_MODE</tt>:</b> Clears channel user modes from
the corresponding client.</li>
<li><b><tt>CL_ALLOW_CMD</tt>:</b> Allows a command or set of
commands to be used.</li>
<li><b><tt>CL_OTHER</tt>:</b> Handled separately (or a no-op).</li>
</ul></dd>
<dt><tt>union {...} <b>target</b></tt></dt>
<dd>Data used in implementing the privilege. The union has two
members:
<ul>
<li class="spaced"><tt>struct {...} <b>cumode</b></tt>: Used for
<tt>CL_SET_MODE</tt> and <tt>CL_CLEAR_MODE</tt>. Includes
three fields:
<ul>
<li><tt>const char *<b>modes</b></tt>: The string of
mode(s) to set on the client.</li>
<li><tt>int <b>cont</b></tt>: Used to "chain" privileges
together, so that only the first applicable mode
set is used (used for <tt>AUTOOP</tt>,
<tt>AUTOHALFOP</tt>, and <tt>AUTOVOICE</tt>).</li>
<li><tt>int32 <b>flags</b></tt>: The mode flags equivalent
to <tt>modes</tt>. Set at module initialization
time (this field can be left uninitialized).</li>
</ul></li>
<li class="spaced"><tt>struct {...} <b>cmd</b></tt>: Used for
<tt>CL_ALLOW_CMD</tt>. Includes two string fields:
<ul>
<li><tt>const char *<b>main</b></tt>: The relevant command
name.</li>
<li><tt>const char *<b>sub</b></tt>: The relevant subcommand
for the given command, or <tt>NULL</tt> if none.</li>
</ul></li>
</ul></dd>
</dl>
<p>In addition to the <tt>levelinfo[]</tt> table itself, exported for use
by other modules (particularly the <tt>http/dbaccess</tt> module, described
in <a href="8.html#s2-7">section 8-2-7</a>), the file's initialization
routine <tt>init_access()</tt> copies relevant parts of the table into two
local arrays indexed by privilege (<tt>CA_*</tt> value):
<tt>def_levels[]</tt>, containing each entry's default access level (the
<tt>defval</tt> field), and <tt>lev_is_max[]</tt>, containing a boolean
indication of whether the privilege's access level is a maximum (whether
the <tt>action</tt> field has <tt>CL_LESSEQUAL</tt> set).</p>
<p>The main portion of <tt>access.c</tt> 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.</p>
<dl>
<dt><tt>int <b>get_ci_level</b>(const ChannelInfo *<i>ci</i>, int <i>what</i>)</tt></dt>
<dd><i>Exported routine.</i> Returns the given channel's level for the
given privilege, translating <tt>ACCLEV_DEFAULT</tt> in the
channel's <tt>levels[]</tt> into the appropriate default value.
Returns <tt>ACCLEV_INVALID</tt> (and logs an error message) on
invalid parameters.</dd>
<dt><tt>int <b>check_access</b>(const User *<i>user</i>, const ChannelInfo *<i>ci</i>, int <i>what</i>)</tt></dt>
<dd><i>Exported routine.</i> Returns whether the given client has the
given privilege (<tt><i>what</i></tt>) on the given channel. Due
to an unfortunate coincidence of terms, this routine can seem
confusing to call for the <tt>CA_NOJOIN</tt> "privilege": the
return value will be 1 if the client does <i>not</i> "have access
to" the channel, <i>i.e.</i> matches the <tt>CA_NOJOIN</tt>
privilege.</dd>
<dt><tt>int check_access_if_idented(const User *user, const ChannelInfo *ci, int what)</tt></dt>
<dd><i>Exported routine.</i> Like <tt>check_access()</tt>, 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 <tt>check_access()</tt>.</dd>
<dt><tt>int check_access_cmd(const User *user, const ChannelInfo *ci, const char *command, const char *subcommand)</tt></dt>
<dd><i>Exported routine.</i> 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
<tt><i>subcommand</i></tt>; otherwise, <tt><i>subcommand</i></tt>
is <tt>NULL</tt>.</dd>
<dt><tt>int <b>get_access</b>(const User *<i>user</i>, const ChannelInfo *<i>ci</i>)</tt></dt>
<dd>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 <tt>SECURE</tt>
options.</dd>
<dt><tt>static int get_access_if_idented(const User *user, const ChannelInfo *ci)</tt></dt>
<dd>Like <tt>get_access()</tt>, but returns the access level associated
with the client's nickname regardless of whether the client has
identified or not.</dd>
<dt><tt>int check_access_cumode(const User *user, const ChannelInfo *ci, int32 newmodes, int32 changemask)</tt></dt>
<dd>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).
<tt><i>newmodes</i></tt> is the client's current set of modes on
the channel; <tt><i>changemask</i></tt> indicates which modes
changed due to the action that caused this function to be called.</dd>
<dt><tt>int access_add(ChannelInfo *ci, const char *nick, int level, int uacc)</tt>
<br/><tt>int access_del(ChannelInfo *ci, const char *nick, int uacc)</tt></dt>
<dd>Adds (<tt>access_add()</tt>) or deletes (<tt>access_del()</tt>) an
entry to or from the given channel's access list for the nickname
group to which <tt><i>nick</i></tt> belongs. <tt><i>level</i></tt>
is the access level for the new entry, and <tt><i>uacc</i></tt> is
the access level of the client making the change. Both routines
return one of the following result codes, defined in
<tt>cs-local.h</tt>:
<ul>
<li><b><tt>RET_ADDED</tt></b> (<tt>access_add()</tt> only): The
target nickname did not previously exist on the channel's
access list, and was successfully added.</li>
<li><b><tt>RET_CHANGED</tt></b> (<tt>access_add()</tt> only): The
target nickname previously had a different access level on
the channel, and the access level was successfully
changed.</li>
<li><b><tt>RET_UNCHANGED</tt></b> (<tt>access_add()</tt> only): The
target nickname already had the desired access level on the
channel.</li>
<li><b><tt>RET_DELETED</tt></b> (<tt>access_del()</tt> only): The
target nickname was successfully deleted from the channel's
access list.</li>
<li><b><tt>RET_LISTED</tt></b> (used by other modules): The target
nickname was listed.</li>
<li><b><tt>RET_PERMISSION</tt>:</b> The calling user does not have
permission to make the requested change.</li>
<li><b><tt>RET_NOSUCHNICK</tt>:</b> The given nickname is not
registered.</li>
<li><b><tt>RET_NICKFORBID</tt>:</b> The given nickname is
forbidden.</li>
<li><b><tt>RET_LISTFULL</tt></b> (<tt>access_add()</tt> only): The
channel's access list is full and the given nickname is not
already on the list.</li>
<li><b><tt>RET_NOENTRY</tt></b> (<tt>access_del()</tt> only): The
given nickname does not exist on the channel's access
list.</li>
<li><b><tt>RET_INTERR</tt></b>: An internal error occurred.</li>
</ul>
Note that successful return codes are positive, while failure
return codes are negative (zero is not used as a return code).</dd>
</dl>
<p><tt>access.c</tt> also includes initialization code in the
<tt>init_access()</tt> routine, called from the <tt>chanserv/main</tt>
module's <tt>init_module()</tt>. This routine initializes the
<tt>def_levels[]</tt> and <tt>lev_is_max[]</tt> arrays, as well as the
<tt>flags</tt> field of the <tt>target.cumode</tt> 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.
<i>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.</i></p>
<p>There is also a corresponding <tt>exit_access()</tt> routine; it does
nothing, but is included for completeness.</p>
<p class="backlink"><a href="#top">Back to top</a></p>
<h5 class="subsubsubsection-title" id="s4-2-2">7-4-2-2. Manipulation via <tt>ACCESS</tt> and <tt>LEVELS</tt></h5>
<p>The <tt>chanserv/access-levels</tt> module is one of two modules for
manipulating channel access lists. It provides direct access to both the
channel access list itself, via the <tt>ACCESS</tt> command, and channels'
privilege level settings, via the <tt>LEVELS</tt> command.</p>
<p>Since the functions available in both commands change depending on
runtime parameters, their help messages are handled by a "<tt>HELP</tt>"
callback function, <tt>do_help()</tt>. In addition to description of the
two commands, a third help option, <tt>LEVELS DESC</tt> provides
descriptions of the available channel privileges; unlike most other help
messages, the text is generated on the fly from the <tt>levelinfo[]</tt>
array and corresponding description strings.</p>
<p>The <tt>ACCESS</tt> command handler, <tt>do_access()</tt>, calls one of
several subroutines to perform each of the available actions, depending on
the subcommand given. Two of these, <tt>do_access_add()</tt> and
<tt>do_access_del()</tt>, simply call the <tt>access_add()</tt> and
<tt>access_del()</tt> routines mentioned in <a href="#s4-2-1">section
7-4-2-1</a>, sending appropriate result messages to the calling client
depending on the return codes from those functions.
<tt>do_access_list()</tt> and <tt>do_access_listlevel()</tt> select access
entries for listing based on the given parameters, then call
<tt>access_list()</tt>, defined below the <tt>ACCESS</tt> subcommand
handlers, to actually send the list text to the calling client. (The
<tt><i>sent_header</i></tt> parameter to <tt>access_list()</tt> is used to
to record whether a list header message has been sent.) The final
subcommand handler, <tt>do_access_count()</tt>, simply counts up the number
of (active) access entries in the list and sends that in a response message
to the calling client.</p>
<p>The <tt>LEVELS</tt> command also has several subcommands, though these
are all handled within the command handler <tt>do_levels()</tt>. The
command implementation itself is fairly straightforward, with <tt>SET</tt>
and <tt>DISABLE</tt> searching the <tt>levelinfo[]</tt> array for the named
privilege, and <tt>LIST</tt> iterating through that array to display the
current level for each privilege.</p>
<p class="backlink"><a href="#top">Back to top</a></p>
<h5 class="subsubsubsection-title" id="s4-2-3">7-4-2-3. Manipulation via <tt>XOP</tt></h5>
<p>The <tt>chanserv/access-xop</tt> module, defined in <tt>access-xop.c</tt>,
provides the <tt>XOP</tt> 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:</p>
<ul>
<li class="spaced">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.</li>
<li class="spaced">Changes to the access list made by the <tt>ACCESS</tt>
command will also be reflected in the relevant XOP lists. If the
<tt>ACCESS</tt> command is used to add a nickname at a level not
associated with any <tt>XOP</tt> command, that nickname will be
invisible from every <tt>XOP</tt> list, even if it would have
privileges associated with one or more of the lists.
<i>Implementation note: One could conceivably change the <tt>XOP
LIST</tt> commands to list all entriess in intermediate or extreme
ranges; for example, nicknames with an access level between
<tt>ACCLEV_AOP</tt> and <tt>ACCLEV_SOP</tt> could be shown on the
AOP list, and nicknames with an access level below
<tt>ACCLEV_NOP</tt> could be shown on the NOP list.</i></li>
<li class="spaced">If the channel's privilege levels are changed (such as
with the <tt>LEVELS</tt> command), the <tt>XOP</tt> commands may no
longer perform as their help messages describe, because the XOP
lists are associated with hardcoded access levels rather than
specific privileges. <i>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.</i></li>
</ul>
<p>The commands themselves are handled by a single central routine,
<tt>handle_xop()</tt>, which is called from each command's particular
handler with the appropriate access level. <tt>handle_xop()</tt> works
much like <tt>do_access()</tt> from <tt>access-levels.c</tt> (in fact, it
is a modified copy of <tt>do_access()</tt>, and its subroutines are
likewise derived from the handlers for the <tt>ACCESS</tt> 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 <tt>XOP_LISTNAME</tt> macro at the top of the file.</p>
<p class="backlink"><a href="#top">Back to top</a></p>
<!------------------------------------------------------------------------>
<hr/>
<h3 class="subsection-title" id="s5">7-5. MemoServ</h3>
<p>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 <tt>memoserv/main</tt> module,
described below in <a href="#s5-1">section 7-5-1</a>; additional modules
allow users to maintain "ignore" lists (<tt>memoserv/ignore</tt>,
<a href="#s5-2">section 7-5-2</a>) and have memos forwarded via E-mail
(<tt>memoserv/forward</tt>, <a href="#s5-3">section 7-5-3</a>).</p>
<p class="backlink"><a href="#top">Back to top</a></p>
<h4 class="subsubsection-title" id="s5-1">7-5-1. MemoServ core functionality</h4>
<p>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 <tt>memoserv/main</tt> module is
implemented by a single source file, <tt>main.c</tt>. There is also a
header file, <tt>memoserv.h</tt>, containing structures and definitions
used by MemoServ.</p>
<p class="backlink"><a href="#top">Back to top</a></p>
<h5 class="subsubsubsection-title" id="s5-1-1">7-5-1-1. Memo data structures</h5>
<p>The structure used to store memos are defined in the header file
<tt>memoserv.h</tt>. Each memo is stored in a structure called
(appropriately enough) <tt>Memo</tt>, and the set of memos belonging to a
particular nickname group is stored in a <tt>MemoInfo</tt> structure. The
<tt>Memo</tt> structure contains the following fields:</p>
<dl>
<dt><tt>uint32 <b>number</b></tt></dt>
<dd>The index number associated with this memo, for use with MemoServ
commands such as <tt>READ</tt> and <tt>DEL</tt>. Note that while
the array of memos in a <tt>MemoInfo</tt> structure is kept free of
holes caused by memo deletions, memo index numbers do not change
except as a result of the <tt>RENUMBER</tt> command, so there is
not a one-to-one mapping between the two.</dd>
<dt><tt>int16 <b>flags</b></tt></dt>
<dd>Flags associated with the memo. Zero or more of the following
constants, OR'd together:
<ul>
<li><b><tt>MF_UNREAD</tt>:</b> The memo has not yet been read.</li>
<li><b><tt>MF_EXPIREOK</tt>:</b> The memo is allowed to expire.
<i>Implementation note: The sense of this flag is opposite
that of MemoServ's design, which expires any memos except
those explicitly locked with the <tt>SAVE</tt> 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.</i></li>
</ul></dd>
<dt><tt>time_t <b>time</b></tt></dt>
<dd>The timestamp when the memo was sent.</dd>
<dt><tt>time_t <b>firstread</b></tt></dt>
<dd>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 <tt>MSExpireDelay</tt> configuration
option controls how long MemoServ will wait after this timestamp
before expiring the memo).</dd>
<dt><tt>char <b>sender</b>[NICKMAX]</tt></dt>
<dd>The nickname of the client that sent the memo.</dd>
<dt><tt>char *<b>channel</b></tt></dt>
<dd>For memos sent to channels, the name of the channel to which the
memo was sent. <tt>NULL</tt> for other memos.</dd>
<dt><tt>char *<b>text</b></tt></dt>
<dd>The text of the memo.</dd>
</dl>
<p>The <tt>MemoInfo</tt> structure contains:</p>
<dl>
<dt><tt>Memo *<b>memos</b></tt>
<br/><tt>int16 <b>memos_count</b></tt></dt>
<dd>A variable-length array containing the memos associated with this
structure.</dd>
<dt><tt>int16 <b>memomax</b></tt></dt>
<dd>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:
<ul>
<li><b><tt>MEMOMAX_UNLIMITED</tt>:</b> There is no limit on the
number of nicknames that can be stored.</li>
<li><b><tt>MEMOMAX_DEFAULT</tt>:</b> The default limit, set by the
<tt>MSMaxMemos</tt> configuration option, is used. (If
<tt>MSMaxMemos</tt> is changed, the new limit will be
automatically applied.)</li>
</ul>
The constant <tt>MEMOMAX_MAX</tt> is also provided to give the
largest possible maximum value (a side-effect of storing the value
in 16 bits).</dd>
</dl>
<p>Each <tt>MemoInfo</tt> structure is stored as part of the corresponding
nickname group's <tt>NickGroupInfo</tt> structure (see
<a href="#s3-1-1">section 7-3-1-1</a>). 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.</p>
<p>There are two more constants in <tt>memoserv.h</tt>:
<tt>MS_RECEIVE_PRI_CHECK</tt> and <tt>MS_RECEIVE_PRI_DELIVER</tt>. These
are callback priorities that can be used with <tt>add_callback_pri()</tt>
to allow a "<tt>receive memo</tt>" callback function to ensure it is called
before functions which try to deliver the memo. Both the
<tt>memoserv/ignore</tt> and <tt>memoserv/forward</tt> modules take
advantage of these constants.</p>
<p class="backlink"><a href="#top">Back to top</a></p>
<h5 class="subsubsubsection-title" id="s5-1-2">7-5-1-2. The <tt>memoserv/main</tt> module</h5>
<p>The <tt>memoserv/main</tt> module is defined in the file <tt>main.c</tt>,
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.</p>
<p>Since, as mentioned above, the <tt>MemoInfo</tt> structures containing
memo data are stored as part of the corresponding <tt>NickGroupInfo</tt>
structures, special handling is required when moving the data to or from
persistent storage. MemoServ uses iterator functions for the
<tt>MemoInfo</tt> table that in turn iterate through <tt>NickGroupInfo</tt>
structures, and sets up a dummy record containing each record's associated
nickname group ID, much like the <tt>nickserv/access</tt> module (see
<a href="#s3-2">section 7-3-2</a>). For individial <tt>Memo</tt> records,
an additional iterator is used to loop through each memo in every
<tt>MemoInfo</tt> structure.</p>
<p>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:</p>
<ul>
<li><b><tt>send_memo()</tt></b> and <b><tt>send_chan_memo()</tt></b>,
which send memos to users and channels respectively;</li>
<li><b><tt>list_memo()</tt></b>, which sends the calling client a one-line
description of a memo, and <b><tt>list_memo_callback()</tt></b>, a
callback function to do the same thing;</li>
<li><b><tt>read_memo()</tt></b> and <b><tt>read_memo_callback</tt></b>,
which display the text of a memo;</li>
<li><b><tt>save_memo()</tt></b> and <b><tt>save_memo_callback</tt></b>,
which mark memos as non-expiring; and</li>
<li><b><tt>del_memo()</tt></b> and <b><tt>del_memo_callback()</tt></b>,
which delete memos.</li>
</ul>
<p>Other utility routines include:</p>
<dl>
<dt><tt>void <b>check_memos</b>(User *<i>u</i>)</tt></dt>
<dd>Checks whether the given client's nickname group has any unread
memos, sending an appropriate message to the client if so (and if
the <tt>NF_MEMO_SIGNON</tt> flag is set for the nickname group).
Called by the "<tt>user create</tt>" callback function
<tt>do_user_create()</tt> if the client is recognized.</dd>
<dt><tt>void <b>expire_memos</b>(MemoInfo *<i>mi</i>)</tt></dt>
<dd>Deletes all memos in the given <tt>MemoInfo</tt> structure that
are eligible for expiration.</dd>
<dt><tt>MemoInfo *<b>get_memoinfo</b>(const char *<i>name</i>, NickGroupInfo **<i>owner_ret</i>, int *<i>error_ret</i></tt></dt>
<dd>Returns the <tt>MemoInfo</tt> structure corresponding to the given
nickname, or <tt>NULL</tt> on error. On success,
<tt>*<i>owner_ret</i></tt> is set to point to the
<tt>NickGroupInfo</tt> structure for the corresponding nickname
group; on error, <tt>*<i>error_ret</i></tt> is set to one of the
following error codes:
<ul>
<li><b>GMI_INTERR</b>: An internal error occurred.</li>
<li><b>GMI_FORBIDDEN</b>: The given nickname is forbidden.</li>
<li><b>GMI_SUSPENDED</b>: The given nickname is suspended.</li>
<li><b>GMI_NOTFOUND</b>: The given nickname is not registered.</li>
</ul>
<i>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.</i></dd>
</dl>
<p>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, <tt>SET</tt> and <tt>INFO</tt>, are mostly
branching trees for the various nickname options and statuses.</p>
<p class="backlink"><a href="#top">Back to top</a></p>
<h4 class="subsubsection-title" id="s5-2">7-5-2. Memo ignore lists</h4>
<p>The <tt>memoserv/ignore</tt> 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 <tt>ignore.c</tt>.</p>
<p>For the most part, <tt>ignore.c</tt> works in the same way as the
<tt>nickserv/access</tt> and <tt>nickserv/autojoin</tt> modules. The
one routine unique to <tt>memoserv/ignore</tt> is the
<tt>check_if_ignored()</tt> routine, added as a callback function to the
core MemoServ module's "<tt>receive memo</tt>" callback at priority
<tt>MS_RECEIVE_PRI_CHECK</tt>. The routine runs through the nickname
group's ignore list twoce: the first time, it treats each entry as a
<tt><i>Nick</i>!<i>user</i>@<i>host</i></tt> 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
<tt>MEMO_C_GETS_NO_MEMOS</tt> (the same as is used if the target has a
memo limit of zero), to prevent leaking information about the ignore list
contents.</p>
<p class="backlink"><a href="#top">Back to top</a></p>
<h4 class="subsubsection-title" id="s5-3">7-5-3. Memo forwarding</h4>
<p>The <tt>memoserv/forward</tt> module, defined in the source file
<tt>forward.c</tt>, provides an interface between memos in Services and an
external mail system. As such, one of its prerequisite modules (other than
<tt>memoserv/main</tt>, of course) is <tt>nickserv/mail-auth</tt>, to
reduce the possibility of Services being abused to send mail to arbitrary
addresses.</p>
<p>The module includes two methods of forwarding memos: manual and
automatic. The former, manual forwarding, is done through the
<tt>FORWARD</tt> command, implemented by the <tt>do_forward()</tt> routine
and its subroutines <tt>fwd_memo()</tt> and <tt>fwd_memo_callback()</tt>.
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 <tt>MSForwardDelay</tt> specifies.
If this and the other checks pass, the command handler takes one of two
actions, depending on its parameter. If "<tt>ALL</tt>" was given, then
<tt>fwd_memo()</tt> is called for each memo in the calling client's
<tt>MemoInfo</tt> structure; otherwise, the parameter is passed to
<tt>process_numlist()</tt> to call the <tt>fwd_memo_callback()</tt>
callback function (which in turn calls <tt>fwd_memo()</tt>) for each memo
specified in the index list.</p>
<p><tt>fwd_memo()</tt> itself does not perform the actual sending of the
mail message; rather, it accumulates message body text in its parameters
<tt>char **<i>p_body</i></tt> and <tt>int *<i>p_bodylen</i></tt> (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.</p>
<p>The second method of forwarding, automatic forwarding on receipt, is
implemented by the <tt>do_receive_memo()</tt> function, attached to the
MemoServ "<tt>receive memo</tt>" callback, along with the
<tt>do_set_forward()</tt> option handler (attached to the "<tt>SET</tt>"
callback) to set forwarding options for a nickname group. Of the
forwarding options, <tt>ON</tt> (flag <tt>NF_MEMO_FWD</tt>) and
<tt>OFF</tt> (no flags set) are fairly obvious. For <tt>COPY</tt> (flags
<tt>NF_MEMO_FWD</tt> and <tt>NF_MEMO_FWDCOPY</tt>), 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 <tt>SET FORWARD COPY</tt> 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.</p>
<p class="backlink"><a href="#top">Back to top</a></p>
<!------------------------------------------------------------------------>
<hr/>
<h3 class="subsection-title" id="s6">7-6. StatServ</h3>
<p>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 <tt>modules/statserv</tt>
directory by a single source file, <tt>main.c</tt>, and an accompanying
header file, <tt>statserv.h</tt>.</p>
<p class="backlink"><a href="#top">Back to top</a></p>
<h4 class="subsubsection-title" id="s6-1">7-6-1. StatServ data structures</h4>
<p>The current implementation of StatServ only records data for servers.
This data is stored in a <tt>ServerStats</tt> structure, defined in
<tt>statserv.h</tt> and containing the following fields:</p>
<dl>
<dt><tt>ServerStats *<b>next</b>, *<b>prev</b></tt></dt>
<dd>Used to maintain the linked list of <tt>ServerStats</tt>
structures.</dd>
<dt><tt>char *<b>name</b></tt></dt>
<dd>The name of the server to which this structure applies.</dd>
<dt><tt>time_t <b>t_join</b></tt></dt>
<dd>The timestamp at which the server most recently joined the network.
Zero if the server is not currently connected.</dd>
<dt><tt>time_t <b>t_quit</b></tt></dt>
<dd>The timestamp at which the server last disconnected. Zero if
Services has never seen the server disconnect.</dd>
<dt><tt>char *<b>quit_message</b></tt></dt>
<dd>The quit message used the last time the server disconnected.
<tt>NULL</tt> if Services has never seen the server disconnect.</dd>
<dt><tt>int <b>usercnt</b>, <b>opercnt</b></tt></dt>
<dd>The current number of clients and IRC operators on the server.</dd>
</dl>
<p>As the data has no direct references to other data stored persistently,
saving the <tt>ServerStats</tt> records is straightforward.</p>
<p>There are also commented-out definitions for <tt>MinMax</tt> and
<tt>MinMaxHistory</tt> structures, and <tt>MinMaxHistory</tt> fields in
<tt>ServerStats</tt>; these were originally intended for keeping a history
of client and operator counts on each server, but this functionality was
never implemented.</p>
<p class="backlink"><a href="#top">Back to top</a></p>
<h4 class="subsubsection-title" id="s6-2">7-6-2. The StatServ module</h4>
<p>The module itself, defined in <tt>main.c</tt>, is likewise quite simple.
It follows the general layout of other pseudoclient core modules, though it
only has two commands (other than <tt>HELP</tt>) and a few callback
routines.</p>
<p>The two commands, <tt>SERVERS</tt> and <tt>USERS</tt>, are handled by
<tt>do_servers()</tt> and <tt>do_users()</tt> 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). <tt>do_servers()</tt> is only slightly more
complex, handling four distinct subcommands. Three of these
(<tt>STATS</tt>, <tt>LIST</tt>, and <tt>VIEW</tt>) extract information from
the <tt>ServerStats</tt> structures and send them to the calling client;
the last, <tt>DELETE</tt>, allows a structure to be deleted, and is
protected by an <tt>is_services_admin()</tt> check in the <tt>if</tt>
chain.</p>
<p>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 "<tt>server create</tt>" and
"<tt>server delete</tt>" callbacks; these functions update the relevant
<tt>ServerStats</tt> structures as servers join and leave the network, with
<tt>stats_do_server()</tt> (the "<tt>server create</tt>" handler) creating
a new <tt>ServerStats</tt> structure if it sees a server connect that is
not recorded in the <tt>ServerStats</tt> structure table.</p>
<p>StatServ also uses the "<tt>user create</tt>", "<tt>user delete</tt>",
and "<tt>user MODE</tt>" callbacks to keep track of the number of clients
and IRC operators on the network, stored in the file-scope variables
<tt>usercnt</tt> and <tt>opcnt</tt> 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).</p>
<p class="backlink"><a href="#top">Back to top</a></p>
<!------------------------------------------------------------------------>
<hr/>
<h3 class="subsection-title" id="s7">7-7. Miscellaneous pseudoclients</h3>
<p>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 <tt>modules/misc</tt> directory.</p>
<p class="backlink"><a href="#top">Back to top</a></p>
<h4 class="subsubsection-title" id="s7-1">7-7-1. HelpServ</h4>
<p>The HelpServ pseudoclient, defined in <tt>modules/misc/helpserv.c</tt>,
uses the parameters given in each <tt>PRIVMSG</tt> it receives to form a
pathname for a text file, which (if it exists) is then sent to the calling
client as <tt>NOTICE</tt>s. The pathname is formed by concatenating the
path given by the <tt>HelpDir</tt> module configuration setting (the
example configuration file uses <tt>helpfiles</tt>, relative to the
Services data directory) with each of its space-separated parameters,
inserting a path separator ("<tt>/</tt>") 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.</p>
<p>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 "<tt>../../../../../etc/passwd</tt>". (If there are
explicit symbolic links to parent directories, of course, HelpServ will
happily follow them.) <i>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 "<tt>.</tt>" and
"<tt>..</tt>" are the only entries that are automatically created within
each directory, so the set of allowed characters is essentially "everything
except <tt>/</tt> and <tt>.</tt>".</i></p>
<p>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
<tt>data/helpfiles</tt> 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.</p>
<p class="backlink"><a href="#top">Back to top</a></p>
<h4 class="subsubsection-title" id="s7-2">7-7-2. DevNull</h4>
<p>The DevNull pseudoclient, defined in <tt>modules/misc/devnull.c</tt>, is
an extremely simple pseudoclient which, like its Unix namesake
<tt>/dev/null</tt>, simply discards any <tt>PRIVMSG</tt>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 <tt>/query</tt> target to prevent messages
from going to unintended users.</p>
<p class="backlink"><a href="#top">Back to top</a></p>
<!------------------------------------------------------------------------>
<hr/>
<p class="backlink"><a href="6.html">Previous section: Database handling</a> |
<a href="index.html">Table of Contents</a> |
<a href="8.html">Next section: Other modules</a></p>
</body>
</html>