IRC Services Technical Reference Manual
7. Services pseudoclients
7-1. Basic features of a pseudoclient
7-2. OperServ
7-2-1. OperServ core functionality
7-2-2. Usermask-related functions
7-2-2-1. Common mask data support
7-2-2-2. Autokills
7-2-2-3. S-lines
7-2-3. Session limiting
7-2-4. News
7-3. NickServ
7-3-1. NickServ core functionality
7-3-1-1. Nickname data structures and utility macros
7-3-1-2. Overall module structure
7-3-1-3. The SET and UNSET commands
7-3-1-4. NickServ utility routines
7-3-1-5. Nickname colliding
7-3-2. Nickname access lists
7-3-3. Nickname auto-join lists
7-3-4. Linking and nickname groups
7-3-5. E-mail address authentication
7-4. ChanServ
7-4-1. ChanServ core functionality
7-4-1-1. Channel data structures
7-4-1-2. Overall module structure
7-4-1-3. Channel status checking and modification
7-4-1-4. The SET and UNSET commands
7-4-1-5. ChanServ utility routines
7-4-2. Channel access list handling
7-4-2-1. Access list basics
7-4-2-2. Manipulation via ACCESS and LEVELS
7-4-2-3. Manipulation via XOP
7-5. MemoServ
7-5-1. MemoServ core functionality
7-5-1-1. Memo data structures
7-5-1-2. The memoserv/main module
7-5-2. Memo ignore lists
7-5-3. Memo forwarding
7-6. StatServ
7-6-1. StatServ data structures
7-6-2. The StatServ module
7-7. Miscellaneous pseudoclients
7-7-1. HelpServ
7-7-2. DevNull
Previous section: Database handling |
Table of Contents |
Next section: Other modules
7-1. Basic features of a pseudoclient
Pseudoclients are the user-visible part of Services, providing the
actual service functions which IRC users take advantage of. While the
details of each pseudoclient differ greatly, all pseudoclients share some
common features:
- Register one or more nicknames via the
"introduce_user" callback, through which the pseudoclient
communicates with IRC clients; see below for details.
- Receive commands from IRC clients via PRIVMSG
messages. (According to RFC 1459, pseudoclients must not
respond to NOTICE messages, in order to prevent infinite
loops in which two pseudoclients repeatedly respond to each others'
notices.)
- Communicate to IRC clients via NOTICE messages.
(While not explicitly mandated by the RFC, this is in order to
avoid potential message loops with other pseudoclients. Occasional
requests have been made for Services to allow using
PRIVMSG for communication with users, apparently because
some IRC programs do not show NOTICE messages to users,
but such requests have been intentionally disregarded for the above
reason.)
- Provide a set of commands which users can use to invoke
the pseudoclient's functions. (The miscellaneous modules described
in section 7-7 are an exception; the HelpServ
pseudoclient has only one function with no associated command name,
and the DevNull pseudoclient has none.) Each message to a
pseudoclient is interpreted as a command name and parameters, each
separated by one or more space characters (ASCII 0x20—note
that the tab character is not treated as a separator).
The "introduce_user" callback mentioned above is called by the
same-named function, introduce_user(), to request that each module
introduce its nickname(s) to the IRC network by calling
send_pseudo_nick() (in send.c). The callback takes a
single const char * parameter, which is NULL when
the callback is called at startup, or the nickname seen in a KILL
message when one is received; accordingly, pseudoclients should introduce
their nicknames only when the parameter either is NULL or matches
(case-insensitively, as determined by irc_stricmp()) the nickname
to be introduced. send_pseudo_nick() takes three parameters: the
nickname of the pseudoclient to be introduced, the pseudoclient's "real
name" string (usually a description of the pseudoclient), and a flag value,
composed of zero or more of the following flags:
- PSEUDO_OPER: The client requires IRC operator
privileges. (This does not necessarily guarantee that the client
will be given the +o user mode; some IRC servers allow any
Services pseudoclient to use IRC operator functions, and
+o is omitted with such servers.)
- PSEUDO_INVIS: The client should be marked invisible
(user mode +i).
Command processing is typically performed by hooking into the core's
"m_privmsg" callback, which is intended specifically for this
purpose. Depending on the pseudoclient, it may also be necessary to
respond to other events on the IRC network, such as channel joins or mode
changes; these can typically be handled by hooking into the relevant
callback. In extreme cases, it may be necessary to make use of the
low-level "receive message" callback as well; this should be
avoided when possible, however, as it circumvents the standard message
processing and can result in network desynchronization.
For databases maintained by pseudoclients, the following six functions
are typically provided by the pseudoclient for other code that needs direct
access to the database (type is the record data
type, name is a distinguishing name generally
derived from type, and keytype
is the data type of the key field):
- type *add_name(type *record)
- Adds the given record to the database, returning a pointer to the
record structure as stored (which may be different from the pointer
passed into the function).
- void del_name(type *record)
- Deletes the given record from the database. record
is assumed to be valid, i.e. a pointer returned by a
previous call to another database function.
- type *get_name(keytype *key)
- Returns the record for the given key, or NULL if the key
is not found in the database.
- void put_name(type *record)
- Indicates that the given record is no longer in use. Each call to
a database's get() function must be followed by a
put() call for the same record, unless the record is
deleted first. Implementation note: A put() function
was first introduced in 5.0 with the intention that it be used for
indicating when a record had been updated, for the purpose of
storing the updated data into persistent storage such as an
SQL-based database. DBMS support never materialized, and the
function's purpose was redefined for version 5.1 to keep track of
which records are actively in use, but I have no confidence that
the code does put() calls in all necessary cases and no
others; in fact, it appears that put_nickgroupinfo() (at
least) is called more often than it should be. It may have been
better to drop put() entirely and use other methods of
checking whether records are in use before expiring them.
- type *first_name()
type *next_name()
- Iterates through the database. first() returns the first
record in the database, and subsequent next() calls return
each successive record, finally returning NULL when all
records have been iterated through (if there are no records at all,
both first() and next() return NULL).
The order is database-dependent, but no record will be returned
more than once from the time first() is called to the time
next() returns NULL. It is unspecified whether
records added while iterating through the database will be
included in the iteration. Note that these functions are
not considered a "get" for the purposes of the
put() function; thus a call to put() is not
required for simple iteration, but if any other database function
is to be called before the following next(), then an
explicit call to get() (and a matching call to
put()) must be made to prevent the record in use from
being deleted.
The following sections describe each of the Services pseudoclients in
detail. The reader is assumed to be familiar with the functions of each
pseudoclient from a user's point of view, as described in
section 3 of the user's manual.
Back to top
7-2. OperServ
The OperServ pseudoclient provides services to IRC operators allowing
control of the network and of Services itself. While not seen by most IRC
users, this pseudoclient is discussed first since it provides functionality
used by most other pseudoclients.
As with most of the Services pseudoclients, OperServ is composed of one
core or "main" module, operserv/main, and several optional modules
providing additional functionality. These are each discussed in separate
sections below.
Back to top
7-2-1. OperServ core functionality
The core functionality of OperServ is contained in the
modules/operserv/main.c source file, compiled into the
operserv/main module. In addition to the implementation of the
core OperServ commands, this file also defines several utility functions
used by several other pseudoclient modules; external declarations of these
functions and associated constants are located in
modules/operserv/operserv.h.
main.c is written using the same general structure as most
module source files. At the top of the file are variable definitions,
including configuration variables, which are given the same names as their
corresponding configuration directives; these are followed by forward
declarations of individual command routines and the command list. Next
come database-related structures and routines, followed by the top-level
pseudoclient routines (the "introduce_user", "m_privmsg",
and "m_whois" callback functions), and finally the actual command
routines, along with any utility routines needed.
For OperServ, the first item of note is the list of several commands and
command routines inside #ifdef DEBUG_COMMANDS. These are, as the
conditional name suggests, commands used for debugging Services, and are
only available to the Services superuser; the commands are described in
detail below.
OperServ stores several values to persistent storage, including the
maximum client count, the time at which that maximum was reached, and the
superuser (SU command) password. This data is stored by
aggregating the data into a single structure, operserv_data, and
storing that structure as a single database "record" in a table named
"oper". Two exported functions, get_operserv_data() and
put_operserv_data(), are also provided to allow external modules,
in particular the XML import and export modules (see
section 8-4), to access the data as well.
In order to check a client's Services privilege level (Services
operator, Services administrator, or Services superuser), OperServ requires
access to the nickname data, in which each registered nickname group's
privilege level is stored (the os_priv member). However, since
NickServ requires that OperServ be loaded first, OperServ must look up the
symbols for these routines during its normal operation. Six local functions
are defined, one for each of the imported routines (get_nickinfo(),
put_nickinfo(), _get_ngi(), put_nickgroupinfo(),
first_nickgroupinfo(), and next_nickgroupinfo()), taking
the same parameters and returning the same values as the real routines; the
local versions look up the symbol for each routine and then call the
corresponding address, returning an appropriate error value if the symbol
cannot be resolved (or NickServ is not loaded).
The main processing routine itself, operserv(), is registered
as a callback function for the "m_privmsg" callback, called for
each PRIVMSG received by Services. The routine first checks that
the message is intended for OperServ; then it ensures that the client that
sent the message is an IRC operator, to avoid any possibility of
non-operator clients exploiting a bug in the OperServ code. The message
received is then logged in the log file, except that parameters to the
SU and SET SUPASS commands are replaced with a dummy
string to avoid leaving the superuser password in the log file. (OperServ
has no way to detect if one of these commands is misspelled, so for
example, a mistaken SET SUPSAS will be logged in full, including
the password.) Next, the command name is extracted from the message
using the strtok() function; this also prepares strtok()
for the command handler to use in extracting the command parameters (see
below). After handling CTCP PING messages separately, OperServ
calls a "command" callback, allowing other modules a chance to
process the command first. Finally, if no callback function responds to
the command, it is looked up in the command table and the command's handler
function is called (if the command is not found, an error message is sent
instead).
Following this and other callback functions are the privilege check
functions; is_services_root(), is_services_admin(), and
is_services_oper(), which check whether a client has Services
superuser, Services administrator, and Services operator privileges
respectively (any client with a higher privilege level is treated as
having all lower privilege levels as well). The is_services_root()
function relies on the ServicesRoot configuration setting, along
with the UF_SERVROOT flag in the User structure
indicating clients which have successfully used the SU command;
lower privilege levels check the os_priv field of the nickname
group data structure (see section 7-3-1) for privilege
determination. These routines are exported, and used widely throughout the
other pseudoclient modules to perform privilege checks on clients; in
particular, they can be used as privilege check functions in the
has_priv member of a Command structure. There is another
routine, nick_is_services_admin(), which checks if a particular
nickname can potentially can Services administrator access, ignoring
whether the nickname is actually in use at the time; this is used by
NickServ to prevent certain operations from being performed on such
nicknames by clients without Services administrator privilege.
The command routines themselves are fairly straightforward. One thing
to note is that the routines all obtain parameters via
strtok(NULL,...) and strtok_remaining(); this relies on
the fact that operserv() leaves the message string in the
strtok() buffer after stripping the command name, so that each
routine can parse the command's parameters as appropriate for that command.
strtok_remaining() is used when the full remaining string is
desired, such as when sending a global message; this function is preferred
to strtok(NULL,"") because the latter can leave leading or
trailing whitespace in the result.
The processing for the HELP command, in do_help(), is
somewhat tortuous (although still simpler than in other pseudoclients) in
order to give proper help responses depending on how Services is
configured. In the case of OperServ, some commands may or may not be
available depending on what submodules are loaded; the COMMANDS
help text, which lists the available commands, is combined from several
language strings depending on whether the appropriate modules are available
to provide a list of the commands which are actually usable at that
particular time. Other modules include more complex processing, such as
checking the configuration variable values or protocol features. For
commands that do not need such special-casing, the help_cmd()
routine in the Services core (see section 2-10)
sends a help message as defined by the Command structure.
The debug commands, defined toward the bottom of main.c, are as
follows:
- LISTSERVERS (send_server_list())
- Sends a NOTICE for each server in the server list giving
the contents of the Server structure.
- LISTCHANS (send_channel_list())
- Sends two NOTICEs for each channel in the channel list.
The first NOTICE gives the channel name, creation time,
modes (as a string), limit, key ("-") if none, and
topic; the second contains each client on the channel along with
that client's channel user modes on the channel. Any messages
which exceed the maximum length of an IRC line are silently
truncated.
- LISTCHAN channel (send_channel_info())
- Sends a NOTICE for each client on the given channel with
the client's channel user modes (as a hexadecimal value) and
nickname.
- LISTUSERS (send_user_list())
- Sends a NOTICE for each client on the network, giving
the client's nickname and usermask, the "fake host" or "-"
if none, the IP address or "-" if none, user modes as a
string, signon timestamp (from the remote server), servicestamp,
server name, nickname status flags or "-" if the nickname
is not registered, ignore value, and real name field.
- LISTUSER nickname (send_user_info())
- Sends three NOTICEs describing the state of the given
client. The first is identical to the line that LISTUSERS
would output for the client; the second gives the channels which
the client has currently joined; and the third gives the channels
which the client has identified to ChanServ for.
- LISTTIMERS (send_timeout_list(), in
timeout.c)
- Sends a NOTICE giving the current time, followed by a
NOTICE for each timeout currently in the list giving the
timeout pointer, timestamp, function pointer, and function
argument. This routine is defined in timeout.c because
the internal timeout fields are hidden from other source
files.
- MATCHWILD pattern string (do_matchwild())
- Sends a NOTICE giving the result of calling
match_wild with the given parameters.
- SETCMODE [channel modes mode-params...] (do_setcmode())
- Calls set_cmode() with the given parameters, using
ServerName as the message sender. If no parameters are
given, calls set_cmode(NULL,NULL) to flush out all
pending mode changes. Note that the number of mode parameters
(including the mode string itself) is limited by the
SETCMODE_NPARAMS macro, defined to 10 in the source
code.
- MONITOR-IGNORE [nickname] (do_monitor_ignore())
- If a nickname is given, starts recording that nickname's ignore
value to the log file at 100ms intervals; if no nickname is given,
cancels any previous monitoring. Note that in order to ensure
sub-second resolution, the TimeoutCheck configuration
variable is set to 10 (milliseconds) when a nickname is given,
potentially causing a reduction in performance; the old value is
not restored when the command is given without a
nickname.
- GETSTRING language string (do_getstring())
- Sends a NOTICE containing the text corresponding to the
given string in the given language. Both language
and string can be given as either names or raw
numbers.
- SETSTRING language string [text] (do_setstring())
- Calls setstring() to set the given string in the given
language to the given text. If no text is given, the string
becomes empty.
- MAPSTRING old new (do_mapstring())
- Calls mapstring() to map one string to another.
old and new are string names or
numbers.
- ADDSTRING name (do_addstring())
- Calls addstring() to add a string with the given name to
the language table. On success, sends a NOTICE with the
new string number.
Back to top
7-2-2. Usermask-related functions
7-2-2-1. Common mask data support
One of the major features of OperServ not included in the core module
is the autokill and S-line functionality. While these are in fact defined
in separate modules, most of the processing for both sets of functions is
subsumed under the MaskData structure and its generic processing
routines included in the core OperServ module, located in
maskdata.c and maskdata.h. This structure contains the
elements common to all of these features: a mask string (whose
interpretation is left to the particular module), an associated reason
string, the nickname of the client that added the mask, the time the mask
was added, the time it expires, and the last time the mask was applied to a
client; these are stored in the mask, reason,
who, time, expires, and lastused
fields of the structure, respectively. The remaining fields (other than
the record management fields next, prev, and
usecount) are: type, containing an 8-bit value
identifying the type of mask; num, giving the user-visible index
number for session exception records (see section
7-2-3); and limit, used for the session limit in session
exception records.
As mentioned above, each MaskData record has an associated
type; more accurately, there are multiple sets of MaskData
records, one set for each type (the type field is only used
internally for loading and saving data from or to persistent storage, and
does not need to be set by the caller). The available types, defined by
the MD_* constants in maskdata.h, are:
- MD_AKILL: An autokill record.
- MD_EXCLUDE: An autokill exclusion record.
- MD_EXCEPTION: A session exception record.
- MD_SGLINE: An SGline record.
- MD_SQLINE: An SQline record.
- MD_SZLINE: An SZline record.
It is worth noting that (as also mentioned in section
7-2-2-3 below) the values chosen for MD_SGLINE,
MD_SQLINE, and MD_SZLINE are the ASCII values of the
characters G, Q, and Z respectively; this was
done in order to simplify common code for all three types of S-lines, so
that the type value could be used as is in messages sent to clients rather
than having to make separate tests to determine the appropriate text to
send. (For example, most of the language file messages for S-line actions
use "S%cLINE" in the format string, where the %c is
replaced by the type value.)
One other structure, MaskDataCmdInfo, can be found in
maskdata.h; this structure collects information particular to a
single MaskData type for use by the common command processing.
The bulk of the structure consists of language string indices, which are
used in sending responses to commands procesed by the common code. These
are preceded by: name, the command name (such as "AKILL"
or "SGLINE"); type, the record type to be used; and
def_expiry_ptr, a pointer to a variable containing the default
expiration period in seconds (a pointer is used so that any changes to the
value can be recognized immediately without having to modify this structure
as well). There are also five function pointers, all of which are optional
(and should be set to NULL if not needed):
- void (*mangle_mask)(char *mask)
- Makes any necessary changes to a mask before it is operated on.
Any modifications may be made to the mask as long as they do not
lengthen it beyond the original string length. Currently, this
is used to force case-insensitive masks to lowercase, to avoid the
possibility of multiple matching masks with differing case from
being added to the database.
- int (*check_add_mask)(const User *u, uint8 type, char *mask, time_t *expiry_ptr)
- Checks whether the given mask of the given type is allowed to be
added with the given expiration time (in seconds from the present
time, passed as a pointer); returns nonzero to allow the mask to be
added, zero to deny it. The mask and expiration time may be
modified by the function, provided that the mask is not lengthened
beyond the original string length.
- void (*do_add_mask)(const User *u, uint8 type, MaskData *md)
- Performs any extra actions necessary when a mask is added to the
database, such as sending that mask to the network.
- void (*do_del_mask)(const User *u, uint8 type, MaskData *md)
- Performs any extra actions necessary when a mask is removed from
the database.
- int (*do_unknown_cmd)(const User *u, const char *cmd, char *mask)
- Processes an unknown subcommand for the associated command,
returning nonzero if the subcommand was handled, zero otherwise.
The function which implements the mask-related command support is
do_maskdata_cmd(), defined below the database support functions in
maskdata.c. This function behaves in the same way as a standard
pseudoclient command handler, taking a single User *
parameter giving the client that sent the command, and retrieving command
parameters via strtok(NULL,...). After obtaining the subcommand
name and its parameters (with double-quote processing for masks), the
routine checks for the known subcommands ADD, DEL,
CLEAR, LIST, VIEW, CHECK, and
COUNT, processing each appropriately. If the subcommand is not
one of these, it is passed to the command's do_unknown_cmd()
function; if that function returns zero or does not exist, an error is
sent to the client.
The subcommands themselves are implemented by local routines:
do_maskdata_add(), do_maskdata_del(), and so on. These
check the validity of the parameters for the particular subcommand and
perform the appropriate action, using the language string indices in the
MaskDataCmdInfo structure to send replies to the client that
issued the command.
There are also three utility functions defined after the command
handler routines:
- void *new_maskdata()
- Allocates, initializes, and returns a new MaskData
structure. (The return value is void * to avoid a
type warning in the database table declaration.)
- void free_maskdata(void *record)
- Frees the MaskData structure pointed to by
record, Does nothing if record is
NULL.
- char *make_reason(const char *format, const MaskData *data)
- Returns the reason string for the given mask and format string.
If format contains a "%s", it will be
replaced by the mask's reason string in the return value;
otherwise, the returned string is identical to
format. The result string is stored in a static
buffer, which is overwritten by subsequent calls to
make_reason().
With respect to the database management routines, MaskData
records are stored in variable-length arrays, one array for each of the 256
possible mask types. (Arrays were chosen over hash tables for simplicity;
since the most common use of these records is searching all records of a
particular type for one that matches a given string, there would be little
benefit to the use of a hash table.) The next and prev
fields are not ordinarily required for array handling, but the code stores
the (integer) array index value in the next field via a cast to
void *, allowing the code to know where in the array a given
record is located without having to search through the array every time
(note that the num field is used for a different
purpose—index numbers for session exceptions—and is not
available). Aside from the standard database operations, which take a
uint8 type parameter in addition to the mask record itself,
the following functions are included:
- MaskData *get_matching_maskdata(uint8 type, const char *str)
- Searches for and returns (in the same manner as
get_maskdata(), including expiration checks) a mask which
matches the wildcard pattern given by str. If more
than one mask matches, an arbitrary one is returned.
- MaskData *get_exception_by_num(int num)
- Retrieves a mask of the MD_EXCEPTION (session exception)
type by its index number.
- MaskData *move_exception(MaskData *except, int newnum)
- Changes the index number of the given MD_EXCEPTION mask.
(Internally, this reorders the mask array so that entries remain in
order by index number.)
Back to top
7-2-2-2. Autokills
As mentioned above, one of the main uses of this mask data code is the
autokill module, operserv/akill, defined in akill.c and
akill.h. While support for the OperServ AKILL and
EXCLUDE commands simply makes use of the aforementioned
do_maskdata_cmd() function, handling for the actual autokills and
autokill exclusions themselves is defined within the autokill module. This
includes:
- send_akill() and cancel_akill(), and
their autokill exclusion companions send_exclude() and
cancel_exclude(), which (via callbacks hooked into by
protocol modules) take care of adding and removing autokills and
exclusions on the network. Note that cancel_akill() and
cancel_exclude() destroy their char *
parameters, but as they are only called when deleting a record,
this is not a problem.
- do_user_check(), which hooks into the
"user check" callback to check whether a newly connecting
client matches any active autokills or exclusions and take
appropriate action.
- create_akill(), an exported function which
creates a new autokill record given the usermask, reason, setter
nickname, and time to expiration. (There is currently no
equivalent function for autokill exclusions.)
- The data structure and helper functions for
do_maskdata_cmd(). In particular, check_add_akill
uses simple heuristics to check for masks that are so general that
they would match any conceivable combination of username and
hostname (for example, "*@*" or "?*@*?.*?*") and
disallow such masks, in order to avoid situations where no one
could connect to the network because every client matched the
autokill.
- The AKILLCHAN command implementation,
do_akillchan(). It is worth noting that (as commented in
the code) there is a race condition that can allow the client to
reconnect in the miniscule interval between disconnecting the
client with a KILL command and adding an autokill for that
client; this negligible risk was taken in light of the fact that
doing it the other way (sending the autokill first) causes some IRC
servers to automatically kill the client, and OperServ's
KILL would cause a "user not found" message to be logged
(which did in fact generate some complaints from users). However,
once the client is killed, the username and hostname from the
User structure are no longer available, so they must be
saved ahead of time (the code at one point failed to do this,
predictably resulting in crashes—hence the vitriolic comment
about that mistake).
- The "connect" callback function, which sends
all autokills to the network on initial connection.
- The "expire_maskdata" timeout function, which
checks for autokill expiration. In order to prevent flooding of
IRC operators, for example when autokills set by an
AKILLCHAN command expire, expiration announcements via
WALLOPS are (if enabled) only sent at the rate of one per
second, and any further expirations are merged into a single
"nnn more autokills have expired" message sent after all
expirations are complete.
- The OperServ "HELP" callback function, which
is required to display the AKILL help message correctly.
- The OperServ "STATS ALL" callback function,
used to calculate and display autokill memory usage in response to
a STATS ALL command.
One item of interest in the module setup code at the bottom of the
source file is the handling of the EXCLUDE command.
EXCLUDE can be enabled or disabled via a configuration file
option (EnableExclude), for reasons discussed below. Rather than
create two command tables, one with EXCLUDE and one without, the
code simply modifies the EXCLUDE entry to have an empty command
name if the command is disabled; since command names parsed by OperServ
cannot be empty, the command will never be found. (In order to locate the
entry again once the name has been cleared, a static variable is used, set
during module initialization to point to the proper element of the command
array. The name is restored during module cleanup so that a subsequent
initialization will likewise be able to find it.)
The handling of autokill exclusions is trickier than it may seem at
first glance, due to protocols which support network-wide autokill masks
but not exclusion masks. If one takes the naive approach of simply adding
autokill masks as usual, one will find that the effectiveness of the
autokill exclusions is severely limited. For example, consider a network
with an autokill for *@*.example.com and an exclusion for
*@oper.example.com. As long as the only users in the
example.com domain to connect are from the
oper.example.com host, the exclusion will function as expected.
However, as soon as a user from another example.com host connects,
the autokill will be triggered and sent out to the network—with the
unintended result that even users from oper.example.com are
prevented from connecting, since Services has no way to inform other
servers on the network about the autokill exclusion.
In order to avoid this problem, the operserv/akill module will
not make use of a protocol's autokill features if that protocol does not
also support autokill exclusions, and will simply send out a KILL
message for each user to be disconnected from the network. However, this
may not be desirable for networks that have no intention of using the
autokill exclusion functionality. For this reason, the
EnableExclude configuration option was added, allowing such
networks to choose between taking advantage of the protocol's autokill
feature and using exclusions with autokills.
Back to top
7-2-2-3. S-lines
S-lines are the other primary user of the mask data code. These sets of
client restriction masks (SGlines, SQlines, and SZlines) are implemented by
the operserv/sline module, defined in sline.c and
sline.h.
The S-line module is very similar to the autokill module, both
functionally and internally (in fact, sline.c started out as a
copy of akill.c). The primary difference is in the sharing of
data and code between the three mask types. Since the command names differ
only in a single character, the code takes the shortcut of using that
character as the mask type for use with the MaskData support
functions—this is the reason for the warning comment in
maskdata.h about changing the type values—and including a
%c token in relevant messages which is replaced by the mask type.
This enables a single message, such as "%d masks on the S%cLINE
list.", to be shared by code for all three sets of masks. Several
functions are likewise shared by the three mask types, taking a type
parameter to select which data set to operate on.
Naturally, each type of mask is interpreted differently, so the actual
processing for each type is handled separately. SQlines, in particular,
are checked by the utility routine check_sqline(), called from the
callback functions do_user_check() and
do_user_nickchange_after(), because there are several possible
ways of handling a match:
- If the client is an IRC operator and the
SQlineIgnoreOpers configuration option is enabled, then
SQlines are not checked at all. (The new_oper
parameter is required because, when called from
do_user_check(), the User record will not yet
have been created.)
- If the SQline is for a guest nick, the client is left
alone, though the SQline itself is sent to the network.
- If the SQlineKill configuration option is
not set and the protocol in use supports forced nickname
changing, the client's nickname is changed to a guest nickname, and
the SQline is sent to the network.
- Otherwise, the client is killed, and the SQline is sent
to the network.
Back to top
7-2-3. Session limiting
The last method of client control, session limiting, is implemented by
the operserv/sessions module, defined in sessions.c. The
module consists of two major parts: the actual session maintenance and
limiting code, and session exception mask management via the
EXCEPTION command.
The session maintenance functionality is contained within the
add_session() and del_session() routines, called from
callback functions when clients join or leave the network, respectively.
These routines keep track of each host with one or more clients on the
network by means of a hash table containing Session structures,
one per host. When add_session is called, it looks up the
Session record for the new client's hostname, creating a new
record with a count of zero if no record for that host already exists, then
increments the host's client count by one; conversely,
del_session() decrements the host's client count, deleting the
Session structure when the count reaches zero.
The "limiting" part of session limiting is a check in
add_session() to determine whether the host has "too many"
clients on the network. Here, "too many" is defined by either the limit
given in an exception record (see below) or the default limit given in the
DefSessionLimit configuration directive; if adding the new client
would cause the host's client count to exceed the relevant limit, then the
client is killed (any clients already connected are left alone). An
autokill can also be added for thte host depending on the configuration
settings.
The module also provides a SESSION command to allow Services
operators to view the contents of the session list; this can be used to
find hosts from which a large number of clients are connecting. The
SESSION command is available even if actual limiting of sessions
is disabled (by setting DefSessionLimit to zero).
The second part of the module, session exceptions, allows fine-tuning of
the default limit on client connections. For example, it may be desirable
to allow extra connections from a particular host known to be used by IRC
operators. Such exceptions to the default session limit are stored using
MaskData structures with the MD_EXCEPTION type; each
record is treated as a wildcard pattern against which a connecting client's
hostname is matched, and if a match is found, that limit is used instead of
the default, as described above. If more than one matching session
exception exists, the first one in the list (from a user's point of view,
the one with the lowest index number) is used.
Session exceptions are maintained using the EXCEPTION command.
The processing for this command, in do_exception() and its
subroutines, is very similar to other mask-type commands like
AKILL; however, session exceptions require a limit value in
addition to the mask, and it was considered simpler to keep the handling
for EXCEPTION separate rather than modify
do_maskdata_cmd() to handle both command formats. The
EXCEPTION command also includes a MOVE subcommand,
allowing one record to be moved relative to another within the list (so,
for example, a newly-added exception can be moved earlier in the list to
take precedence over other exceptions).
A careful look at the initialization code will show that the
"user check" callback function, check_sessions() (which
in turn calls add_session()), is added at a priority of -10. This
is in order to ensure that the function is called after all other checks
have been performed, as described in section
4-5-2 (and is a poor design choice for the reasons described there).
Back to top
7-2-4. News
The last OperServ submodule is the operserv/news module,
defined in news.c and news.h. This is a fairly simple
module, containing routines to handle news item storage and implement the
LOGONNEWS and OPERNEWS commands.
News items are stored in a single variable-length array of
News structures, newslist. The News structure
contains, aside from the text of the news item itself, the news type
(NEWS_LOGON or NEWS_OPER), an index number used when
deleting the item, the nickname of the client that added the item, and the
time the item was added. As with MaskData structures,
next and prev fields are included only to mirror other
structures, and the array index is stored in the next field.
The two news commands, LOGONNEWS and OPERNEWS, share
the same code, do_news(); this routine is called by the actual
command handlers with one of the news type codes, either
NEWS_LOGON or NEWS_OPER, and accesses an array of
language string indices (msgarray[]) to return proper messages
for each command, similar to do_maskdata_cmd(). Unlike most other
commands (but like the OPER and ADMIN commands in the
OperServ core), the news commands include a LIST command available
to all IRC operators, and other subcommands (ADD and DEL)
restricted to Services operators; thus, the command handler must perform
privilege checks on its own rather than specifying a privilege check
routine in the command table.
Back to top
7-3. NickServ
NickServ is typically the first pseudoclient IRC users interact with.
It was also the first pseudoclient created during Services' initial design,
on which all other pseudoclients were based. The current NickServ is
divided into a core module and four submodules implementing additional
features; each module is described in its own section below.
Back to top
7-3-1. NickServ core functionality
The core NickServ functionality is implemented by the
nickserv/main module. Alongside the primary module source file,
main.c, the module makes use of three additional source files:
collide.c, handling the disconnection or forced removal of clients
using unauthorized nicknames; set.c, implementing the SET
command and its subcommands; and util.c, containing various
utility routines used by NickServ.
NickServ makes use of two distinct header files. One,
nickserv.h, defines the data structures used for storing nickname
information (NickInfo and NickGroupInfo, described below)
along with declarations of exported routines and macros used with nickname
records. The other, ns-local.h, contains declarations of routines
used within NickServ, necessarily declared extern because they
reside in separate source files but not intended to be used outside the
NickServ modules.
Back to top
7-3-1-1. Nickname data structures and utility macros
Two separate structures are used to store nickname data; this is to
facilitate the implementation of nickname links, as described in
section 7-3-4. One structure, NickInfo,
contains data that is distinct for each individual nickname; the other,
NickGroupInfo, contains data that is shared among each group of
linked nicknames.
The NickInfo structure includes the following data:
- NickInfo *next, *prev
- Used to link records together in the internal hash table.
- int usecount
- The record's usage count (number of gets minus number of
puts).
- char nick[NICKMAX]
- The actual nickname. Capitalization is as used when the nickname
was registered, and does not change due to later actions. The
buffer size, NICKMAX, is defined in the global header
file defs.h.
- int16 status
- The nickname's status. This is a combination of zero or more of
the following flags:
- NS_VERBOTEN: The nickname is a forbidden
nickname set with the FORBID command. ("Verboten"
is German for "forbidden", but there is no particular
meaning behind this choice other than a whim of the
developer as he was writing the code.)
- NS_NOEXPIRE: The nickname is not to be expired
regardless of how long it remains unused (SET
NOEXPIRE).
- NS_KILL_HELD: The nickname is currently being
held by an "enforcer" pseudoclient after killing (or
changing the nickname of) a client that was using the
nickname without permission.
- NS_GUESTED: An IRC message has been sent to
change the nickname of the client using the nickname, but
the nickname change has not yet occurred.
Of these flags, NS_VERBOTEN and NS_NOEXPIRE are
"permanent" flags (collected in the NS_PERMANENT mask),
which are retained across restarts of Services, while
NS_KILL_HELD and NS_GUESTED are "temporary" flags
(collected in the NS_TEMPORARY mask), which are cleared
each time the database is loaded from persistent storage. Note
that the value 0x0001 (bit 0) is not used because it served a
separate purpose in previous versions of Services.
- char *last_usermask
- The last user@host mask used by the owner
of the nickname (i.e., a client authorized to use the
nickname). If the owner is currently online, that client's
user@host mask is used. On IRC networks
where a "fake hostname" is available, that hostname is used
instead of the client's actual hostname.
- char *last_realmask
- Like last_usermask, but uses the real hostname instead of
any "fake hostname". On networks without such a fake hostname,
this field is identical to last_usermask.
- char *last_realname
- The last "real name" string used by the owner of the nickname.
- char *last_quit
- The message used the last time the owner quit IRC. NULL
if not available, such as for newly-registered nicknames.
- time_t time_registered
- The timestamp when the nickname was registered.
- time_t last_seen
- The timestamp at which the owner most recently used the nickname.
Only updated when the owner stops using the nickname; if the
nickname is currently in use, this field should not be relied on.
- uint32 nickgroup
- The ID of the nickname group with which this nickname is associated
(see below).
- uint32 id_stamp
- The servicestamp of the client that last identified for the
nickname. Used to retain identification status across Services
restarts.
- User *user
- Not saved to persistent storage. A pointer to the
User structure for the client currently using the
nickname, or NULL if the nickname is not currently in
used.
- int16 authstat
- Not saved to persistent storage. Zero or more of the
following flags, indicating the nickname's authentication status:
- NA_IDENTIFIED: The current user of the nickname
has identified by password as the nickname's owner.
Mutually exclusive with NA_IDENT_NOEMAIL.
- NA_IDENT_NOMAIL: The current user of the
nickname has identified by password as the nickname's
owner, but has not registered an E-mail address with the
nickname when one is required. Mutually exclusive with
NA_IDENTIFIED.
- NA_RECOGNIZED: The current user of the nickname
is recognized via the nickname access list (see
section 7-3-2).
- int bad_passwords
- Not saved to persistent storage. The number of consecutive
failed password identification attempts for the nickname. Used to
determine whether or not to kill a client for attempted password
cracking.
The NickGroupInfo structure includes the following data:
- NickGroupInfo *next, *prev
- Used to link records together in the internal hash table.
- int usecount
- The record's usage count (number of gets minus number of
puts).
- uint32 id
- The nickname group's ID, a unique 32-bit value. Typically, this
value is randomly assigned. The value zero is not allowed (it is
used in NickInfo records to indicate the lack of an
associated nickname group, as for forbidden nicknames).
- nickname_t *nicks
uint16 nicks_count
- Not saved to persistent storage. A variable-length array
containing the nicknames associated with this nickname group. Used
for convenience, to avoid having to search through the nickname
database every time a list of nicknames is needed.
- uint16 mainnick
- The "main nickname" for the group, used to represent the nickname
group in things such as channel access lists. Specified as an
index into the nicks[] array.
- Password pass
- The password used for identification for the nickname group.
- char *url
- A URL associated with the nickname group. Can be arbitrarily set
by the owner.
- char *email
- An E-mail address associated with the nickname group. Can be
arbitrarily set by the owner, and may be required at registration
time by setting the NSRequireEmail configuration option.
- char *last_email
- When mail-based authentication (see section
7-3-5) is in use, this field is set to the previous contents
of the email field when the owner changes the E-mail
address, and is cleared when the new address is authenticated; this
allows the RESTOREMAIL command to function.
- char *info
- A free-form text string associated with the nickname group. Can be
arbitrarily set by the owner.
- int32 flags
- A bitmask containing zero or more of the following nickname group
flags:
- NF_KILLPROTECT: NickServ should prevent other
clients from using the nickname by either killing them or
changing their nicknames, depending on the IRC protocol in
use (SET KILL ON/QUICK/IMMED).
- NF_SECURE: NickServ should require password
identification for the nickname even if the client is on
the nickname's access list (SET SECURE).
- NF_MEMO_HARDMAX: The client is not permitted to
change the nickname's memo limit (MemoServ SET LIMIT
HARD).
- NF_MEMO_SIGNON: MemoServ should inform the
client of new memos at signon (MemoServ SET NOTIFY
ON/LOGON).
- NF_MEMO_RECEIVE: MemoServ should inform the
client of new memos when they are sent (MemoServ SET
NOTIFY ON/NEW).
- NF_PRIVATE: The nickname is hidden from the
LIST and LISTEMAIL command output, except
when used by Services administrators (SET
PRIVATE).
- NF_HIDE_EMAIL: The nickname's E-mail address is
hidden from the INFO and LISTEMAIL
command output, except when used by Services administrators
(SET HIDE EMAIL).
- NF_HIDE_MASK: The nickname's
user@host mask is hidden from the
INFO and LIST command output, except when
used by Services administrators (SET HIDE
USERMASK).
- NF_HIDE_QUIT: The nickname's last quit message
is hidden from the INFO command output, except
when used by Services administrators (SET HIDE
QUIT).
- NF_KILL_QUICK: NickServ should allow only 20
seconds instead of 60 for an unauthorized client to change
nickname (SET KILL QUICK/IMMED).
- NF_KILL_IMMED: NickServ should kill or
nickchange unauthorized clients immediately with no grace
period (SET KILL IMMED).
- NF_MEMO_FWD: MemoServ should forward received
memos to the nickname's E-mail address (MemoServ SET
FORWARD ON/COPY).
- NF_MEMO_FWDCOPY: MemoServ should save copies of
forwarded memos (MemoServ SET FORWARD COPY).
- NF_SUSPENDED: The nickname group is suspended
(SUSPEND).
- NF_NOOP: ChanServ should prevent the nickname
from being added to channel access lists (SET
NOOP).
Note that the value 0x00000004 (bit 2) is not included in the above
flags because it served a separate purpose in previous versions of
Services. Instead, this value is used as a temporary flag
(NF_NOGROUP) when loading databases from earlier versions
of Services, to indicate that a nickname group does not yet have an
ID value assigned.
- int16 os_priv
- The nickname group's privilege level with respect to OperServ.
Can be any value, but typically either zero (no special
privileges) or one of the following values:
- NP_SERVOPER: Services operator privilege.
- NP_SERVADMIN: Services administrator
privilege.
Other values are treated by the OperServ privilege checking code
(see section 7-2-1) as having the next lowest
recognized value.
- int32 authcode
- The authentication code set for the nickname group, or zero if no
code is set. See section 7-3-5.
- time_t authset
- The timestamp when the nickname group's authentication code was
set (meaningless if no code is set).
- int16 authreason
- The reason the nickname group's current authentication code was
set (meaningless if no code is set). One of the following
constants:
- NICKAUTH_REGISTER: The nickname was newly
registered, and the E-mail address provided in the
REGISTER command requires authentication.
- NICKAUTH_SET_EMAIL: The nickname group's
E-mail address was changed with the SET EMAIL
command, and the new address requires authentication.
- NICKAUTH_SETAUTH: Authentication is required as
the result of a Services administrator using the
SETAUTH command.
- NICKAUTH_REAUTH: Authentication is required as
the result of the nickname owner using the REAUTH
command.
- char suspend_who[NICKMAX]
- The nickname of the client that suspended the nickname group
(meaningless if the NF_SUSPENDED flag is not set).
- char *suspend_reason
- The reason the nickname group was suspended (meaningless if the
NF_SUSPENDED flag is not set).
- time_t suspend_time
- The timestamp when the nickname group was suspended (meaningless if
the NF_SUSPENDED flag is not set).
- time_t suspend_expires
- The timestamp at which the nickname group's suspension expires
(meaningless if the NF_SUSPENDED flag is not set).
- int16 language
- The language preferred for messages sent to the nickname group.
One of the LANG_* constants in the Services core's
language.h.
- int16 timezone
- The time zone offset specified by the nickname group's owner for
use in displaying times. A number of minutes (possibly negative)
to be added to the UTC timestamps, or TIMEZONE_DEFAULT to
use the Services process' default.
- int16 channelmax
- The maximum number of channels the nickname group is allowed to
register, CHANMAX_UNLIMITED for no limit, or
CHANMAX_DEFAULT for the default limit (set by the
ChanServ CSMaxReg configuration setting).
- char **access
int access_count
- The nickname group's access list (see section
7-3-2).
- char **ajoin
int ajoin_count
- The nickname group's auto-join list (see section
7-3-3).
- char **ignore
int ignore_count
- The nickname group's memo ignore list (see section
7-5-2).
- MemoInfo memos
- The nickname group's stored memos (see section
7-5-1).
- channame_t *channels
int channels_count
- Not saved to persistent storage. The names of the channels
currently registered by this nickname group.
- User **id_users
int id_users_count
- Not saved to persistent storage. Pointers to User
structures for clients which have identified for this nickname
group.
- time_t last_sendauth
- Not saved to persistent storage. The timestamp when the
SENDAUTH command was last used for this nickname group
(see section 7-3-5).
- int bad_auths
- Not saved to persistent storage. The number of times the
AUTH command has been used with a bad authentication code
for this nickname group (see section 7-3-5).
In addition, nickserv.h declares the following convenience
functions and macros:
- int nick_recognized(const NickInfo *ni)
int user_recognized(const User *u)
- Returns whether the given client is recognized via an access list
entry, regardless of whether the client has identified for the
nickname group or not. The client can be specified by either
NickInfo or User structure. (Nickname
authorization flags are always cleared when a client disconnects
from the network, so the NickInfo form will always return
false if used on a nickname not currently in use.)
- int nick_identified(const NickInfo *ni)
int user_identified(const User *u)
- Returns whether the given client has identified for its nickname.
The client can be specified by either NickInfo or
User structure.
- int nick_id_or_rec(const NickInfo *ni)
int user_id_or_rec(const User *u)
- Returns whether the given client is recognized via access list or
has identified for its nickname (or both). The client can be
specified by either NickInfo or User structure.
- int nick_ident_nomail(const NickInfo *ni)
int user_ident_nomail(const User *u)
- Returns whether the NA_IDENT_NOMAIL flag is set for the
given client; i.e., evaluates to true when has identified
for its nickname but the nickname lacks a required E-mail address.
The client can be specified by either NickInfo or
User structure.
- int ngi_unauthed(const NickGroupInfo *ngi)
- Returns whether the given nickname group (more accurately, the
nickname group's E-mail address) is unauthenticated. Note that use
of the REAUTH command does not cause the nickname group to
lose its authenticated status.
- int valid_ngi(const NickGroupInfo *ngi)
- Returns whether the given NickGroupInfo structure pointer
is valid; i.e., evaluates to true when ngi
is neither NULL nor NICKGROUPINFO_INVALID. (The
latter value is used in User structures to indicate that
the client has an associated NickInfo structure but no
NickGroupInfo structure, as is the case for forbidden
nicknames.)
- const char *ngi_mainnick(const NickGroupInfo *ngi)
- Returns the given nickname group's main nickname.
- NickGroupInfo *get_ngi(const NickInfo *ni)
NickGroupInfo *get_ngi_id(uint32 id)
- Retrieves the NickGroupInfo record corresponding to the
given NickInfo or ID value; if there is no corresponding
record, a warning message is logged and NULL is returned.
(Like any other database "get" routine, the structure must be "put"
with put_nickgroupinfo() when no longer needed.)
- int check_ngi(const NickInfo *ni)
int check_ngi_id(uint32 id)
- Returns whether there is a corresponding NickGroupInfo
record for the given NickInfo or ID value, logging a
warning message if not. (The
put_xxx(get_xxx(...)) is a common way of
checking for the existence of a record, since the
put_xxx() explicitly allow a NULL
parameter.)
The STANDALONE_NICKSERV define and (non-macro) utility
functions are discussed in section 7-3-1-4.
Back to top
7-3-1-2. Overall module structure
The overall structure of the NickServ module generally follows the same
pattern as the OperServ module: variable and command declarations, database
handling, PRIVMSG and other callbacks, and command routines.
The nickserv/main module uses two separate databases, one for
NickInfo records and one for NickGroupInfo records. The
database handling code is more or less straightforward, using hash.h
to maintain the in-memory tables; however, since most of the record
management routines take additional actions (for example, the "add" and
"get" functions update the record's use count), the base hash functions are
defined with a trailing underscore, like add_nickinfo_(), and the
actual functions (like add_nickinfo()) are wrapped around these.
NickServ exports all of the NickInfo and NickGroupInfo
database functions, as well as a "put" function for each record type.
When saving the databases to persistent storage, the nicks[]
array and mainnick field of NickGroupInfo records are not
saved directly; rather, the main nickname itself is saved as a
NICKMAX-sized buffer, and the nickname group is initialized with
this nickname when first loaded (the array is subsequently filled in when
the relevant NickInfo records are loaded).
NickServ keeps track of clients' status using callback functions for
new clients, clients changing nickname, and disconnecting clients. The
routines that do the actual processing, validate_user() (for a
client starting to use a nickname) and cancel_user() (for a client
no longer using a nickname), are located in util.c, discussed in
section 7-3-1-4.
The NickServ commands themselves tend to be fairly complex, especially
when compared to the OperServ command handlers. This is in part due to the
wide range of features available in NickServ, and in part due to the fact
that NickServ and its system of registered nicknames are the primary way by
which clients authenticate themselves, and as such must handle a variety of
circumstances to maintain security, while other pseudoclients simply rely
on the authorization flags set by NickServ. The following command handlers
are particularly worthy of note:
- do_help()
- NickServ's commands include a number whose help text changes based
on factors such as the requesting client's IRC operator status,
features available in the IRC protocol in use, and NickServ
configuration settings. The code to handle help requests is
accordingly complex, with many commands unable to rely on the
help_cmd() routine.
- do_register()
- Just as NickServ is the gateway to the rest of Services' functions,
the REGISTER command is the gateway to NickServ, providing
one of the two methods by which a client can gain access to
Services (the other being authentication to a previously registered
nickname). The REGISTER handler must therefore be
particularly careful to guard against abuse, both to prevent
improper access to other Services commands and to prevent the
REGISTER command itself from being abused by arbitrary
clients. The command handler takes the following precautions
before allowing a nickname to be registered:
- Prevents the command from being used by any
particular client more than once every NSRegDelay
seconds. This stops mass-registration of nicknames by
automated clients, avoiding both the accompanying load on
Services itself (more memory usage, more time spent looking
up nicknames) and any undesirable side effects, such as the
sending of automated E-mail to arbitrary address when mail
authentication is in use (see section
7-3-5).
- Prevents the command from being used by a client
within NSInitialRegDelay seconds of connecting to
the network. This prevents automated clients from getting
around the NSRegDelay limitation by repeatedly
connecting, issuing a REGISTER command, and
disconnecting in rapid succession. (It is still possible
to avoid the limitation by connecting a large number of
clients at once, but as a practical matter it is impossible
for NickServ to distinguish such attempts from ordinary
registration requests, and the sudden presence of a large
number of clients on the network should itself be an
indication of trouble.)
- Prevents "guest" nicknames from being
registered, which could result in unauthenticated clients
suddenly gaining Services access after a forced nickname
change. (The check itself is performed by the
do_reglink_check() earlier in main.c,
a callback function attached to the "REGISTER/LINK
check" callback; do_register() calls this
callback via the reglink_check() function in
util.c.)
- Ensures that the nickname is not already
registered. Ordinarily, if a nickname is registered then
the client's User structure will have a pointer to
the record in its ni field, but if the nickname is
missing a corresponding nickname group (a database error
unless the nickname is forbidden), the ni field
will be NULL and the ngi field will be
set to the constant NICKGROUPINFO_INVALID, so that
combination is checked for as well. Also, just in case,
do_register() performs a final check by accessing
the database directly, to ensure that the nickname is not
registered in duplicate.
- Checks that the E-mail address, if given, is (1)
syntactically valid and (2) not disallowed due to a
RejectEmail configuration directive.
- Prevents more than NSRegEmailMax
nicknames from being registered to the same address, again
to avoid undue load on Services from a registration flood.
This check calls count_nicks_with_email() to
actually count nicknames; this routine has to search the
entire nickname database, which can take a significant
amount of time if many nicknames are registered, so this
check is performed last.
- As an adjunct to the previous check (and
regardless of the setting of NSRegEmailMax), also
prevents a nickname from being registered if the E-mail
address given is already in use by another nickname which
is awaiting mail authentication; this acts as a further
guard to prevent a particular mail address from getting
"mailbombed" by multiple registration requests that make it
through the previous checks.
- do_dropemail()
do_dropemail_confirm()
- The DROPEMAIL and DROPEMAIL-CONFIRM commands are
the only two commands in Services that require state to be kept
specifically for those commands. Due to the potential for data
loss through an erroneous DROPEMAIL command, some form of
confirmation was desired, such as an "Are you sure?" requester in
response to a user deleting a file in a GUI. Since Services'
interface is limited to single-line commands, however, this can
only be accomplished through two commands, the second of which
(DROPEMAIL-CONFIRM) serves to confirm the action requested
by the first (DROPEMAIL). In order for this to be
effective, the DROPEMAIL-CONFIRM handler must know which
commands have been sent by whom, so that clients cannot send
arbitrary DROPEMAIL-CONFIRM commands to get around the
confirmation check. This is accomplished through the file-local
dropemail_buffer[] array, which holds the most recently
issued, unconfirmed DROPEMAIL commands. (A single state
record stored in the User structure was another
possibility, but one that was discarded to avoid bloat in that
structure, particularly since the vast majority of clients would
never use the command anyway.) When a valid DROPEMAIL
command is given, the client is told the number of nicknames that
would be deleted, and the given mask is stored in the buffer array,
with the oldest unconfirmed mask removed if no slots are empty. A
DROPEMAIL-CONFIRM for the same mask will then locate the
appropriate buffer slot, ensure that the same client sent both
commands and that the elapsed time between the two commands is not
too long (as defined by the NSDropEmailExpire option), and
performs the actual nickname deletion.
- do_info()
- The INFO command has the ability to show extended
information about a nickname with the option ALL (only
available to the nickname owner or Services administrators).
However, not all nicknames have any additional information to be
displayed. To prevent the "use ALL for more information"
message from being appended if there is not actually anything else
to show, the INFO command handler uses a method inspired
by superuser privilege checks in the Linux kernel, which keeps
track of whether a process has taken advantage of those privileges.
When the macro CHECK_SHOW_ALL is included in a conditional
test, it will evaluate to true when the ALL option is
present (and the client has permission to use it), but a separate
flag variable, used_all, will also be set regardless of
the presence of ALL; the routine can then determine
whether there were any items that would have been displayed if
ALL was given. As noted in the source code comments, the
macro should be the last test in any conditional expression which
uses it, to prevent used_all from being set for an item
that will not actually be displayed due to a subsequent test.
- do_set()
do_unset()
- As the SET and UNSET commands (SET in
particular) have a large number of options, they are defined in a
separate source file, set.c. See
section 7-3-1-3 for details.
NickServ also includes a debug command enabled by the
DEBUG_COMMANDS preprocessor symbol: LISTNICK, which
displays the NickInfo and (if present) NickGroupInfo data
for a given nickname.
In addition to the ordinary module setup code, the
nickserv/main module supports two command-line options. One,
-encrypt-all, is recognized by the Services core; NickServ's
init_module() routine checks the corresponding global flag,
encrypt_all and, if it is set, encrypts all nicknames using the
encryption type specified by the core's EncryptionType setting.
The other option, -clear-nick-email, is NickServ-specific, and is
handled by do_command_line(), a callback function for the core's
"command line" callback; when the option is encountered, the
callback function clears the E-mail address from all nickname groups.
As several messages used by NickServ can change based on configuration
options or the features available in the IRC server, the initialization and
cleanup code (as well as the reconfiguration handler,
do_reconfigure()) call mapstring() to adjust the messages
appropriately. The commands REGISTER,
DROPEMAIL/DROPEMAIL-CONFIRM, and GETPASS can
also be disabled by configuration options; the commands are disabled by
setting the name field of the corresponding Command
structure to the empty string, so that it will not be found when the
command table is searched. Pointers to the structures are saved in
file-local variables so that the names can be restored at reconfiguration
or module cleanup time.
Back to top
7-3-1-3. The SET and UNSET commands
The handlers for the SET and UNSET commands,
do_set() and do_unset() in set.c, work much like
miniature versions of the top-level NickServ message handler
nickserv(), in that they check which option name was used with the
command and call an appropriate subroutine to do the actual work. However,
the do_set() routine parses the option parameters itself, rather
than leave such parsing to the individual routines (UNSET does not
take any additional parameters, so no such parsing is needed); for this
reason, the setup code in do_set() is more complex than that in
nickserv(), since the INFO option treats the entire line
as a single parameter, HIDE takes two single-word parameters
separated by a space, and the other options take a single one-word
parameter.
Additionally, both SET and UNSET can be used by
Services administrators to set options for other users' nicknames. For
this reason, the individual option-setting routines take both a
User * and a NickInfo * parameter, where the
NickInfo * parameter is the nickname whose options are to be
changed (if the client giving the command is not a Services administrator
or does not give a target nickname, this will simply be equal to the
ni field of the User structure). Implementation note:
This raises an interesting problem—how does an option's handler
routine tell the difference between SET option, SET
!MyNick option, and SET !OtherNick option when
sending result messages? The simple answer is that it doesn't: all option
handlers use the "your nick" message style, as mentioned in
Appendix D of the user's manual. If implemented,
it would probably be reasonable to ignore the distinction between the
SET option and SET !MyNick option cases,
and simply judge which message to use by comparing the NickInfo
parameter with the ni field of the client's User
structure.
The option handlers themselves are simple for the most part, checking
the option value given and setting or clearing the relevant flag or field
in the NickInfo structure or its associated NickGroupInfo
structure. Routines which deserve special mention are:
- do_set_password()
- The password setting itself is straightforward (note that the
memory containing the cleartext password and the temporary copy of
the encrypted password is cleared as soon as it is no longer
needed); however, the routine first checks the
NSSecureAdmins option, and disallows the change if the
target is a (different) Services administrator and the command
sender is not the Services superuser.
- do_set_email()
- This routine makes several checks mostly related to mail
authentication before allowing the E-mail address to be changed:
- The address must be a valid E-mail address.
- The address must not be rejected by a RejectEmail
configuration directive.
- The address must not be in use by a nickname awaiting mail
authentication (as with REGISTER).
- The number of nicknames currently using the address must be
less than NSRegEmailMax, if set. (Note that if
the current nickname's group contains other linked
nicknames, the E-mail address change can cause the nickname
total to exceed NSRegEmailMax. This is not seen as
a significant problem, and it avoids the opposite problem in
which a user who somehow exceeded the limit would no longer
be able to change their E-mail address at all.)
- The time since the last successful SET EMAIL must be
at least NSSetEmailDelay seconds, if set.
If the above checks all pass, the change is performed, and if the
client used to have the NA_IDENT_NOMAIL status and an
E-mail address was set, the status is changed to
NA_IDENTIFIED. The routine also features its own
callback, "SET EMAIL", used by the mail authentication
code. Note that this routine does not check the
NSRequireEmail configuration option, and assumes that if
it is passed a NULL value, indicating that the address
should be unset, then that is valid. (In fact, do_unset()
checks NSRequireEmail before calling
do_set_email().)
- do_set_timezone()
- SET TIMEZONE allows the time zone to be specified as
either a literal time offset (-5, +6:30, etc.) or a time zone
name. Time zone names (other than "GMT+/-n" and
"UTC+/-n", which are treated as literal offsets) are parsed
using the timezones[] table defined just above the
do_set_timezone() routine itself, which (hopefully)
includes most common time zones; the table can of course be
modified to include other time zones as particular networks
desire. Once the the time zone has been set, a message is sent to
the calling client giving the current time in the resulting time
zone; however, this is tricky if the calling client is a Services
administrator changing the setting for another nickname, because
strftime_lang() always uses the time zone setting of the
nickname used to select the language. To get around this, the
routine determines the difference between the calling nickname's
time zone and the target nickname's time zone, adjusting the
timestamp passed to strftime_lang() by that amount
(multiplied by 60, since the timezone field is specified
in minutes). Incidentally, support for "daylight saving time"
as used in some countries was deliberately omitted, partly due to
the difficulty of supporting the various systems used in different
countries, and partly because the details of such systems are
highly dependent upon each country's political landscape and can
change at any time (witness the abrupt extension to DST proposed,
and eventually implemented, in the United States of America in
2006).
Back to top
7-3-1-4. NickServ utility routines
Most of the utility routines used by NickServ are collected in the file
util.c. This file has two functions: aside from providing utility
functions to NickServ itself (several of which are exported for use by
other modules), it can also be #include'd in an external source
file to provide definitions of the four routines new_nickinfo(),
free_nickinfo(), new_nickgroupinfo(), and
free_nickgroupinfo(), so that such files do not have to define
similar routines themselves. This latter mode is activated by defining
the STANDALONE_NICKSERV preprocessor symbol, as documented in the
comments at the top of util.c. In this case, only the four
routines mentioned above are defined, with the rest of the file commented
out using #ifndef; additionally, the new_nickgroupinfo()
routine does not check for the presence of the nickname group IDs it
generates, as it cannot assume that get_nickgroupinfo() is
available.
With respect to its primary use as part of NickServ, util.c
defines the following routines:
- NickInfo *new_nickinfo()
- Returns a pointer to a newly-allocated and initialized
NickInfo structure. (For creating a new record in the
database, makenick() is preferred; see below.)
- void free_nickinfo(NickInfo *ni)
- Frees the given NickInfo structure and all associated
data. (See delnick() below for removing a nickname
record from the database.)
- NickGroupInfo *new_nickgroupinfo(const char *seed)
- Returns a pointer to a newly-allocated and initialized
NickGroupInfo structure. If seed is not
NULL, then it is used to generate an initial ID value for
the nickname group; if that ID value is used, new values are
randomly generated until an unused one is found. (If the code
loops NEWNICKGROUP_TRIES times without finding an unused
value, an error is returned; assuming a good random number
generator, the default value of 1000 should ensure success on
typical databases. NEWNICKGROUP_TRIES is defined in
ns-local.h.) If seed is NULL,
then the new nickname group's ID is left at zero.
- void free_nickgroupinfo(NickGroupInfo *ngi)
- Frees the given NickGroupInfo structure and all associated
data. (See delgroup() below for removing a nickname group
and all its nicknames from the database.)
- NickGroupInfo *_get_ngi(NickInfo *ni,
const char *file, int line)
NickGroupInfo *_get_ngi_id(uint32 id,
const char *file, int line)
- Implement the get_ngi() and get_ngi_id() macros,
respectively. file and line are
the source file and line from which the function was called, and
are filled in by the corresponding macro with __FILE__ and
__LINE__.
- int has_identified_nick(const User *u, uint32 group)
- Returns whether the given client has identified for the nickname
group indicated by group.
- int reglink_check(User *u, const char *nick,
char *password, char *email)
- Calls the "REGISTER/LINK check" callback and returns its
result. (A utility function is used rather than directly calling
call_callback_4() because the nickserv/link
module needs to make use of the callback as well, and the module
system does not allow one module to call another's callbacks (which
would be bad design in any case).
- void update_userinfo(const User *u)
- Updates the user information for the client's nickname. The
NickInfo fields last_usermask,
last_realmask, and last_realname are set from
the corresponding fields of the User structure, and the
last_seen field is set to the current time.
u->ni is assumed to be non-NULL.
- int validate_user(User *u)
- Sets the ni and ngi fields of the User
structure to point to the NickInfo and associated
NickGroupInfo, if any, for the client's nickname (if an
error occurs looking up the nickname group, u->ni
is set to NULL and u->ngi is set to
NICKGROUP_INVALID); then compares the client's information
with the nickname data and determines what level of access for the
nickname should be granted to the client. Returns 1 if the client
is granted either NA_IDENTIFIED or NA_RECOGNIZED
access, otherwise zero.
This routine, along with the REGISTER command handler,
is one of the two "points of entry" into Services, and as such is a
critical point for Services security. This is particularly
relevant for the section of code conditionally granting full
(NA_IDENTIFIED) nickname access; while
has_identified_nick(), mentioned above, operates purely on
data which has been seen since Services started (specifically, the
list of nicknames the client is known to have become identified
for, maintained by set_identified()) and is comparatively
safe, the second check, which matches the servicestamp, username,
and hostname of the last client to identify with those of the
current client, needs special attention to ensure that it does not
allow clients to gain improper access. As noted in the comments in
that section of code, the servicestamp provides a fairly high level
of protection on servers which support it natively, while that
level is reduced for servers which do not (such servers are rare
nowadays). The entire section of code can be disabled with the
NoSplitRecovery configuration option for added security.
If the client is determined not to have identified for the
nickname previously, the routine continues, determining whether to
give NA_RECOGNIZED access. "Recognized" status is only
implemented by access lists (see section 7-3-2),
and if the corresponding module is not loaded, the
NA_RECOGNIZED flag will never be set on any nickname,
except when set along with NA_IDENTIFIED. Likewise, if a
nickname's NF_SECURE flag is set, then
NA_RECOGNIZED will not be set (and zero will be returned
from the routine) even if the client is in fact recognized.
If the client is not identified or recognized for the nickname,
validate_user() checks whether the client should be
killed or nick-changed, setting an appropriate timeout or calling
the collide routines (see section 7-3-1-5
below) depending on the nickname group settings. However, if the
client was recognized (which will only be true if the nickname has
the SECURE option set and thus NA_RECOGNIZED was
not set), the kill checks are not performed, allowing the client to
identify at its leisure.
Finally, the routine checks the nickname's expiration time, and
if it is due to expire "soon" (as defined by the
NSExpireWarning configuration option), a warning notice is
sent to the client.
- void cancel_user(User *u)
- Updates a client's nickname data when the client stops using the
nickname. The last_seen field is updated if the client
was either identified or recognized; the authstat field
is cleared along with temporary status flags in the status
field; an enforcer is introduced if the client was killed or
nick-changed (see section 7-3-1-5); the
cancel user" callback is called; and any active nick
collide timeouts are removed. The ni and ngi
fields of the client's User structure are also reset to
NULL.
- void set_identified(User *u)
- Marks the given client as having identified for the nickname it is
currently using. In addition to setting the authentication status
to NA_IDENTIFIED | NA_RECOGNIZED and updating the
nickname's id_stamp field, the routine adds the nickname
group ID to the list of nickname groups for which the client has
identified, stored in the User structure and checked by
has_identified_nick().
- NickInfo *makenick(const char *nick,
NickGroupInfo **nickgroup_ret)
- Creates a new NickInfo record with the given nickname,
adds it to the database, and returns a pointer to the new record.
If nickgroup_ret is not NULL, then a new
NickGroupInfo record is also created for the nickname,
the nickname's nickgroup field is set accordingly, and a
pointer to the NickGroupInfo record (which is also added
to the database) is stored in the variable pointed to by
nickgroup_ret. Returns NULL on error.
- int delnick(NickInfo *ni)
- Removes the given nickname from the database and frees all
resources used by the NickInfo structure. If the nickname
was the last of its group, then the nickname group is deleted as
well. Returns nonzero on success, zero on error.
- int delgroup(NickGroupInfo *ngi)
- Removes the given nickname group from the database, along with all
associated nicknames. Returns nonzero on success, zero on error.
- int drop_nickgroup(NickGroupInfo *ngi,
const User *u, const char *dropemail)
- Removes the given nickname group from the database, like
delgroup(), but first log information about the nicknames
to be deleted. u is the User structure
for the client that sent the command resulting in the deletion.
dropemail should be:
- NULL if the call is because of a DROP command
from the nickname owner;
- PTR_INVALID if the call is because of a DROPNICK
command from a Services administrator;
- for a DROPEMAIL command, the parameter (address
wildcard) used with the command.
- void suspend_nick(NickGroupInfo *ngi,
const char *reason, const char *who,
const time_t expires)
- Suspends the given nickname group, copying the parameters
reason, who, and
expires into the suspension data fields. (If
expires is zero, then the suspension will not
expire.)
- void unsuspend_nick(NickGroupInfo *ngi, int set_time)
- Cancels the suspension on the given nickname group. If
set_time is nonzero, the last-seen time of each
nickname in the group will be updated according to
NSSuspendGrace to prevent the nickname from expiring for
that length of time (if NSSuspendGrace or NSExpire
are not set, or if the nickname already has enough time before
expiration, the last-seen time will not be changed).
- int nick_check_password(User *u, NickInfo *ni,
const char *password, const char *command,
int failure_msg)
- Performs a password check for a nickname as part of a NickServ
command. If the password is incorrect or an error occurs when
checking, a notice will be sent to the client; a WALLOPS
will also be sent for repeated bad password attempts on the same
nickname. u is the User structure for
the client that issued the command; ni is the
NickInfo structure for the nickname whose password is
being checked; password is the password given by
the client, command is the name of the command
being executed; and failure_msg is the index of the
message (language string) to be sent if an error occurs when
checking the password.
- int count_nicks_with_email(const char *email)
- Counts and returns the number of registered nicknames with the
given E-mail address. If a nickname has the given address but it
is awaiting mail authentication, the value returned is negative;
for example, if there are five nicknames using a given address but
the address is not authenticated, -5 would be returned. Note that
this function must scan through the entire nickname database, so
care should be taken not to call it too frequently.
util.c also defines initialization and cleanup routines,
init_util() and exit_util(), which take care of
registering and unregistering the callbacks used by various utility
functions. The routines are called as part of the nickserv/main
module iniitialization and cleanup.
Back to top
7-3-1-5. Nickname colliding
In the general sense, a "nickname collision" is what happens when a
client on an IRC network attempts to use a nickname that is already in use
by another client. The original (RFC 1459) solution to this was to kill
both clients, but with the advent of timestamps, most modern servers only
kill one or the other depending on the timestamps of the two colliding
clients. Early versions of Services took advantage of this behavior to
implement kill protection: by introducing an "enforcer" pseudoclient with
an appropriate timestamp, the old client would be killed and would not be
able to reconnect with the same nickname.
With respect to Services, then, "nickname colliding" is the act of
forcing a client to stop using a particular nickname. While the nickname
collision method itself has been abandoned, both to avoid depending on
particular collision semantics and to provide a more meaningful disconnect
message to the client ("Nick kill enforced" rather than an arbitrary server
collision message), and although many modern IRC servers allow Services to
forcibly change a client's nickname without going as far as disconnecting
the client altogether, the term "colliding" is still used to refer to this
set of actions.
Nickname colliding functionality is provided by the file
collide.c. This file provides two methods of colliding nicknames:
directly, or via timeouts. The nickname colliding code also has its own
initialization and cleanup functions (init_collide() and
exit_collide()), which are called from the nickserv/main
module initialization and cleanup routines.
The following routines are used when colliding nicknames directly:
- void collide_nick(NickInfo *ni, int from_timeout)
- Collides the given nickname, either killing the client using the
nickname or forcibly changing the client's nickname to a "guest"
nickname depending on configuration settings.
from_timeout is used internally with collide
timeouts, and should always be zero when called externally. This
routine automatically calls introduce_enforcer() after
the client has been killed or nick-changed (in the case of a
forced nickname change, the enforcer is introduced by
cancel_user() upon receipt of the NICK message
indicating that the client's nickname has been changed). Any
pending nickname collide or "433" timeouts (see below) on the
nickname are cancelled by thie routine.
- void introduce_enforcer(NickInfo *ni)
- Introduces an enforcer pseudoclient for the given nickname, to
prevent other clients from using the nickname. This routine
automatically adds a timeout to call release_nick() after
NSReleaseTimeout seconds have passed.
- void release_nick(NickInfo *ni, int from_timeout)
- Removes the enforcer pseudoclient for the given nickname, allowing
other clients to use it again. As with collide_nick(),
from_timeout is used internally with collide
timeouts, and should always be zero when called externally. Any
pending release timeouts on the nickname are cancelled by this
routine.
Callers can also establish timeouts to collide or release a nick after
a certain time. To avoid each caller having to include its own timeout
handlers, collide.c provides two wrapper routines around the
generic timeout functions:
- void add_ns_timeout(NickInfo *ni, int type,
time_t delay)
- Adds a timeout of the given type on the given nickname to occur in
delay seconds. type can be any of
the following:
- TO_COLLIDE: A timeout for colliding a nickname
(collide_nick() will be called).
- TO_RELEASE: A timeout for releasing the hold on
a nickname (release_nick() will be called).
- TO_SEND_433: A timeout for sending the client
using the nickname a "433" error message. (433 is the code
for the ERR_NICKCOLLISION reply to a NICK
message, and will cause most interactive client software to
request a new nickname from the user. However, some server
software has been known to disallow servers from sending
433 replies to remote clients.)
- void rem_ns_timeout(NickInfo *ni, int type,
int del_to)
- Removes any timeout of the given type on the given nickname;
ni can be NULL to cause timeouts on all
nicknames (of the given type) to be removed. type
can be any of the type values used with add_ns_timeout(),
or -1 to remove timeouts of all types. del_to
should always be nonzero when called externally (the parameter is
used for calling from within a timeout function, where it is not
necessary to delete the Timeout structure as well).
Each of the three timeout types has its own timeout function:
timeout_collide(), timeout_release(), and
timeout_send_433(). The timeout functions check for any change
in status (such as identification for the nickname) before performing
their respective functions.
Back to top
7-3-2. Nickname access lists
Nickname access lists are managed by the nickserv/access
module, defined in access.c. The module is quite simple,
consisting of a database table, two callback functions, and a NickServ
command (ACCESS). For simplicity, the module (along with other
NickServ submodules) assumes the presence of the nickserv/main
module rather than explicitly importing every required NickServ symbol,
although the module's initialization does look up the
nickserv/main module handle for use in adding the requisite
callbacks and the ACCESS command.
One unusual feature is the use of a static buffer for reading and
writing database records. Since the access list itself is stored in the
corresponding nickname group's NickGroupInfo structure as an
array, the entries must be extracted and made available to the database
subsystem in an independent (normalized) format. This is done by using a
{nickgroup-ID,access-mask} record format, and
providing a single record buffer. When loading data from persistent
storage, the table's newrec() function returns a pointer to this
buffer, taking advantage of the fact that only one record is loaded at a
time (see section 6-2-1); the insert()
routine then looks up the nickname group stored in the buffer and appends
the given access mask to that nickname group's access list, while the
freerec() routine frees the access mask string. When saving data,
the first() and next() routines call
first_nickgroupinfo() and next_nickgroupinfo() in turn,
looping through each access mask of each nickname group.
Ideally, the access lists would be stored in memory in the same fashion,
as a distinct table using the nickname group ID as a key. However, since
the current database implementation does not provide an efficient way to
look up records matching arbitrary criteria (like a SELECT
statement in SQL), and since problems would ensue when trying to save the
data using the (deprecated, but still available) database/version4
module, the in-memory data structures were left as is. See the comments in
the autojoin.c source file for a more complete description.
Other than this, there is little of note in the module; the
do_access() command handler simply adds or removes the requested
mask to or from the access list, and the callback functions take care of
setting an initial access mask on registration and determining whether the
user should be treated as recognized by validate_user().
Back to top
7-3-3. Nickname auto-join lists
Nickname auto-join lists are managed by the nickserv/autojoin
module, defined in autojoin.c. Aside from the details of its
operation, this module is nearly identical to the nickserv/access
module, including the hack used for database loading and saving; see the
discussion of that module above (section 7-3-2) for
details.
Back to top
7-3-4. Linking and nickname groups
The distinction between "nicknames" and "nickname groups" has been made
several times above. Ordinarily, this is only of importance as far as
which structure is accessed (NickInfo or NickGroupInfo);
however, the nickserv/link module, defined in link.c,
allows nicknames to be linked together by assigning the same nickname group
to both nicknames. This results in all information in the
NickGroupInfo structure being shared among all linked nicknames,
with only the data in the NickInfo structure kept separately for
each nickname.
The nickserv/link includes three commands: LINK,
UNLINK, and LISTLINKS, as well as one additional
SET option, SET MAINNICK (linked in through NickServ's
"SET" callback). Of these, LISTLINKS and SET
MAINNICK are straightforward: do_listlinks() simply echoes
the contents of the nicks[] array for the calling client's
nickname group (or the specified nickname's group for Services
operators), and do_set_mainnick() modifies the nickname
group's mainnick field based on the given nickname.
When the LINK command is given to link a new nickname to the
caller's nickname group, do_link() first ensures that the new
nickname is not already in use and that creating the link would not cause
the caller's total number of nicknames to exceed the NSRegEmailMax
limit, if set. (Note that LINK does not abort if the mail
address is not authenticated, simply checking the absolute value of the
return from count_nicks_with_email() against the limit; creating
the link does not in itself grant any additional privileges to the user,
and can at most be used to "hide" from other users while maintaining
current privileges.) If these checks pass, a new NickInfo
structure is created, passing NULL as the
nickgroup_ret parameter to makenick() to indicate
that a new nickname group is not required; updates the NickInfo
structure with the calling user's data; stores the nickname group ID in the
new nickname's nickgroup field; and appends the new nickname to
the nickname group's nicks[] array.
UNLINK acts similarly to the Services administrator command
DROPNICK for a single nickname in a group. However, since
UNLINK is not limited to Services administrators, care must be
taken that an unprivileged client is not allowed to delete nicknames from
other nickname groups; this check is made by ensuring that (1) the target
nickname has a valid associated NickGroupInfo structure and (2)
the nickname group IDs of the two nickname groups are equal, and
disallowing the command otherwise if the FORCE option is not
given. (The check is made on the FORCE option, disallowed for
unprivileged clients, rather than on the client's privilege level in order
to prevent accidental deletion of others' nicknames by Services
administrators.) If the command is allowed, the routine then deletes the
nickname by calling delnick(); if the nickname group's main
nickname is the one being unlinked, delnick() automatically
adjusts the mainnick field to the next nickname in the
nicks[] array (or the previous nickname, if the deleted nickname
was the last one in the array).
Back to top
7-3-5. E-mail address authentication
While it has long been possible to associate an E-mail address with a
registered nickname, there was traditionally no way to ensure that the
address given was in fact a valid one belonging to the nickname owner.
Since Services 5.0, such functionality has been provided by the
nickserv/mail-auth module, defined in mail-auth.c. As
described in the user's manual, E-mail address authentication works by
assigning a random "authentication code" to the nickname, then sending an
E-mail message to the registered address containing that code; the owner is
not allowed to identify to the nickname until the code has been entered,
ensuring that the address is one which the owner has (or at least had, at
the time the message was sent) access to.
Internally, this processing is managed with the authcode,
authset, and authreason fields of the
NickGroupInfo structure. When an event occurs requiring E-mail
address authentication, the module generates a random 9-digit numeric code
(100000000 through 999999999 inclusive—codes with leading zeroes are
not used in order to avoid confusion), stores the code in the nickname
group's authcode field, and calls the mail subsystem's
sendmail() routine to send a message to the nickname group's
registered E-mail address (see section 8-3 for
information about how mail is sent). Additionally, the current timestamp
is stored in the authset field, and the reason for setting the
code (one of the NICKAUTH_* constants) is stored in the
authreason field. (In early versions of the module, the reason
was stored in two bits of the authentication code; this method was later
rejected, however, as being too kludgey and inflexible as well as leaking
information.) The presence of a nonzero value in the authcode
field indicates that the nickname group is awaiting authentication, and a
nickname identification callback function ensures that clients are not
allowed to identify for such nicknames (nor does validate_user()
allow automatic identification if an authentication code is present), thus
effectively preventing their use. By issuing an AUTH command with the
correct code, a nickname owner can clear the nickname group's
authcode field, allowing identification to the nickname(s) once
more.
The module begins with several utility functions:
- void make_auth(NickGroupInfo *ngi, int16 reason)
- Generates a random authentication code, and sets the nickname
group's authcode, authset, and authreason
fields appropriately. The reason parameter is
copied directly into the authreason field, without checks
on its value.
- void clear_auth(NickGroupInfo *ngi)
- Clears the given nickname group's authentication code (if any), as
well as all related nickname group data fields (including the
previous E-mail address).
- int send_auth(User *u, NickGroupInfo *ngi,
const char *nick, int what)
- Sends an E-mail to the given nickname group's owner containing the
nickname's current authentication code. u is the
User structure for the client whose command caused the
message to be sent; nick is the specific nickname
for which the command was issued; and what is
one of the IS_* constants defined for the routine
(internally a language string index for the mail body or -1 for the
special case of SETAUTH). The routine itself is defined
as send_auth_, taking a line parameter
indicating where in the source the routine was called from; this is
filled in automatically by the send_auth() macro. In
order to inform the calling client of the success or failure of
sending the message, send_auth() creates a
sendauth_data structure for each message sent, used in the
two routines listed below.
- void send_auth_callback(int status, void *data)
- The callback function used for sendmail() when called from
send_auth(). This routine uses the sendauth_data
structure for the sent message (passed as the data
parameter) to send a reply to the client that issued the original
command, and also to clear the nickname group's
last_sendauth field if the command used was
SENDAUTH and the message could not be sent. The structure
is then removed from the global list and freed.
- int sendauth_userdel(User *user,
const char *reason, int is_kill)
- Used as a callback function for the core's "user delete"
callback. Iterates through the sendauth_data list,
clearing the User pointer of any entries for the user
being removed; this causes send_auth_callback() to skip
sending a reply when the mail sending completes.
These routines are followed by the command handlers for the commands
supported by the module: AUTH, SENDAUTH, REAUTH,
RESTOREMAIL, and the Services administrator commands
SETAUTH, GETAUTH, and CLEARAUTH. Of these, the
only fairly complex one is the AUTH handler, do_auth(),
as it must watch for attempts to guess the authentication code. (Invalid
AUTH commands are treated the same as bad passwords to
IDENTIFY or other commands, by calling bad_password() and
incrementing the nickname group's bad_auths field, which itself
can generate a warning via WALLOPS.)
Finally, the module includes four callback functions. Two of these,
do_registered() and do_set_email(), hook into the
NickServ callbacks for the REGISTER and SET EMAIL
commands, respectively, dropping the client's identified status and
generating and sending an authentication code. (Changes to the E-mail
address made by Services administrators are not subject to authentication,
however.) These are followed by the IDENTIFY command callback
function do_identify(), which disallows IDENTIFY for
nicknames with pending authentication codes, and the expiration check
callback function do_check_expire(), which drops a
newly-registered nickname after NSNoAuthExpire seconds (if set) if
not authenticated, and also clears any pending REAUTH after the
same amount of time (the user can subsequently use a second REAUTH
if necessary).
Since the ability to send E-mail is essential for this module to work,
the init_module() function checks for the presence of the
mail/main module, refusing to load if it is not available.
Back to top
7-4. ChanServ
ChanServ is the most complex of the standard Services pseudoclients,
owing to the variety of operations that can be performed on channels.
There is no easy way to split those operations up into separate modules
(except by individual command, an avenue which has not been pursued), and
as a result, the core ChanServ module is itself the largest module in
Services.
As with NickServ, the core ChanServ module, chanserv/main, is
split up over several source files. These are discussed in
section 7-4-1 and its subsections, with the exception
of the access.c source file, which is discussed in
section 7-4-2 along with the access list manipulation
submodules, chanserv/access-levels and
chanserv/access-xop.
Back to top
7-4-1. ChanServ core functionality
The core ChanServ module, chanserv/main, is built from several
source files, along much the same lines as the core NickServ module:
main.c, containing the central module code and most command
handlers; access.c, for handling channel access lists;
check.c, for checking the status of a channel against registered
data and making appropriate changes; set.c, implementing the
SET command; and util.c, defining various ChanServ
utility functions.
Back to top
7-4-1-1. Channel data structures
As with NickServ, ChanServ splits its declarations into two header
files: chanserv.h, containing structures and routine declarations
intended to be exported to other modules, and cs-local.h, for
internal use by ChanServ only. (There is also a separate header file,
access.h, specifically for channel access list definitions; this
is discussed in section 7-4-2-1.)
The channel data structure, ChannelInfo, is naturally exported.
As ChanServ does not have the concept of "links" or "channel groups" that
NickServ uses with nicknames, all data for a channel is stored in this
single structure. ChannelInfo does, however, include three
substructures, defined before it in chanserv.h:
- ChanAccess
- Contains the data for an entry on a channel's access list. Access
list entries are stored by nickname group ID, rather than by
nickname, both to optimize checking an access list for a particular
client and to eliminate the possibility of two or more entries
matching a single client. (This is why only registered nicknames
are allowed on channel access lists, and why channel access list
entries are always displayed using the nickname group's main
nickname, regardless of the nickname actually added to the list.)
Rather than resizing the array every time a change is made to the
list, deleted entries are left in memory with a nickname group ID
of zero, and subsequent adds reuse these entries before attempting
to expand the array. The channel field is used to link
the record with its associated channel when loading and saving
data. There are also a number of access-level related constants
declared below this structure, such as the maximum and minimum
access levels and the equivalent access levels for the XOP
commands.
- AutoKick
- Contains the data for an entry on a channel's autokick list. As
with access list entries, deleted entries are left in the list
rather than resizing the array (a NULL value for the mask
string indicates an unused entry), and the channel field
is used when loading and saving data to link the record with its
associated channel.
- ModeLock
- Contains a channel's mode lock data, including the set of modes
locked on and off along with parameters for modes that require
them; with the exception of the presence of two mode sets rather
than one (modes can be locked on, locked off, or neither), the
structure's contents are the same as the fields used in channel
data structures (Channel records) to record the channel's
current mode. The mode sets are normally maintained as bitmasks,
but when used in the convert-db tool, they are defined as
strings instead to allow lossless conversion to XML without needing
to know the specific set of modes supported by the program that
created the database. Unlike the ChanAccess and
AutoKick structures, there is only one ModeLock
structure per channel, and the data in the structure is saved along
with the channel record itself, so there is no need for a separate
channel field.
chanserv.h also defines constants for each of the channel
privilege levels (indices into the levels[] array of the
ChannelInfo structure). As noted in the comment above the list
of definitions, changing the values of any of the constants will cause
malfunctions when using the database/version4 module, since that
module simply reads in the list of levels as a block. (This is why the
index 18, formerly used for CA_AUTOOWNER, is unused—to avoid
problems with databases in which that index is used.)
The ChannelInfo structure itself contains the following
fields:
- ChannelInfo *next, *prev
- Used to link records together in the internal hash table.
- int usecount
- The record's usage count (number of gets minus number of
puts).
- char name[CHANMAX]
- The channel's name. Capitalization is as used when the channel was
registered, and does not change due to later actions. The buffer
size, CHANMAX, is defined in the global header file
defs.h.
- uint32 founder
uint32 successor
- The nickname group ID of the channel's founder and successor,
respectively. If the channel does not have a successor set, the
successor field will be zero.
- Password founderpass
- The founder password for the channel.
- char *desc
- The channel's description, as specified at registration time or
with a later SET DESC command.
- char *url
- The URL associated with the channel, as set with the SET
URL command. NULL if no URL has been set.
- char *email
- The E-mail address associated with the channel, as set with the
SET EMAIL command. NULL if no E-mail address
has been set.
- char *entry_message
- The channel's entry message (the message sent as a NOTICE
to clients entering the channel), as set with the SET
ENTRYMSG command. NULL if no entry message has been
set.
- time_t time_registered
- The timestamp at which the channel was registered.
- time_t last_used
- The timestamp at which the channel was last used (see
sec