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 super-user; 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
super-user (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 super-user), 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 super-user password in the log file. (OperServ
has no way to detect if one of these commands is misspelled, so for
example, a mistaken 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
super-user, Services administrator, and Services operator privileges
respectively (any client with a higher privilege level is treated as
having all lower privilege levels as well). The 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).
(Implementation note: As noted in section 7-1,
this field currently serves no actual purpose.)
- 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).
(Implementation note: As noted in section 7-1,
this field currently serves no actual purpose.)
- 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 super-user 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 super-user.
- 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).
(Implementation note: As noted in section 7-1,
this field currently serves no actual purpose.)
- char name[CHANMAX]
- The channel's name. Capitalization is as used when the channel was
registered, and does not change due to later actions. The buffer
size, CHANMAX, is defined in the global header file
defs.h.
- uint32 founder
uint32 successor
- The nickname group ID of the channel's founder and successor,
respectively. If the channel does not have a successor set, the
successor field will be zero.
- Password founderpass
- The founder password for the channel.
- char *desc
- The channel's description, as specified at registration time or
with a later SET DESC command.
- char *url
- The URL associated with the channel, as set with the SET
URL command. NULL if no URL has been set.
- char *email
- The E-mail address associated with the channel, as set with the
SET EMAIL command. NULL if no E-mail address
has been set.
- char *entry_message
- The channel's entry message (the message sent as a NOTICE
to clients entering the channel), as set with the SET
ENTRYMSG command. NULL if no entry message has been
set.
- time_t time_registered
- The timestamp at which the channel was registered.
- time_t last_used
- The timestamp at which the channel was last used (see
section 3-2-3 of the user's manual for
details of how the last-used time is set).
- char *last_topic
char last_topic_setter[NICKMAX]
time_t last_topic_time
- The topic most recently set on the channel, along with the nickname
of the client that set the topic and the timestamp at which it was
set. If no topic has been set on the channel, last_topic
will be NULL, and the other two fields will be
undefined.
- int32 flags
- A bitmask containing zero or more of the following channel flags:
- CF_KEEPTOPIC: ChanServ should restore the
channel's previous topic each time the channel is created
on the IRC network (SET KEEPTOPIC).
- CF_SECUREOPS: Clients without a positive access
level on the channel should be prevented from getting ops
(SET SECUREOPS).
- CF_PRIVATE: The channel is hidden from the
LIST command output, except when used by Services
administrators (SET PRIVATE).
- CF_TOPICLOCK: ChanServ should prevent the topic
from being changed except by the SET TOPIC command
(SET TOPICLOCK).
- CF_RESTRICTED: ChanServ should automatically
kick and ban any clients without a positive access level
that attempt to join the channel (SET
RESTRICTED).
- CF_LEAVEOPS: ChanServ should not remove
server-generated ops for the first client to join a
channel, even if that client would not normally be
auto-opped (SET LEAVEOPS).
- CF_SECURE: When checking a client's channel
access level, require the client to have identified to
NickServ regardless of the setting of the nickname's
SECURE option (SET SECURE).
- CF_VERBOTEN: The channel is forbidden
(FORBID).
- CF_NOEXPIRE: The channel does not expire
(SET NOEXPIRE).
- CF_OPNOTICE: ChanServ should send a notice to
the channel whenever any of the OP, VOICE,
or related commands are used (SET OPNOTICE).
- CF_ENFORCE: ChanServ should prevent clients
from removing automatically-set channel user modes such as
auto-ops (SET ENFORCE).
- CF_HIDE_EMAIL: The channel's E-mail address is
hidden from the INFO command output, except when
used by Services administrators (SET HIDE
EMAIL).
- CF_HIDE_TOPIC: The channel's current or last
topic is hidden from the INFO command output,
except when used by Services administrators (SET HIDE
TOPIC).
- CF_HIDE_MLOCK: The channel's mode lock is
hidden from the INFO command output, except when
used by Services administrators (SET HIDE
MLOCK).
- CF_SUSPENDED: The channel is suspended
(SUSPEND).
- CF_MEMO_RESTRICTED: Only users with the
MEMO privilege are permitted to send memos to the
channel (SET MEMO-RESTRICTED).
The flag values 0x00000100 and 0x00000400 are unused to avoid
difficulties with databases from earlier versions of Services
which used these values.
- char suspend_who[NICKMAX]
char *suspend_reason
time_t suspend_time
time_t suspend_expires
- Suspension data for the channel, if the CF_SUSPENDED flag
is set. Used in the same fashion as the same-named fields in the
NickGroupInfo structure (see section
7-3-1-1).
- int16 levels[CA_SIZE]
- The channel access levels corresponding to each of the channel
privileges (CA_INVITE, CA_AKICK, and so on).
A value of ACCLEV_DEFAULT indicates that the corresponding
privilege should use the default access level defined in
access.c; a value of ACCLEV_INVALID indicates
that the corresponding privilege is disabled entirely, except with
respect to the channel founder.
- ChanAccess *access
int16 access_count
- A variable-length array containing the channel's access list.
- AutoKick *akick
int16 akick_count
- A variable-length array containing the channel's autokick list.
- ModeLock mlock
- The channel's mode lock data.
- Channel *c
- Not saved to persistent storage. Points to the
Channel structure for the channel if it is currently in
use.
- int bad_passwords
- Not saved to persistent storage. The number of times an
incorrect password has been given for a channel command (such as
IDENTIFY) since the last correct password. Used to warn
about attempts to crack the password.
The cs-local.h header file includes three definitions used
internally by ChanServ:
- MAX_MLOCK_PARAMS (constant)
- Sets the maximum number of command parameters that the SET
MLOCK command will process. This constant is used to avoid
the overhead of dynamically allocating an argument array for every
invocation of the command; any parameters passed beyond this limit
will be silently ignored. The default value, 256, is more
parameters than are can be passed in an RFC-standard 512-byte line
(even if every parameter is one character, subtracting out the
"SET MLOCK" leaves space for only 251 parameters, not
considering the trailing CR/LF and other IRC protocol overhead).
- ChanOpt (structure)
- Used to define channel option data, for use by the SET and
INFO commands. The structure has the following fields:
- name: The name of the option, as a string.
- flag: The corresponding ChannelInfo.flags
flag value.
- namestr: The option's descriptive name (for use
in INFO output), as a language string index. If
-1, the option will not be included in INFO
output.
- onstr, offstr: Response messages for
turning the option on and off via SET, as language
string indices.
- syntaxstr: The syntax message for setting the
option, as a language string index.
- RET_* (constants)
- Return values from access list modification routines. See
section 7-4-2-1 for details.
One other point of note in cs-local.h is the
renaming of several functions in set.c and util.c using
#define directives, such as renaming init_set() to
init_set_cs(). This is to avoid conflicts with NickServ, which
includes functions of the same name in its own set.c and
util.c. While the functions involved are not used outside of
ChanServ, they must be declared external for the multi-file link to
succeed; as a result, if the functions are not renamed, linking the final
program when using static modules will result in a symbol name clash when
the symbols from both modules are processed. This problem does not occur
when using dynamic modules, since the presence of conflicting symbols in
dynamic modules is not itself an error, and no attempt is made to reference
either set of symbols from any other module (the references within the
respective modules are resolved at module link time).
Back to top
7-4-1-2. Overall module structure
The main source file for ChanServ, main.c, follows the same
pattern as its NickServ counterpart; see section
7-3-1-2 for a more extensive description.
One of the first things defined in main.c is the
chanopts[] table, an array of ChanOpt structures (see
above) describing on/off options available on channels from a user's
perspective. This table is used primarily for the SET command
(thus only flags which have a corresponding SET command are
listed), and secondarily for outputting the channel's option set in
response to the INFO command. Note that the three HIDE
options are not listed here, as they are handled specially for the
SET command and not listed in the INFO output.
In addition to the standard command list in the cmds[] array,
two separate arrays are defined, for the HALFOP/DEHALFOP
and PROTECT/DEPROTECT command pairs. These commands can
only be used if the IRC server protocol supports the corresponding feature,
so the module initialization routine checks the protocol's feature flags
and conditionally registers these two pairs of commands.
ChanServ includes a significant number of callback functions for various
IRC events: in addition to watching for newly-created channels and users
joining channels, it must also monitor changes of status such as channel
modes and channel topics to ensure that they remain consistent with the
registered settings and to record changes, as well as take notice of
nickname-related events that can affect channels. Of these, the
do_nickgroup_delete() callback function, called when a nickname
group is deleted, is easily the most complex. Since channel founders must
be registered nicknames, the disappearance of a nickname group means that
any channels with that group as founder will no longer have a valid founder
group ID. If the channel has a successor set, the successor may be able to
assume foundership of the channel—but the code must be careful that
this does not cause the successor to exceed his registered channel limit,
or users could circumvent the limit by registering multiple nicknames,
setting one as founder and another as successor, and deliberately dropping
the founder nickname. In addition, if the channel was suspended, it is
changed to a forbidden channel to prevent users from getting around a
suspension by dropping and re-registering their nickname. Because of these
various potentialities, the callback function always logs any actions it
takes in response to the deletion of a nickname group.
Of the command routines, the basic commands (HELP,
REGISTER, IDENTIFY, DROP, DROPCHAN,
INFO, LIST) are very similar to their NickServ
counterparts, and are not discussed in detail here. The two
ChanServ-specific commands whose handlers are fairly complex are
AKICK and the OP/VOICE command set.
The AKICK command handler do_akick(), despite its
length (including a separate helper routine used with LIST and
VIEW), is not dissimilar to the OperServ autokill and S-line
commands, or the NickServ ACCESS and AJOIN handlers. The
only significant difference is the presence of the ENFORCE
subcommand; this is implemented by calling check_kick(), the
routine used to check whether newly-joined clients are allowed to join a
channel (see section 7-4-1-3) for each client on the
channel, causing clients which match an entry on the autokill list to be
kickbanned.
The OP and VOICE family of commands all perform a
common function—adding or removing a channel user mode—and for
this reason, all eight commands are handled by a single routine,
do_opvoice(), with handlers for each command that call the common
routine with the appropriate command name to indicate the mode of
operation. The data used by the common routine is stored in
opvoice_data[], an array of structures with the following
fields:
- cmd: The command name.
- add: Nonzero if the command adds a mode, zero if it
removes a mode.
- mode: The mode character added or removed.
- target_acc: A channel privilege index (CA_*
constant); if the target client is in this privilege class, the
command will be refused.
- success_msg: The language string index for the message
to be sent when the command succeeds.
- already_msg: The language string index for the message
to be sent when the client already has the mode added (or lacks the
mode removed) by the command.
- failure_msg: The language string index for the message
to be sent when the command is rejected.
When called, do_opvoice() first extracts the data for the
command from the opvoice_data[] table, and sets an additional
variable, target_nextacc, used for mode-removal commands to set an
upper bound on the target client access level check; this is used to, for
example, allow DEVOICE on an auto-op client (since the client can
just give themselves voice status again if necessary). The routine then
loops through all target nicknames given with the command; a
do/while loop is used so that if no nicknames are given at all,
the code will still be run once (in this case the client that gave the
command is used as the target). For each target client, the standard
permission and channel status checks are performed, and then the routine
determines whether to allow the command:
- If the target client is the client giving the command, the command is
allowed.
- If the command removes a mode (DEOP, DEVOICE, etc.)
and the channel does not have the ENFORCE option set, the
command is allowed.
- If a channel privilege check (target_acc) is not specified for
the command, the command is allowed.
- If the the target client is not in the privilege class specified for
the command's privilege check, the command is allowed.
- If an upper limit for the privilege check (target_nextacc) is
set for the command and the target client is in that privilege
class, the command is allowed.
- Otherwise, the command is refused.
Once the command has been allowed, the routine determines which mode
flags need to be set or cleared for the target client. (For extensibility,
the code allows for more than one flag to be set or cleared for a single
command, though the data table only allows one mode character.) If there
are no modes to be set or cleared, a notice to that effect is sent to the
caller; otherwise, the necessary mode changes are performed, a notice of
the mode change is sent to the channel if the OPNOTICE option is
enabled, and a success notice is sent to the caller. In addition, if the
command was an OP command, the channel's last-used time is updated
as for auto-ops.
Back to top
7-4-1-3. Channel status checking and modification
ChanServ's routines for checking and adjusting channel status are
located in the source file check.c. There are six routines
exported from this file; two, init_check() and exit_check(),
are initialization and cleanup routines called by the module initialization
and cleanup code, respectively. The remaining routines are:
- void check_modes(Channel *c)
- Checks the given channel's modes, making any changes necessary.
For registered channels, the "registered" mode (if any) is always
added, and other modes are set or cleared according to the mode
lock; for unregistered channels, the "registered" mode is always
cleared. This routine also checks for the "bouncy modes"
phenomenon in tandem with the channel MODE message
handle (see section 2-6-3).
- void check_chan_user_modes(const char *source,
struct c_userlist *u, Channel *c, int32 oldmodes)
- Checks the channel user modes of the given client (u)
on the given channel, making any changes necessary. The set of
"necessary" changes depends not only on the client's current modes,
but also on the source of the MODE message that caused the
change (passing an empty string for the source
parameter will cause such checks to be skipped).
oldmodes is the client's previous set of modes, or
-1 for a client joining a channel. The sequence of operations is
fairly complicated:
- If the channel is not registered (or forbidden),
no changes are made.
- If source is Services' server
name or the ChanServ or OperServ pseudoclient nickname,
no changes are made (under the assumption that anything
done by Services has been otherwise checked).
- If source is the client whose
modes are being checked, then no changes are made
unless the user is either not opped or is about to
be deopped, in which case the mode changes made by the
client are reversed. However, this check is not performed
for IRC operators (since some IRC servers allow operators
to set arbitrary modes regardless of chanop status), and on
servers supporting halfops, the mode change is not reversed
if the user has halfops and is only changing the halfop or
voice modes.
- If the mode change is the opping by a server of
the first client to join a channel and the channel does not
have the LEAVEOPS option set, the client's channel
access level is checked against the auto-op privilege
level. If the client has auto-op privileges, then the
channel's last-used time is updated as for ordinary auto-op
processing (see below); otherwise, a "channel is
registered" notice is sent to the client and the client is
deopped (in that order, so that a human user will see the
reason for the deop before the mode change itself).
- The "check_chan_user_modes" callback is
called, allowing protocol modules to handle modes not
recognized by the standard processing.
- The client's new modes, based on channel
privilege level, are calculated by calling
check_access_cumode() (see
section 7-4-2-1).
- If the client just joined the channel, the mode
change was done by a server, or the ENFORCE option
is set on the channel, all missing automatic modes are
added. (This has the effect of allowing automatic modes to
be removed from clients if ENFORCE is not set.)
In addition, if the mode change included a +o, the
channel's last-used time is updated.
- If the client is not an IRC operator, any
necessary mode removals are performed.
The mode changes in these last two steps are performed by a
helper routine, local_set_cumodes(), which in turn calls
set_cmode() for each mode in the given set.
- int check_kick(User *user, const char *chan,
int on_join)
- Checks whether a client is permitted to be on a channel; if so,
returns zero, otherwise kickbans the client and returns nonzero.
This routine is normally called when a client joins a channel,
before the actual join processing, but setting the
on_join parameter to zero allows this routine to be
called for clients already in the channel as well, such as for the
AKICK ENFORCE command. A client can be denied access to a
channel for any number of reasons, checked in the following order:
- If the channel name is the single character
"#"" and the CSForbidShortChannel
configuration option is set, the client is kickbanned.
- If the client is a Services administrator, the
client is allowed.
- If the "check_kick" callback returns 1,
the client is kickbanned; if it returns 2, the client is
allowed.
- If the client is an IRC operator, the client is
allowed.
- If the channel already exists with an
IRC-operators-only mode, the client is kickbanned.
(Ordinarily, the IRC server takes care of such processing,
but this code is included to handle desynchs and other
network problems.)
- If the channel is not registered, then the
client is kickbanned if the CSRegisteredOnly
configuration option is set, and allowed otherwise.
- If the channel is forbidden or suspended, the
client is kickbanned.
- If the channel's mode lock has an
IRC-operators-only mode set, the client is kickbanned.
- If the channel's mode lock includes a
registered-nicknames-only mode and the client's nickname is
not registered, the client is kickbanned. (However, this
check is skipped if the CSSkipModeRCeck
configuration option is set.)
- If the client's
nick!user@host string
matches an autokick mask, the client is kickbanned.
- If the client matches the NOJOIN
privilege on the channel, the client is kickbanned.
The client is also kickbanned if it would match the
NOJOIN privilege when identified to its nickname;
however, this check is skipped if less time than specified
in the CSRestrictDelay configuration option has
passed since Services startup.
- Otherwise, the client is allowed.
If the client is to be kickbanned, the routine first checks
whether kicking the client would cause the channel to become empty
(and thus be deleted, nullifying the effecet of any ban); if so, a
JOIN message is sent to the network to cause ChanServ to
join the channel, and a timeout for CSInhabit seconds is
added to cause ChanServ to leave the channel after that time.
Following this, the routine ensures that the ban mask is properly
formatted (containing a nickname as well as user and host), then
clears any ban exceptions matching the user and adds the ban mask
if it is not already present. Once the ban is present, the client
is then kicked from the channel, and removed from the internal data
structures if necessary.
Implementation note: As mentioned in
Appendix D of the user manual, when
ChanServ temporarily enters a channel for a kickban, it is not
added to the internal channel data; as a result, a subsequent
UNBAN or INVITE on the channel will return a
"channel does not exist" error. It would probably be better to
add ChanServ to the channel's client list like any ordinary
client.
- int check_topiclock(Channel *c, time_t topic_time)
- Called on a channel topic change (topic_time is the
timestamp associated with the topic change) to restore the topic to
its previous value if the channel's TOPICLOCK option is
set. Returns nonzero if the topic is changed by the routine,
otherwise zero.
Back to top
7-4-1-4. The SET and UNSET commands
The handlers for the SET and UNSET commands are
located in the set.c source file; as for NickServ, they function
like miniature versions of the main chanserv() routine. One
noteworthy difference is that the on/off options (other than the three
HIDE options) are all handled by a single routine,
do_set_boolean(); the main SET handler, do_set(),
looks up the option name in the chanopts[] table defined in
main.c (checking privileges for the NOEXPIRE option),
then calls do_set_boolean(), which uses the data from the
ChanOpt structure to set channel flags and send responses to the
calling client.
Other noteworthy option handlers are:
- do_set_founder()
do_set_successor()
- Both SET FOUNDER and SET SUCCESSOR follow the
same general pattern: the given nickname is looked up to retrieve
its nickname group ID, the ID is checked to ensure that both
founder and successor are not set to the same nickname group, and
an informational message is logged to record the change. The major
difference is that SET FOUNDER checks to ensure that the
new founder has not reached the channel registration limit, while
SET SUCCESSOR makes no such check. (Even if it did, the
check would only make sense at the time the SET SUCCESSOR
command was given, and would not reflect any future channel
registrations or drops by the successor. An alternative
possibility would be to have successor channels count against the
channel limit as well, as mentioned in section
11-1.)
- do_set_mlock()
- The SET MLOCK handler is fairly complex, as it must parse
the mode string and parameters to ensure that users cannot
inadvertently (or maliciously) cause an invalid MODE
message to be sent to the IRC network. The routine parses the mode
string character by character; if a mode is found that conflicts
with an earlier setting in the string, the later occurrence takes
precedence. Additionally, in order to simplify cleanup in case a
problem is found, the new mode lock is accumulated in a temporary
ModeLock structure, which is copied into the channel's
data when the routine successfully completes.
In order to support additional modes provided by particular IRC
protocols, do_set_mlock() defines a callback, "SET
MLOCK", which is called once for every mode in the mode
string; it is also called once after all modes have been processed,
to allow for a final validity check (for example, to check for
modes that require other modes to be set, as the Unreal protocol
module does).
Back to top
7-4-1-5. ChanServ utility routines
ChanServ's utility functions are defined in util.c. As with
NickServ, the preprocessor symbol STANDALONE_CHANSERV can be
defined before including util.c in another file; this causes the
routines new_channelinfo(), free_channelinfo(), and
reset_levels to be defined as static, and eliminates all
other code. new_channelinfo() and free_channelinfo() are
used to allocate and free resources for a ChannelInfo structure,
like their nickname counterparts; reset_levels() resets the
privilege levels for a channel (the levels[] array) to default
values.
Aside from these three functions (all exported from ChanServ),
util.c defines the following routines, along with the
initialization and cleanup routines init_util() and
exit_util():
- int check_channel_limit(const NickGroupInfo *ngi, int *max_ret)
- Compares the given nickname group's registered channel count with
the limit applied to that nickname, returning -1 if the limit has
yet to be reached, 0 if the limit has been reached, and 1 if the
limit has been exceeded (much like string comparison functions).
Also stores the registered channel limit in the variable pointed to
by max_ret if max_ret is not
NULL. This routine is exported.
- ChannelInfo *makechan(const char *chan)
- Creates a new ChannelInfo structure for the given channel
name, adds it to the database, and returns it.
- int delchan(ChannelInfo *ci)
- Removes the given channel from the database, returning nonzero on
success, zero on failure.
- void count_chan(ChannelInfo *ci)
- Updates the NickGroupInfo record for the given channel's
founder to indicate that that nickname group owns the channel,
incrementing the owned-channel count.
- void uncount_chan(ChannelInfo *ci)
- Removes the given channel from its founder's owned-channel list
and decrements the owned-channel count.
- int is_founder(const User *user, const ChannelInfo *ci)
- Returns whether the given user has founder access to the given
channel, whether due to being the actual channel founder or to
identifying for the channel with its founder password.
- int is_identified(const User *user, const ChannelInfo *ci)
- Returns whether the given user has identified for the channel with
its founder password. A subset of is_founder().
- void restore_topic(Channel *c)
- Restores the saved topic on a newly-created channel if the
channel is registered and its KEEPTOPIC option is set.
- void record_topic(ChannelInfo *ci, const char *topic, const char *setter, time_t topic_time)
- Records the given topic in the given channel's data structure.
- void suspend_channel(ChannelInfo *ci, const char *reason, const char *who, const time_t expires)
- Suspends the given channel, copying the parameters
reason, who, and
expires into the suspension data fields. (If
expires is zero, then the suspension will not
expire.)
- void unsuspend_channel(ChannelInfo *ci, int set_time)
- Cancels the suspension on the given channel. If
set_time is nonzero, the last-used time of the
channel will be updated according to CSSuspendGrace to
prevent the channel from expiring for that length of time (if
CSSuspendGrace or CSExpire are not set, or if the
channel already has enough time before expiration, the last-used
time will not be changed).
- void chan_bad_password(User *u, ChannelInfo *ci)
- Records a bad password attempt for the given channel, sending out a
WALLOPS if the number of consecutive bad password attempts
for the channel reaches the limit specified by the
BadPassWarning configuration option (if set).
- ChanOpt *chanopt_from_name(const char *optname)
- Returns the ChanOpt corresponding to the given
(case-insensitive) option name, or NULL if no matching
option is found.
- char *chanopts_to_string(const ChannelInfo *ci, const NickGroupInfo *ngi)
- Returns a string describing the set of options active on the given
channel in human-readable form. ngi indicates the client
to which the string will be sent and is used in getstring()
calls. The returned string is stored in a static buffer, which
will be overwritten by the next call to this routine.
Back to top
7-4-2. Channel access list handling
As discussed in the user's manual, user privileges on channels are
maintained via channel access lists. Channel access list handling in
ChanServ is split into three files. One, access.c, contains
common routines and privilege level definitions, and is included in the
chanserv/main module; the other two, access-levels.c and
access-xop.c, are independent modules which provide two different
ways of manipulating access lists. A separate header file,
access.h, contains definitions related to channel access lists.
Back to top
7-4-2-1. Access list basics
The access.c source file, included as part of the main ChanServ
module, serves two main purposes; to define the set of privileges
associated with channels, and to provide utility routines for performing
common operations related to channel access lists. It also makes use of
the access.h header file for certain structure and constant
definitions.
The list of channel privileges is defined in the levelinfo[]
array at the top of the file; each element in the array is a
LevelInfo structure that describes one privilege. (The word
"level" in the identifiers comes from the command, LEVELS, used
to modify the settings on a per-channel basis; that command name was
originally used for the meaning of "setting the levels at which
privileges are given".)
The word "privilege" itself is something of a misnomer, as it includes
two "negative privileges", CA_AUTODEOP and CA_NOJOIN.
While these can be used in the same manner as ordinary privileges, their
primary function is in conjunction with the SECUREOPS and
RESTRICTED channel options; these cause the respective privilege
levels to be treated as zero, preventing users not on the channel access
list to be auto-deopped or blocked from entering the channel. However,
these two privileges are not visible to users, so "privilege" is considered
clear enough to use in the documentation
The LevelInfo structure, defined in access.h, contains
the following fields:
- int what
- The CA_* constant used for this privilege.
- int defval
- The default channel access level corresponding to this
privilege.
- const char *name
- The user-visible name for this privilege, used in the
LEVELS command. An empty string makes the privilege
invisible to users.
- int desc
- The message string index giving the privilege's description.
- int action
- The "meaning" of the privilege: the action to be performed for
clients with the appropriate access level. One of the following
flags, any of which may be combined (OR'd) with
CL_LESSEQUAL to make the privilege's associated level a
maximum rather than a minimum:
- CL_SET_MODE: Sets channel user modes on the
corresponding client.
- CL_CLEAR_MODE: Clears channel user modes from
the corresponding client.
- CL_ALLOW_CMD: Allows a command or set of
commands to be used.
- CL_OTHER: Handled separately (or a no-op).
- union {...} target
- Data used in implementing the privilege. The union has two
members:
- struct {...} cumode: Used for
CL_SET_MODE and CL_CLEAR_MODE. Includes
three fields:
- const char *modes: The string of
mode(s) to set on the client.
- int cont: Used to "chain" privileges
together, so that only the first applicable mode
set is used (used for AUTOOP,
AUTOHALFOP, and AUTOVOICE).
- int32 flags: The mode flags equivalent
to modes. Set at module initialization
time (this field can be left uninitialized).
- struct {...} cmd: Used for
CL_ALLOW_CMD. Includes two string fields:
- const char *main: The relevant command
name.
- const char *sub: The relevant subcommand
for the given command, or NULL if none.
In addition to the levelinfo[] table itself, exported for use
by other modules (particularly the http/dbaccess module, described
in section 8-2-7), the file's initialization
routine init_access() copies relevant parts of the table into two
local arrays indexed by privilege (CA_* value):
def_levels[], containing each entry's default access level (the
defval field), and lev_is_max[], containing a boolean
indication of whether the privilege's access level is a maximum (whether
the action field has CL_LESSEQUAL set).
The main portion of access.c defines the following utility
functions for use by the main ChanServ module and the two access list
manipulation modules. Several of the routines are exported for use by
external modules as well.
- int get_ci_level(const ChannelInfo *ci, int what)
- Exported routine. Returns the given channel's level for the
given privilege, translating ACCLEV_DEFAULT in the
channel's levels[] into the appropriate default value.
Returns ACCLEV_INVALID (and logs an error message) on
invalid parameters.
- int check_access(const User *user, const ChannelInfo *ci, int what)
- Exported routine. Returns whether the given client has the
given privilege (what) on the given channel. Due
to an unfortunate coincidence of terms, this routine can seem
confusing to call for the CA_NOJOIN "privilege": the
return value will be 1 if the client does not "have access
to" the channel, i.e. matches the CA_NOJOIN
privilege.
- int check_access_if_idented(const User *user, const ChannelInfo *ci, int what)
- Exported routine. Like check_access(), but returns
what the result would be if the client was identified for its
nickname. (If the nickname is not registered, the return value
will be the same as for check_access().
- int check_access_cmd(const User *user, const ChannelInfo *ci, const char *command, const char *subcommand)
- Exported routine. Returns whether the client is allowed to
use the given command on the given channel. If the check is being
made for a specific subcommand, that subcommand is specified in
subcommand; otherwise, subcommand
is NULL.
- int get_access(const User *user, const ChannelInfo *ci)
- Returns the given client's access level on the given channel,
taking into account whether the client has identified for its
nickname with respect to the nickname and channel SECURE
options.
- static int get_access_if_idented(const User *user, const ChannelInfo *ci)
- Like get_access(), but returns the access level associated
with the client's nickname regardless of whether the client has
identified or not.
- int check_access_cumode(const User *user, const ChannelInfo *ci, int32 newmodes, int32 changemask)
- Checks the channel user modes of a client on a channel, returning a
bitmask of modes that are to be changed (in other words, the
bitwise exclusive-or of the current and resulting mode sets).
newmodes is the client's current set of modes on
the channel; changemask indicates which modes
changed due to the action that caused this function to be called.
- int access_add(ChannelInfo *ci, const char *nick, int level, int uacc)
int access_del(ChannelInfo *ci, const char *nick, int uacc)
- Adds (access_add()) or deletes (access_del()) an
entry to or from the given channel's access list for the nickname
group to which nick belongs. level
is the access level for the new entry, and uacc is
the access level of the client making the change. Both routines
return one of the following result codes, defined in
cs-local.h:
- RET_ADDED (access_add() only): The
target nickname did not previously exist on the channel's
access list, and was successfully added.
- RET_CHANGED (access_add() only): The
target nickname previously had a different access level on
the channel, and the access level was successfully
changed.
- RET_UNCHANGED (access_add() only): The
target nickname already had the desired access level on the
channel.
- RET_DELETED (access_del() only): The
target nickname was successfully deleted from the channel's
access list.
- RET_LISTED (used by other modules): The target
nickname was listed.
- RET_PERMISSION: The calling user does not have
permission to make the requested change.
- RET_NOSUCHNICK: The given nickname is not
registered.
- RET_NICKFORBID: The given nickname is
forbidden.
- RET_LISTFULL (access_add() only): The
channel's access list is full and the given nickname is not
already on the list.
- RET_NOENTRY (access_del() only): The
given nickname does not exist on the channel's access
list.
- RET_INTERR: An internal error occurred.
Note that successful return codes are positive, while failure
return codes are negative (zero is not used as a return code).
access.c also includes initialization code in the
init_access() routine, called from the chanserv/main
module's init_module(). This routine initializes the
def_levels[] and lev_is_max[] arrays, as well as the
flags field of the target.cumode structure for privileges
that set or clear modes; it also disables (by removing from the table) any
privileges for features not supported by the IRC protocol in use.
Implementation note: This relies on the current design of Services, in
which the protocol module will never be changed while the program is
running. If this design is changed, the code will need to be updated to
leave the appropriate entries in the table, perhaps by adding a "disabled"
flag or field to each entry.
There is also a corresponding exit_access() routine; it does
nothing, but is included for completeness.
Back to top
7-4-2-2. Manipulation via ACCESS and LEVELS
The chanserv/access-levels module is one of two modules for
manipulating channel access lists. It provides direct access to both the
channel access list itself, via the ACCESS command, and channels'
privilege level settings, via the LEVELS command.
Since the functions available in both commands change depending on
runtime parameters, their help messages are handled by a "HELP"
callback function, do_help(). In addition to description of the
two commands, a third help option, LEVELS DESC provides
descriptions of the available channel privileges; unlike most other help
messages, the text is generated on the fly from the levelinfo[]
array and corresponding description strings.
The ACCESS command handler, do_access(), calls one of
several subroutines to perform each of the available actions, depending on
the subcommand given. Two of these, do_access_add() and
do_access_del(), simply call the access_add() and
access_del() routines mentioned in section
7-4-2-1, sending appropriate result messages to the calling client
depending on the return codes from those functions.
do_access_list() and do_access_listlevel() select access
entries for listing based on the given parameters, then call
access_list(), defined below the ACCESS subcommand
handlers, to actually send the list text to the calling client. (The
sent_header parameter to access_list() is used to
to record whether a list header message has been sent.) The final
subcommand handler, do_access_count(), simply counts up the number
of (active) access entries in the list and sends that in a response message
to the calling client.
The LEVELS command also has several subcommands, though these
are all handled within the command handler do_levels(). The
command implementation itself is fairly straightforward, with SET
and DISABLE searching the levelinfo[] array for the named
privilege, and LIST iterating through that array to display the
current level for each privilege.
Back to top
7-4-2-3. Manipulation via XOP
The chanserv/access-xop module, defined in access-xop.c,
provides the XOP command set for managing channel access lists.
While these commands present the access list as several distinct lists
(SOP, AOP, and so on), they are in fact only different views of the same
channel access list, each containing nicknames with a particular predefined
access level. This has the following side effects:
- A particular nickname cannot be present on two or more
lists simultaneously (this would require having two entries on the
access list for the same nickname). Attempting to add a nickname
that is already on a different list is treated as an access level
change.
- Changes to the access list made by the ACCESS
command will also be reflected in the relevant XOP lists. If the
ACCESS command is used to add a nickname at a level not
associated with any XOP command, that nickname will be
invisible from every XOP list, even if it would have
privileges associated with one or more of the lists.
Implementation note: One could conceivably change the XOP
LIST commands to list all entriess in intermediate or extreme
ranges; for example, nicknames with an access level between
ACCLEV_AOP and ACCLEV_SOP could be shown on the
AOP list, and nicknames with an access level below
ACCLEV_NOP could be shown on the NOP list.
- If the channel's privilege levels are changed (such as
with the LEVELS command), the XOP commands may no
longer perform as their help messages describe, because the XOP
lists are associated with hardcoded access levels rather than
specific privileges. Implementation note: Attempting to base
the lists on privilege levels could have unusual results if, for
example, the auto-op privilege was lowered below auto-voice.
The commands themselves are handled by a single central routine,
handle_xop(), which is called from each command's particular
handler with the appropriate access level. handle_xop() works
much like do_access() from access-levels.c (in fact, it
is a modified copy of do_access(), and its subroutines are
likewise derived from the handlers for the ACCESS subcommands).
Response messages include the list name (SOP, AOP, etc.) as a parameter in
the message, and this name is determined based on the command's access
level using the XOP_LISTNAME macro at the top of the file.
Back to top
7-5. MemoServ
The MemoServ pseudoclient serves as an adjunct to NickServ, allowing
short messages (memos) to be sent between users and storing those messages
for the recipient. Like other pseudoclients, the majority of MemoServ's
functionality is implemented in the memoserv/main module,
described below in section 7-5-1; additional modules
allow users to maintain "ignore" lists (memoserv/ignore,
section 7-5-2) and have memos forwarded via E-mail
(memoserv/forward, section 7-5-3).
Back to top
7-5-1. MemoServ core functionality
Unlike OperServ, NickServ, and ChanServ, MemoServ only provides
additional functionality to users (nicknames and, to an extent, channels)
already registered with Services. For this reason, the main MemoServ
code is comparatively simple, and the memoserv/main module is
implemented by a single source file, main.c. There is also a
header file, memoserv.h, containing structures and definitions
used by MemoServ.
Back to top
7-5-1-1. Memo data structures
The structure used to store memos are defined in the header file
memoserv.h. Each memo is stored in a structure called
(appropriately enough) Memo, and the set of memos belonging to a
particular nickname group is stored in a MemoInfo structure. The
Memo structure contains the following fields:
- uint32 number
- The index number associated with this memo, for use with MemoServ
commands such as READ and DEL. Note that while
the array of memos in a MemoInfo structure is kept free of
holes caused by memo deletions, memo index numbers do not change
except as a result of the RENUMBER command, so there is
not a one-to-one mapping between the two.
- int16 flags
- Flags associated with the memo. Zero or more of the following
constants, OR'd together:
- MF_UNREAD: The memo has not yet been read.
- MF_EXPIREOK: The memo is allowed to expire.
Implementation note: The sense of this flag is opposite
that of MemoServ's design, which expires any memos except
those explicitly locked with the SAVE command.
This was done to keep compatibility with memos sent using
old versions of Services, in which memos did not expire;
rather than explicitly adding a "locked" flag to every memo
when loading databases from such a version, the flag takes
advantage of the fact that the corresponding bit in such
memos is zero. This way, for a memo to expire, the flag
must be explicitly enabled by the new version when the memo
is sent.
- time_t time
- The timestamp when the memo was sent.
- time_t firstread
- The time at which the memo was first read by its recipient. This
is stored to ensure that an unread memo does not expire immediately
after it is first read (the MSExpireDelay configuration
option controls how long MemoServ will wait after this timestamp
before expiring the memo).
- char sender[NICKMAX]
- The nickname of the client that sent the memo.
- char *channel
- For memos sent to channels, the name of the channel to which the
memo was sent. NULL for other memos.
- char *text
- The text of the memo.
The MemoInfo structure contains:
- Memo *memos
int16 memos_count
- A variable-length array containing the memos associated with this
structure.
- int16 memomax
- The maximum number of memos this nickname group is allowed to have
stored. Either a nonnegative literal value (zero is allowed, and
means that the nickname group cannot receive any memos at all), or
one of the following constants:
- MEMOMAX_UNLIMITED: There is no limit on the
number of nicknames that can be stored.
- MEMOMAX_DEFAULT: The default limit, set by the
MSMaxMemos configuration option, is used. (If
MSMaxMemos is changed, the new limit will be
automatically applied.)
The constant MEMOMAX_MAX is also provided to give the
largest possible maximum value (a side-effect of storing the value
in 16 bits).
Each MemoInfo structure is stored as part of the corresponding
nickname group's NickGroupInfo structure (see
section 7-3-1-1). In previous versions, channels had
associated memo lists as well, but this feature was removed for version 5.1
in favor of the current system of distributing channel memos to privileged
users.
There are two more constants in memoserv.h:
MS_RECEIVE_PRI_CHECK and MS_RECEIVE_PRI_DELIVER. These
are callback priorities that can be used with add_callback_pri()
to allow a "receive memo" callback function to ensure it is called
before functions which try to deliver the memo. Both the
memoserv/ignore and memoserv/forward modules take
advantage of these constants.
Back to top
7-5-1-2. The memoserv/main module
The memoserv/main module is defined in the file main.c,
and aside from the lack of any auxiliary source files, its structure is
more or less the same as that of the other pseudoclients' core modules.
Since, as mentioned above, the MemoInfo structures containing
memo data are stored as part of the corresponding NickGroupInfo
structures, special handling is required when moving the data to or from
persistent storage. MemoServ uses iterator functions for the
MemoInfo table that in turn iterate through NickGroupInfo
structures, and sets up a dummy record containing each record's associated
nickname group ID, much like the nickserv/access module (see
section 7-3-2). For individial Memo records,
an additional iterator is used to loop through each memo in every
MemoInfo structure.
Most MemoServ actions are implemented by separate routines in the
"MemoServ private routines" section of the file, rather than directly in
the command handlers. These functions are:
- send_memo() and send_chan_memo(),
which send memos to users and channels respectively;
- list_memo(), which sends the calling client a one-line
description of a memo, and list_memo_callback(), a
callback function to do the same thing;
- read_memo() and read_memo_callback,
which display the text of a memo;
- save_memo() and save_memo_callback,
which mark memos as non-expiring; and
- del_memo() and del_memo_callback(),
which delete memos.
Other utility routines include:
- void check_memos(User *u)
- Checks whether the given client's nickname group has any unread
memos, sending an appropriate message to the client if so (and if
the NF_MEMO_SIGNON flag is set for the nickname group).
Called by the "user create" callback function
do_user_create() if the client is recognized.
- void expire_memos(MemoInfo *mi)
- Deletes all memos in the given MemoInfo structure that
are eligible for expiration.
- MemoInfo *get_memoinfo(const char *name, NickGroupInfo **owner_ret, int *error_ret
- Returns the MemoInfo structure corresponding to the given
nickname, or NULL on error. On success,
*owner_ret is set to point to the
NickGroupInfo structure for the corresponding nickname
group; on error, *error_ret is set to one of the
following error codes:
- GMI_INTERR: An internal error occurred.
- GMI_FORBIDDEN: The given nickname is forbidden.
- GMI_SUSPENDED: The given nickname is suspended.
- GMI_NOTFOUND: The given nickname is not registered.
Implementation note: This function's primary purpose of handling
both nicknames and channel names transparently was obsoleted with
the redesign of channel memo handling; however, the function still
serves the purpose of checking for unregistered, forbidden, or
suspended nicknames, eliminating the necessity to include such
checks directly in every command handler.
The command handlers are for the most part simple, only needing to parse
parameters and call the appropriate utility routine. The two commands
which are implemented directly, SET and INFO, are mostly
branching trees for the various nickname options and statuses.
Back to top
7-5-2. Memo ignore lists
The memoserv/ignore module was added to allow users a way to
block memos from unwanted senders such as spammers. The module is
implemented by the source file ignore.c.
For the most part, ignore.c works in the same way as the
nickserv/access and nickserv/autojoin modules. The
one routine unique to memoserv/ignore is the
check_if_ignored() routine, added as a callback function to the
core MemoServ module's "receive memo" callback at priority
MS_RECEIVE_PRI_CHECK. The routine runs through the nickname
group's ignore list twoce: the first time, it treats each entry as a
Nick!user@host mask which is compared against
that of the memo sender, while the second time, it treats each entry as a
nickname, and the memo is blocked if that nickname's nickname group ID
matches that of the sender (thus proventing malicious clients from evading
the block by simply changing to another linked nick). If thd memo is
blocked, the routine returns the language string index
MEMO_C_GETS_NO_MEMOS (the same as is used if the target has a
memo limit of zero), to prevent leaking information about the ignore list
contents.
Back to top
7-5-3. Memo forwarding
The memoserv/forward module, defined in the source file
forward.c, provides an interface between memos in Services and an
external mail system. As such, one of its prerequisite modules (other than
memoserv/main, of course) is nickserv/mail-auth, to
reduce the possibility of Services being abused to send mail to arbitrary
addresses.
The module includes two methods of forwarding memos: manual and
automatic. The former, manual forwarding, is done through the
FORWARD command, implemented by the do_forward() routine
and its subroutines fwd_memo() and fwd_memo_callback().
As with other commands that allow users to send mail via Services, the
command handler includes a check that the command is not used more
frequently than the configuration option MSForwardDelay specifies.
If this and the other checks pass, the command handler takes one of two
actions, depending on its parameter. If "ALL" was given, then
fwd_memo() is called for each memo in the calling client's
MemoInfo structure; otherwise, the parameter is passed to
process_numlist() to call the fwd_memo_callback()
callback function (which in turn calls fwd_memo()) for each memo
specified in the index list.
fwd_memo() itself does not perform the actual sending of the
mail message; rather, it accumulates message body text in its parameters
char **p_body and int *p_bodylen (both of
which are modified as a result of the routine). This allows the caller to
combine multiple memos into a single message without knowing ahead of time
how many or which memos are to be sent.
The second method of forwarding, automatic forwarding on receipt, is
implemented by the do_receive_memo() function, attached to the
MemoServ "receive memo" callback, along with the
do_set_forward() option handler (attached to the "SET"
callback) to set forwarding options for a nickname group. Of the
forwarding options, ON (flag NF_MEMO_FWD) and
OFF (no flags set) are fairly obvious. For COPY (flags
NF_MEMO_FWD and NF_MEMO_FWDCOPY), however, it is worth
noting that (as also mentioned in the user's manual and help messages)
memos will be rejected when the recipient has a full list of memos. This
is partly a result of the callback implementation, in the sense that the
check for a full memo list is made before the callback is ever called; but
it also ensures that every memo received by the user is either processed
completely (both forwarded and saved) or not at all. This can be important
if, for example, a user uses SET FORWARD COPY to save copies of
memos to an archival E-mail address, but only uses Services to actually
read the memos. If a memo that would overflow the list was nonetheless
forwarded and treated as "sent", the sender would be left wondering why the
recipient was not responding to the purportedly delivered memo.
Back to top
7-6. StatServ
The StatServ pseudoclient is intended to record and provide network
statistics; however, mostly due to lack of developer interest, it has
floundered. The module is defined in the modules/statserv
directory by a single source file, main.c, and an accompanying
header file, statserv.h.
Back to top
7-6-1. StatServ data structures
The current implementation of StatServ only records data for servers.
This data is stored in a ServerStats structure, defined in
statserv.h and containing the following fields:
- ServerStats *next, *prev
- Used to maintain the linked list of ServerStats
structures.
- char *name
- The name of the server to which this structure applies.
- time_t t_join
- The timestamp at which the server most recently joined the network.
Zero if the server is not currently connected.
- time_t t_quit
- The timestamp at which the server last disconnected. Zero if
Services has never seen the server disconnect.
- char *quit_message
- The quit message used the last time the server disconnected.
NULL if Services has never seen the server disconnect.
- int usercnt, opercnt
- The current number of clients and IRC operators on the server.
As the data has no direct references to other data stored persistently,
saving the ServerStats records is straightforward.
There are also commented-out definitions for MinMax and
MinMaxHistory structures, and MinMaxHistory fields in
ServerStats; these were originally intended for keeping a history
of client and operator counts on each server, but this functionality was
never implemented.
Back to top
7-6-2. The StatServ module
The module itself, defined in main.c, is likewise quite simple.
It follows the general layout of other pseudoclient core modules, though it
only has two commands (other than HELP) and a few callback
routines.
The two commands, SERVERS and USERS, are handled by
do_servers() and do_users() respectively. The latter
needs no additional explanation, as it only sends the calling client the
user and operator counts it maintains internally (via the client-related
callbacks mentioned below). do_servers() is only slightly more
complex, handling four distinct subcommands. Three of these
(STATS, LIST, and VIEW) extract information from
the ServerStats structures and send them to the calling client;
the last, DELETE, allows a structure to be deleted, and is
protected by an is_services_admin() check in the if
chain.
In order to keep track of statistics, StatServ naturally relies on
callbacks. To watch for connecting and disconnecting servers, StatServ
adds callback functions to the core's "server create" and
"server delete" callbacks; these functions update the relevant
ServerStats structures as servers join and leave the network, with
stats_do_server() (the "server create" handler) creating
a new ServerStats structure if it sees a server connect that is
not recorded in the ServerStats structure table.
StatServ also uses the "user create", "user delete",
and "user MODE" callbacks to keep track of the number of clients
and IRC operators on the network, stored in the file-scope variables
usercnt and opcnt respectively. Note that this overlaps
slightly with the maximum client count maintained by OperServ; a good
argument could be made for moving this functionality to StatServ as well,
but it has been left in OperServ for historical reasons (the maximum client
count monitoring functionality has been part of OperServ since the earliest
versions of Services, long before StatServ existed).
Back to top
7-7. Miscellaneous pseudoclients
Aside from the standard pseudoclients listed above, there are two
additional pseudoclients, HelpServ and DevNull, provided in case they can
be of use. These pseudoclients are disabled in the default Services
configuration. These modules are so simple as to not warrant their own
subdirectories, and are stored in the modules/misc directory.
Back to top
7-7-1. HelpServ
The HelpServ pseudoclient, defined in modules/misc/helpserv.c,
uses the parameters given in each PRIVMSG it receives to form a
pathname for a text file, which (if it exists) is then sent to the calling
client as NOTICEs. The pathname is formed by concatenating the
path given by the HelpDir module configuration setting (the
example configuration file uses helpfiles, relative to the
Services data directory) with each of its space-separated parameters,
inserting a path separator ("/") between each element. To allow
the parameter to be case-insensitive even when using case-sensitive
filesystems, all uppercase characters in the parameters are lowercased.
Since this involves access to a file specified by an arbitrary
user-specified string, the routine must take care not to allow access to
inappropriate files. This is done by replacing all slashes and periods in
the string with underscores, to prevent a malicious user from specifying
a parameter like "../../../../../etc/passwd". (If there are
explicit symbolic links to parent directories, of course, HelpServ will
happily follow them.) Implementation note: As a general rule, when
sanitizing user input like this it is better to make a set of explicitly
allowed characters and delete or convert anything outside that set.
However, pathnames on Unix systems are simple and well defined:
disregarding the effects of symbolic links, only a slash can allow access
to file entries outside of the current directory, and "." and
".." are the only entries that are automatically created within
each directory, so the set of allowed characters is essentially "everything
except / and .".
In the earliest versions of Services, HelpServ was a standard
pseudoclient, and other pseudoclients made use of its functionality to
display their own help messages, which were stored as text files under the
data/helpfiles directory. However, the help messages were later
moved to string data stored directly in the executable file, and then to
the current model of language files, diluting the usefulness of HelpServ
itself. The module has nonetheless been left in Services in case it is
useful for things such as providing network information.
Back to top
7-7-2. DevNull
The DevNull pseudoclient, defined in modules/misc/devnull.c, is
an extremely simple pseudoclient which, like its Unix namesake
/dev/null, simply discards any PRIVMSGs sent to it. It
is not particularly useful in the ordinary course of events, but the author
has made use of it as a default /query target to prevent messages
from going to unintended users.
Back to top
Previous section: Database handling |
Table of Contents |
Next section: Other modules