/* Unreal protocol module for IRC Services.
*
* IRC Services is copyright (c) 1996-2007 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file COPYING for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "language.h"
#include "messages.h"
#include "timeout.h"
#include "modules/operserv/operserv.h"
#include "modules/operserv/maskdata.h"
#include "modules/nickserv/nickserv.h"
#include "modules/chanserv/chanserv.h"
#define UNREAL_HACK /* For SJOIN; see comments in sjoin.c */
#include "banexcept.h"
#include "chanprot.h"
#include "halfop.h"
#include "invitemask.h"
#include "sjoin.h"
#include "svsnick.h"
#include "token.h"
/*************************************************************************/
/* Variable to hold the current module's pointer: */
static Module *module;
/* Variables for modules referenced by this one: */
static Module *module_operserv;
static Module *module_chanserv;
/* Variables set by configuration directives: */
/* Numeric to send, 0 for none */
static int32 ServerNumeric = 0;
/* Should we use TSCTL SVSTIME to set server timestamps? */
static int SetServerTimes = 0;
/* Frequency to send TSCTL SVSTIME at (0 = startup only) */
static time_t SVSTIMEFrequency = 0;
/* Timeout for sending SVSTIME */
static Timeout *to_svstime = NULL;
/* The following flag sets are set by init_modes(). We could have done
* this using #defines as well, but this way is cleaner (there's no need
* to make sure these values are synchronized with the list of modes
* below). */
static int32 usermode_admin = 0; /* +aANT */
static int32 usermode_secure = 0; /* +z */
static int32 usermode_hiding = 0; /* +I */
static int32 chanmode_admins_only = 0; /* +A */
static int32 chanmode_secure_only = 0; /* +z */
static int32 chanmode_no_hiding = 0; /* +H */
/* This variable holds the version of the Unreal protocol the remote server
* is using, extracted from its SERVER message. */
static int unreal_version = 0;
/* Does the remote server support NICKIP? */
static int has_nickip = 0;
/*************************************************************************/
/****************** Local interface to external symbols ******************/
/*************************************************************************/
/*
* We import `s_ChanServ' from the "chanserv/main" module; this variable
* holds a pointer to `s_ChanServ', obtained via get_module_symbol(). When
* the ChanServ module is not loaded, this holds a pointer to `ServerName'
* instead as a default value.
*
* Note that we use a #define here to substitute `*p_s_ChanServ' for
* `s_ChanServ', so subsequent code can simply use `s_ChanServ' as if it
* were declared normally.
*/
static char **p_s_ChanServ = &ServerName;
#define s_ChanServ (*p_s_ChanServ)
/*************************************************************************/
/*
* We also import `is_services_admin' from the "operserv/main" module.
* As with `s_ChanServ', the value is obtained via get_module_symbol(),
* and defaults to NULL when the module is not loaded. We access the
* function via a wrapper (local_is_services_admin()), which checks
* whether the function pointer is non-NULL before calling it, and returns
* a default value if the pointer is NULL (in this case, if the OperServ
* module is not loaded then we assume no one is a Services admin and
* return false).
*
* Here, too, we use a #define to substitute `local_is_services_admin' for
* `is_services_admin', so we can use the latter as if it were declared
* normally.
*/
static int (*p_is_services_admin)(User *u) = NULL;
static int local_is_services_admin(User *u)
{
return p_is_services_admin && (*p_is_services_admin)(u);
}
#define is_services_admin local_is_services_admin
/*************************************************************************/
/************************** User/channel modes ***************************/
/*************************************************************************/
/*
* Here we define modes specific to this IRC protocol. To simplify the
* initialization code, we define each category of modes (user modes,
* channel modes, and channel user modes) using the structure below, which
* holds a mode character (typed as uint8 because it is used as an array
* index) and a ModeData structure (defined in modes.h).
*/
struct modedata_init {
uint8 mode;
ModeData data;
};
/*
* We also define new MI_ flags for certain modes; these are used by the
* initialization code to set up the usermode_* and chanmode_* variables
* above. These flags must use only the bits defined in modes.h for local
* use (MI_LOCAL_FLAGS, 0xFF000000).
*
* Note that the user mode flags and channel mode flags overlap; this is
* okay, since they will never be used together.
*/
#define MI_ADMIN 0x01000000 /* Usermode given to admins */
#define MI_SECURE 0x02000000 /* Usermode for users on secure conn's */
#define MI_HIDING 0x04000000 /* Usermode for hiding users */
#define MI_ADMINS_ONLY 0x01000000 /* Chanmode for admin-only channels */
#define MI_SECURE_ONLY 0x02000000 /* Chanmode for secure-user-only chans */
#define MI_NO_HIDING 0x04000000 /* Chanmode for no-hiding channels */
/*************************************************************************/
/* New user modes: */
static const struct modedata_init new_usermodes[] = {
{'g', {0x00000008}}, /* Receive globops */
{'h', {0x00000010}}, /* Helpop */
{'r', {0x00000020,0,0,0,MI_REGISTERED}},
/* Registered nick */
{'a', {0x00000040,0,0,0,MI_ADMIN}},
/* Services admin */
{'A', {0x00000080,0,0,0,MI_ADMIN}},
/* Server admin */
{'N', {0x00000100,0,0,0,MI_ADMIN}},
/* Network admin */
{'T', {0x00000200,0,0,0,MI_ADMIN}},
/* Technical admin */
{'x', {0x00000400}}, /* Mask hostname */
{'S', {0x00000800}}, /* Services client */
{'q', {0x00001000}}, /* Cannot be kicked from chans */
{'I', {0x00002000,0,0,0,MI_HIDING}},
/* Completely invisible (joins etc) */
{'d', {0x00004000}}, /* Deaf */
{'z', {0x00008000,0,0,0,MI_SECURE}},
/* Using secure connection */
};
/*************************************************************************/
/*
* New channel modes. Unlike user modes and channel user modes, these are
* used in database files (for channel mode locks), and thus should be as
* portable as possible between different protocols. To that end, any new
* protocol should, when possible, use the same flag as existing protocols
* for the same mode (or a different mode letter with the same function),
* and should avoid using any flags used by other protocols for modes that
* are peculiar to that protocol.
*/
static const struct modedata_init new_chanmodes[] = {
{'R', {0x00000100,0,0,0,MI_REGNICKS_ONLY}},
/* Only identified users can join */
{'r', {0x00000200,0,0,0,MI_REGISTERED}},
/* Set for all registered channels */
{'c', {0x00000400,0,0}}, /* No ANSI colors in channel */
{'O', {0x00000800,0,0,0,MI_OPERS_ONLY}},
/* Only opers can join channel */
{'A', {0x00001000,0,0,0,MI_OPERS_ONLY|MI_ADMINS_ONLY}},
/* Only admins can join channel */
{'z', {0x00002000,0,0,0,MI_SECURE_ONLY}},
/* Only secure (+z) users allowed */
{'Q', {0x00004000,0,0}}, /* No kicks */
{'K', {0x00008000,0,0}}, /* No knocks */
{'V', {0x00010000,0,0}}, /* No invites */
{'H', {0x00020000,0,0,0,MI_NO_HIDING}},
/* No hiding (+I) users */
{'C', {0x00040000,0,0}}, /* No CTCPs */
{'N', {0x00080000,0,0}}, /* No nick changing */
{'S', {0x00100000,0,0}}, /* Strip colors */
{'G', {0x00200000,0,0}}, /* Strip "bad words" */
{'u', {0x00400000,0,0}}, /* Auditorium mode */
{'f', {0x00800000,1,0}}, /* Flood limit */
{'L', {0x01000000,1,0}}, /* Channel link */
{'M', {0x02000000,0,0}}, /* Moderated to unregged nicks */
{'T', {0x04000000,0,0}}, /* Disable notices to channel */
{'j', {0x08000000,1,0}}, /* Join throttling */
{'e', {0x80000000,1,1,0,MI_MULTIPLE}}, /* Ban exceptions */
{'I', {0x80000000,1,1,0,MI_MULTIPLE}}, /* INVITE hosts */
};
/*************************************************************************/
/* New channel user modes: */
static const struct modedata_init new_chanusermodes[] = {
{'h', {0x00000004,1,1,'%'}}, /* Half-op */
{'a', {0x00000008,1,1,'~'}}, /* Protected (no kick or deop by +o) */
{'q', {0x00000010,1,1,'*',MI_CHANOWNER}},
/* Channel owner */
};
/*************************************************************************/
/*
* This routine inserts the data listed above into the global usermodes[],
* chanmodes[], and chanusermodes[] arrays, initializes the local mode
* variables (usermode_* and chanmode_*), and calls mode_setup().
*/
static void init_modes(void)
{
int i;
for (i = 0; i < lenof(new_usermodes); i++) {
usermodes[new_usermodes[i].mode] = new_usermodes[i].data;
if (new_usermodes[i].data.info & MI_ADMIN)
usermode_admin |= new_usermodes[i].data.flag;
if (new_usermodes[i].data.info & MI_SECURE)
usermode_secure |= new_usermodes[i].data.flag;
if (new_usermodes[i].data.info & MI_HIDING)
usermode_hiding |= new_usermodes[i].data.flag;
}
for (i = 0; i < lenof(new_chanmodes); i++) {
chanmodes[new_chanmodes[i].mode] = new_chanmodes[i].data;
if (new_chanmodes[i].data.info & MI_ADMINS_ONLY)
chanmode_admins_only |= new_chanmodes[i].data.flag;
if (new_chanmodes[i].data.info & MI_SECURE_ONLY)
chanmode_secure_only |= new_chanmodes[i].data.flag;
if (new_chanmodes[i].data.info & MI_NO_HIDING)
chanmode_no_hiding |= new_chanmodes[i].data.flag;
}
for (i = 0; i < lenof(new_chanusermodes); i++)
chanusermodes[new_chanusermodes[i].mode] = new_chanusermodes[i].data;
mode_setup();
};
/*************************************************************************/
/************************* IRC message receiving *************************/
/*************************************************************************/
/* Handler for the NICK message. */
static void m_nick(char *source, int ac, char **av)
{
char *ipaddr;
char *newav[10];
#ifdef CLEAN_COMPILE
ipaddr = NULL;
#endif
if (*source) {
/* Old user changing nicks. */
if (ac != 2) {
if (debug)
module_log("debug: NICK message: wrong number of parameters"
" (%d) for source `%s'", ac, source);
} else {
do_nick(source, ac, av);
}
return;
}
/*
* New user. Unreal's NICK message takes 10 parameters (11 for servers
* supporting NICKIP):
*
* NICK <nick> <hopcount> <timestamp> <username> <hostname>
* <server> <servicestamp> <modes> <fakehost> [<IP>] :<realname>
*
* Processing consists of:
* - Making sure the message has the right number of parameters
* - Rearranging parameters for use by do_nick()
* - Calling do_nick()
* - If do_nick() was successful (i.e. the user was accepted),
* setting the modes given in the NICK message for the user
*
* Previous versions of Services used to also check whether the
* Services stamp sent by the remote server was zero, and assign a new
* stamp if so; this is now handled by do_nick() in users.c, with the
* MODE +d in do_user_servicestamp_change(), so we just pass along what
* we got from the server for now.
*/
if (ac != (has_nickip ? 11 : 10)) {
if (debug)
module_log("debug: NICK message: wrong number of parameters (%d)"
" for new user", ac);
return;
}
/* If this is a NICKIP server, decode and save the IP address, then
* strip it out of the parameter list so that the code below sees a
* standard set of parameters regardless of NICKIP support. */
if (has_nickip) {
if (strcmp(av[9],"*") == 0) {
ipaddr = "0.0.0.0";
} else {
unsigned char tmpbuf[16];
const char *badip = NULL; /* error message, or NULL if no error */
int len = decode_base64(av[9], tmpbuf, sizeof(tmpbuf));
if (len < 0 || len > sizeof(tmpbuf)) {
badip = "Corrupt IP address";
} else if (len == 4) {
ipaddr = unpack_ip(tmpbuf);
if (!ipaddr)
badip = "Internal error decoding IPv4 address";
} else if (len == 16) {
ipaddr = unpack_ip6(tmpbuf);
if (!ipaddr)
badip = "Internal error decoding IPv6 address";
} else {
badip = "Unrecognized IP address format";
}
if (badip) {
static int warned_no_nickip = 0;
if (!warned_no_nickip) {
wallops(NULL, "\2WARNING\2: %s for new nick %s", badip,
av[0]);
warned_no_nickip = 1;
}
module_log("WARNING: %s for new nick %s", badip, av[0]);
ipaddr = "0.0.0.0";
}
av[9] = av[10];
}
} else {
ipaddr = NULL;
}
/* Set up parameter list for do_nick():
* (current) (desired)
* 0: nick nick
* 1: hopcount hopcount
* 2: timestamp timestamp
* 3: username username
* 4: hostname hostname
* 5: server server
* 6: servicestamp realname
* 7: modes servicestamp
* 8: fakehost ipaddr
* 9: realname fakehost
*/
newav[0] = av[0];
newav[1] = av[1];
newav[2] = av[2];
newav[3] = av[3];
newav[4] = av[4];
newav[5] = av[5];
newav[6] = av[9];
newav[7] = av[6];
newav[8] = ipaddr;
newav[9] = av[8];
if (do_nick(source, 10, newav)) {
/* The user was accepted; set modes. */
newav[1] = av[7];
do_umode(newav[0], 2, newav);
}
}
/*************************************************************************/
/* PROTOCTL message handler. */
static void m_protoctl(char *source, int ac, char **av)
{
static int got_nickv2 = 0;
int i;
for (i = 0; i < ac; i++) {
if (stricmp(av[i], "NICKv2") == 0)
got_nickv2 = 1;
if (stricmp(av[i], "NOQUIT") == 0)
protocol_features |= PF_NOQUIT;
if (stricmp(av[i], "NICKIP") == 0)
has_nickip = 1;
}
/* Make sure we got a NICKv2 from the remote server; if not, abort. */
if (!got_nickv2) {
send_error("Need NICKv2 protocol");
strscpy(quitmsg, "Remote server doesn't support NICKv2",
sizeof(quitmsg));
quitting = 1;
}
}
/*************************************************************************/
/* UMODE2 (set user modes) message handler. */
static void m_umode2(char *source, int ac, char **av)
{
char *new_av[2];
if (ac != 1)
return;
new_av[0] = source;
new_av[1] = av[0];
do_umode(source, 2, new_av);
}
/*************************************************************************/
/* SETHOST/CHGHOST message handlers. */
static void m_sethost(char *source, int ac, char **av)
{
User *u;
if (ac != 1)
return;
u = get_user(source);
if (!u) {
module_log("m_sethost: user record for %s not found", source);
return;
}
free(u->fakehost);
u->fakehost = sstrdup(av[0]);
}
static void m_chghost(char *source, int ac, char **av)
{
if (ac == 2) {
if (debug) {
module_log("debug: m_chghost from %s calling m_sethost for %s",
source, av[0]);
}
m_sethost(av[0], ac-1, av+1);
}
}
/*************************************************************************/
/* SETIDENT/CHGIDENT message handlers. */
static void m_setident(char *source, int ac, char **av)
{
User *u;
if (ac != 1)
return;
u = get_user(source);
if (!u) {
module_log("m_setident: user record for %s not found", source);
return;
}
free(u->username);
u->username = sstrdup(av[0]);
}
static void m_chgident(char *source, int ac, char **av)
{
if (ac == 2) {
if (debug) {
module_log("debug: m_chgident from %s calling m_setident for %s",
source, av[0]);
}
m_setident(av[0], ac-1, av+1);
}
}
/*************************************************************************/
/* SETNAME/CHGNAME message handlers. */
static void m_setname(char *source, int ac, char **av)
{
User *u;
if (ac != 1)
return;
u = get_user(source);
if (!u) {
module_log("m_setname: user record for %s not found", source);
return;
}
free(u->realname);
u->realname = sstrdup(av[0]);
}
static void m_chgname(char *source, int ac, char **av)
{
if (ac == 2) {
if (debug) {
module_log("debug: m_chgname from %s calling m_setname for %s",
source, av[0]);
}
m_setname(av[0], ac-1, av+1);
}
}
/*************************************************************************/
/* SJOIN message handler. The actual code is in sjoin.c (note that we use
* UNREAL_HACK, so the routine actually called is do_sjoin_unreal()).
*/
static void m_sjoin(char *source, int ac, char **av)
{
if (ac < 3) {
if (debug)
module_log("debug: SJOIN: expected >=3 params, got %d", ac);
return;
}
do_sjoin(source, ac, av);
}
/*************************************************************************/
/* SVSMODE message handler. */
static void m_svsmode(char *source, int ac, char **av)
{
if (*av[0] == '#') {
if (ac >= 3 && strcmp(av[1],"-b") == 0) {
Channel *c = get_channel(av[0]);
User *u = get_user(av[2]);
if (c && u)
clear_channel(c, CLEAR_BANS, u);
} else {
module_log("Invalid SVS[2]MODE from %s for channel %s: %s",
source, av[0], merge_args(ac-1,av+1));
}
} else if (*av[0] == '&') {
module_log("SVS[2]MODE from %s for invalid target (channel %s): %s",
source, av[0], merge_args(ac-1,av+1));
} else {
if (ac < 2)
return;
do_umode(source, ac, av);
}
}
/*************************************************************************/
/* TKL message handler. Format of a TKL message:
* TKL + type user host set-by expire-time set-time reason
* or TKL - type user host removed-by
*/
static void m_tkl(char *source, int ac, char **av)
{
/* If we get a TKL + that claims to have been set by Services, send a
* TKL - for the same mask to remove it. This is needed in the case
* that a TKL is removed by Services while one or more servers are
* split; in such a case, the split server would, when it relinked,
* propogate the TKL back out across the network even though it was no
* longer "valid" from Services' perspective. Any TKLs that really are
* valid will be re-added as soon as a user matches them.
*
* As a special case, when we receive a TKL + Z, we first check whether
* such a TKL exists in the SZline list, and do not remove it if it's
* found; Unreal does not report IP address information for users, so
* we have no way to re-add the masks by checking connecting users.
*
* FIXME: Unreal 3.2 converts an SQLINE into a TKL and sends it back to
* us! Previous Unreal releases don't understand TKL Q, so for now
* check these against the SQline list.
*/
/* Pointer to the `get_maskdata' function defined in the database
* module. It would be more efficient to store this statically, but
* doing so would require watching for the database module to be
* unloaded, and as we should not normally receive many TKL messages
* anyway, the additional CPU burden is negligible. */
typeof(get_maskdata) *p_get_maskdata = NULL;
if (ac < 5 || *av[0] != '+' || strcmp(av[4],ServerName) != 0)
return;
if (!(p_get_maskdata = get_module_symbol(NULL, "get_maskdata")))
return;
if (*av[1] == 'Z' && (*p_get_maskdata)(MD_SZLINE, av[3]))
return;
if (*av[1] == 'Q' && (*p_get_maskdata)(MD_SQLINE, av[3]))
return;
send_cmd(ServerName, "TKL - %c %s %s %s",
*av[1], av[2], av[3], ServerName);
}
/*************************************************************************/
/* List of all Unreal-specific messages (handlers defined above). */
static Message unreal_messages[] = {
{ "AKILL", NULL },
{ "CHGHOST", m_chghost },
{ "CHGIDENT", m_chgident },
{ "CHGNAME", m_chgname },
{ "EOS", NULL },
{ "GLOBOPS", NULL },
{ "GNOTICE", NULL },
{ "GOPER", NULL },
{ "HELP", NULL },
{ "NETINFO", NULL },
{ "NICK", m_nick },
{ "PROTOCTL", m_protoctl },
{ "RAKILL", NULL },
{ "SENDSNO", NULL },
{ "SETHOST", m_sethost },
{ "SETIDENT", m_setident },
{ "SETNAME", m_setname },
{ "SILENCE", NULL },
{ "SJOIN", m_sjoin },
{ "SMO", NULL },
{ "SQLINE", NULL },
{ "SVSMODE", m_svsmode },
{ "SVS2MODE", m_svsmode },
{ "SWHOIS", NULL },
{ "TKL", m_tkl },
{ "UMODE2", m_umode2 },
{ NULL }
};
/*************************************************************************/
/* List of all Unreal tokens (passed to init_token()). */
static TokenInfo tokens[] = {
/* 0x21 */
{ "!", "PRIVMSG" },
{ "\"", "WHO" },
{ "#", "WHOIS" },
{ "$", "WHOWAS" },
{ "%", "USER" },
{ "&", "NICK" },
{ "'", "SERVER" },
{ "(", "LIST" },
{ ")", "TOPIC" },
{ "*", "INVITE" },
{ "+", "VERSION" },
{ ",", "QUIT" },
{ "-", "SQUIT" },
{ ".", "KILL" },
{ "/", "INFO" },
/* 0x30 */
{ "0", "LINKS" },
{ "1", "SUMMON" },
{ "2", "STATS" },
{ "3", "USERS" },
{ "4", "HELP" },
{ "5", "ERROR" },
{ "6", "AWAY" },
{ "7", "CONNECT" },
{ "8", "PING" },
{ "9", "PONG" },
{ ":", NULL },
{ ";", "OPER" },
{ "<", "PASS" },
{ "=", "WALLOPS" },
{ ">", "TIME" },
{ "?", "NAMES" },
/* 0x40 */
{ "@", "ADMIN" },
{ "A", NULL },
{ "B", "NOTICE" },
{ "C", "JOIN" },
{ "D", "PART" },
{ "E", "LUSERS" },
{ "F", "MOTD" },
{ "G", "MODE" },
{ "H", "KICK" },
{ "I", "SERVICE" },
{ "J", "USERHOST" },
{ "K", "ISON" },
{ "L", NULL },
{ "M", NULL },
{ "N", NULL },
{ "O", "REHASH" },
/* 0x50 */
{ "P", "RESTART" },
{ "Q", "CLOSE" },
{ "R", "DIE" },
{ "S", "HASH" },
{ "T", "DNS" },
{ "U", "SILENCE" },
{ "V", "AKILL" },
{ "W", "KLINE" },
{ "X", "UNKLINE" },
{ "Y", "RAKILL" },
{ "Z", "GNOTICE" },
{ "[", "GOPER" },
{ "\\", NULL },
{ "]", "GLOBOPS" },
{ "^", "LOCOPS" },
{ "_", "PROTOCTL" },
/* 0x60 */
{ "`", "WATCH" },
{ "a", NULL },
{ "b", "TRACE" },
{ "c", "SQLINE" },
{ "d", "UNSQLINE" },
{ "e", "SVSNICK" },
{ "f", "SVSNOOP" },
{ "g", "IDENTIFY" },
{ "h", "SVSKILL" },
{ "i", "NICKSERV" },
{ "j", "CHANSERV" },
{ "k", "OPERSERV" },
{ "l", "MEMOSERV" },
{ "m", "SERVICES" },
{ "n", "SVSMODE" },
{ "o", "SAMODE" },
/* 0x70 */
{ "p", "CHATOPS" },
{ "q", "ZLINE" },
{ "r", "UNZLINE" },
{ "s", "HELPSERV" },
{ "t", "RULES" },
{ "u", "MAP" },
{ "v", "SVS2MODE" },
{ "w", "DALINFO" },
{ "x", "ADCHAT" },
{ "y", "MKPASSWD" },
{ "z", "ADDLINE" },
{ "{", NULL },
{ "|", "UMODE2" },
{ "}", "GLINE" },
{ "~", "SJOIN" },
/* 0x41xx */
{ "AA", "SETHOST" },
{ "AB", "TECHAT" },
{ "AC", "NACHAT" },
{ "AD", "SETIDENT" },
{ "AE", "SETNAME" },
{ "AF", "LAG" },
{ "AG", "SDESC" },
{ "AH", "STATSERV" },
{ "AI", "KNOCK" },
{ "AJ", "CREDITS" },
{ "AK", "LICENSE" },
{ "AL", "CHGHOST" },
{ "AM", "RPING" },
{ "AN", "RPONG" },
{ "AO", "NETINFO" },
{ "AP", "SENDUMODE" },
{ "AQ", "ADDMOTD" },
{ "AR", "ADDOMOTD" },
{ "AS", "SVSMOTD" },
{ "AT", NULL },
{ "AU", "SMO" },
{ "AV", "OPERMOTD" },
{ "AW", "TSCTL" },
{ "AX", "SAJOIN" },
{ "AY", "SAPART" },
{ "AZ", "CHGIDENT" },
/* 0x42xx */
{ "BA", "SWHOIS" },
{ "BB", "SVSO" },
{ "BC", "SVSFLINE" },
{ "BD", "TKL" },
{ "BE", "VHOST" },
{ "BF", "BOTMOTD" },
{ "BG", "REMGLINE" },
{ "BH", "HTM" },
{ "BI", "DCCDENY" },
{ "BJ", "UNDCCDENY" },
{ "BK", "CHGNAME" }, /* also SVSNAME */
{ "BL", "SHUN" },
{ "BM", NULL },
{ "BN", "POST" },
{ "BO", "INFOSERV" },
{ "BP", "CYCLE" },
{ "BQ", "MODULE" },
{ "BR", "SVSJOIN" },
{ "BS", "BOTSERV" },
{ "BT", "SVSPART" },
/* 0x45xx */
{ "ES", "EOS" },
/* 0x53xx */
{ "Ss", "SENDSNO" },
/* Fencepost */
{ NULL }
};
/*************************************************************************/
/************************** IRC message sending **************************/
/*************************************************************************/
/* Send a TSCTL SVSTIME command (internal use). */
static void do_send_svstime(Timeout *to)
{
#ifdef CLEAN_COMPILE
to = to;
#endif
send_cmd(ServerName, "TSCTL SVSTIME %ld", (long)time(NULL));
}
/*************************************************************************/
/* Send a NICK command for a new user. */
static void do_send_nick(const char *nick, const char *user, const char *host,
const char *server, const char *name,
const char *modes)
{
/* NICK <nick> <hops> <TS> <user> <host> <server> <svsid> <mode>
* <fakehost> [<IP>] :<ircname>
* The <IP> parameter is only included if the remote server specified
* NICKIP in its PROTOCTL line.
*
* Note that if SETHOST has not been used, <fakehost> is <host> for VHP
* servers but "*" for others. We send <host> because that works in
* both cases.
*/
send_cmd(NULL, "NICK %s 1 %ld %s %s %s 0 +%s %s%s :%s", nick,
(long)time(NULL), user, host, server, modes, host,
has_nickip ? " *" : "", name);
}
/*************************************************************************/
/* Send a NICK command to change an existing user's nick. */
static void do_send_nickchange(const char *nick, const char *newnick)
{
send_cmd(nick, "NICK %s %ld", newnick, (long)time(NULL));
}
/*************************************************************************/
/* Send a command to change a user's "real name". */
static void do_send_namechange(const char *nick, const char *newname)
{
send_cmd(nick, "SETNAME :%s", newname);
}
/*************************************************************************/
/* Send a SERVER command, and anything else needed at the beginning of the
* connection.
*/
static void do_send_server(void)
{
send_cmd(NULL, "PROTOCTL SJOIN SJOIN2 SJ3 NICKv2 VHP VL NOQUIT UMODE2"
" TOKEN NICKIP");
send_cmd(NULL, "PASS :%s", RemotePassword);
/* Syntax:
* SERVER servername hopcount :U<protocol>-flags-numeric serverdesc
* We use protocol 0, as we are protocol independent, and flags are *,
* to prevent matching with version denying lines. */
send_cmd(NULL, "SERVER %s 1 :U0-*-%d %s", ServerName, ServerNumeric,
ServerDesc);
/* If requested, send TSCTL SVSTIME and start timeout. */
if (SetServerTimes) {
do_send_svstime(NULL);
if (SVSTIMEFrequency)
to_svstime = add_timeout(SVSTIMEFrequency, do_send_svstime, 1);
}
}
/*************************************************************************/
/* Send a SERVER command for a remote (juped) server. */
static void do_send_server_remote(const char *server, const char *reason)
{
send_cmd(NULL, "SERVER %s 2 :%s", server, reason);
}
/*************************************************************************/
/* Send a WALLOPS (really a GLOBOPS). */
static void do_wallops(const char *source, const char *fmt, ...)
{
va_list args;
char buf[BUFSIZE];
va_start(args, fmt);
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
send_cmd(source ? source : ServerName, "GLOBOPS :%s", buf);
}
/*************************************************************************/
/* Send a NOTICE to all users on the network. */
static void do_notice_all(const char *source, const char *fmt, ...)
{
va_list args;
char buf[BUFSIZE];
va_start(args, fmt);
snprintf(buf, sizeof(buf), "NOTICE $* :%s", fmt);
vsend_cmd(source, buf, args);
va_end(args);
}
/*************************************************************************/
/* Send a command which modifies channel status. */
static void do_send_channel_cmd(const char *source, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vsend_cmd(source, fmt, args);
va_end(args);
}
/*************************************************************************/
/******************************* Callbacks *******************************/
/*************************************************************************/
/* Callback for messages received from the uplink server; we use this to
* check for inappropriate servicestamp changes and reverse them.
* Normally, this would be done with a user mode change callback instead,
* but Unreal uses the +d mode without a parameter to mean "deaf", so we
* have to check here for +d with a parameter. We also watch for the
* SERVER message from the uplink server to determine what protocol
* version is in use.
*/
static int do_receive_message(char *source, char *cmd, int ac, char **av)
{
if (((stricmp(cmd, "MODE") == 0 || strcmp(cmd, "G") == 0)
&& ac > 2 && *av[0] != '#' && strchr(av[1],'d'))
|| ((stricmp(cmd, "UMODE2") == 0 || strcmp(cmd, "|") == 0)
&& ac > 1 && strchr(av[0],'d'))
) {
User *user = get_user(*cmd=='U' ? source : av[0]);
if (user) {
module_log("%s tried to change services stamp for %s",
cmd, user->nick);
send_cmd(ServerName, "SVSMODE %s +d %u", user->nick,
user->servicestamp);
}
} else if (stricmp(cmd, "SERVER") == 0) {
const char *s, *t;
int ver;
if (ac < 3) {
module_log("SERVER: not enough parameters");
} else if (*av[2] != 'U'
|| !(s = strchr(av[2], '-'))
|| (ver = strtoul(av[2]+1, (char **)&t, 10), t != s)
) {
module_log("SERVER: bad/missing protocol ID");
} else {
/* Successfully parsed */
unreal_version = ver;
}
}
return 0;
}
/*************************************************************************/
/*
* Callback for new users; we copy the fake hostname parameter from the
* user argument area to the User structure
*/
static int do_user_create(User *user, int ac, char **av)
{
user->fakehost = sstrdup(av[9]);
return 0;
}
/*************************************************************************/
/*
* Callback for users who get assigned new Services stamps: send the change
* out to the network (MODE +d <stamp>).
*/
static int do_user_servicestamp_change(User *user)
{
send_cmd(ServerName, "SVSMODE %s +d %u", user->nick, user->servicestamp);
return 0;
}
/*************************************************************************/
/*
* User mode change callback; we check for inappropriate mode r/a changes
* and reverse them, and give newly-opered identified Services admins +a.
*/
static int do_user_mode(User *user, int modechar, int add, char **av)
{
switch (modechar) {
case 'o':
if (add) {
/* Since is_services_admin() returns true only for opered
* users, and +o is not yet actually set in the User structure,
* we have to set it temporarily to get the information we
* want. We clear it again afterwards because another callback
* may be expecting it to still be unset. */
user->mode |= UMODE_o;
if (user_identified(user) && is_services_admin(user))
send_cmd(ServerName, "SVSMODE %s +a", user->nick);
user->mode &= ~UMODE_o;
}
return 0;
case 'r':
if (user_identified(user)) {
if (!add)
send_cmd(ServerName, "SVSMODE %s +r", user->nick);
} else {
if (add)
send_cmd(ServerName, "SVSMODE %s -r", user->nick);
}
return 1;
case 'a':
if (is_oper(user)) {
if (is_services_admin(user)) {
if (!add)
send_cmd(ServerName, "SVSMODE %s +a", user->nick);
} else {
if (add)
send_cmd(ServerName, "SVSMODE %s -a", user->nick);
}
return 1;
}
}
return 0;
}
/*************************************************************************/
/*
* Channel mode change callback; we handle modes L/f/j here.
*/
static int do_channel_mode(const char *source, Channel *channel,
int modechar, int add, char **av)
{
int32 flag = mode_char_to_flag(modechar, MODE_CHANNEL);
switch (modechar) {
case 'L':
free(channel->link);
if (add) {
channel->mode |= flag;
channel->link = sstrdup(av[0]);
} else {
channel->mode &= ~flag;
channel->link = NULL;
}
return 1;
case 'f':
free(channel->flood);
if (add) {
channel->mode |= flag;
channel->flood = sstrdup(av[0]);
} else {
channel->mode &= ~flag;
channel->flood = NULL;
}
return 1;
case 'j':
if (add) {
int ok = 0;
char *s;
int joinrate1 = strtol(av[0], &s, 0);
if (*s == ':') {
int joinrate2 = strtol(s+1, &s, 0);
if (!*s) {
if (joinrate1 && joinrate2) {
channel->mode |= flag;
channel->joinrate1 = joinrate1;
channel->joinrate2 = joinrate2;
} else {
channel->mode &= ~flag;
channel->joinrate1 = 0;
channel->joinrate2 = 0;
ok = 1;
}
ok = 1;
}
} else if (joinrate1 == 0) {
channel->mode &= ~flag;
channel->joinrate1 = 0;
channel->joinrate2 = 0;
ok = 1;
}
if (!ok) {
module_log("warning: invalid MODE +j %s for %s", av[0],
channel->name);
}
} else {
channel->mode &= ~flag;
channel->joinrate1 = 0;
channel->joinrate2 = 0;
}
return 1;
} /* switch (mode) */
return 0;
}
/*************************************************************************/
/* Callback to handle clearing bans and exceptions for clear_channel().
* Normally, the versions in actions.c and modules/protocol/banexcept.c
* will do the job, but Unreal has "extended ban types" which confuse the
* usermask comparison, so we need to write our own versions of the
* routines. Hooray for feeping creaturism... */
static void unreal_clear_bans_excepts(const char *sender, Channel *chan,
int what, const User *u);
static int do_clear_channel(const char *sender, Channel *chan, int what,
const void *param)
{
if (what & (CLEAR_USERS | CLEAR_BANS | CLEAR_EXCEPTS)) {
unreal_clear_bans_excepts(sender, chan, what,
(what & CLEAR_USERS) ? NULL : param);
}
return 0;
}
static void unreal_clear_bans_excepts(const char *sender, Channel *chan,
int what, const User *u)
{
int i, count;
char **list;
if (what & (CLEAR_USERS | CLEAR_BANS)) {
if (chan->bans_count) {
count = chan->bans_count;
list = smalloc(sizeof(char *) * count);
memcpy(list, chan->bans, sizeof(char *) * count);
for (i = 0; i < count; i++) {
char *s = list[i];
if (*s == '~'
&& ((s[1] == '!' && s[2] && s[3] == ':')
|| (s[1] && s[2] == ':'))
) {
if (s[1] == '!')
s += 4;
else
s += 3;
}
if (!u || match_usermask(s, u))
set_cmode(sender, chan, "-b", list[i]);
if (u && u->ipaddr) {
char tmpbuf[BUFSIZE];
int nicklen = snprintf(tmpbuf, sizeof(tmpbuf), "%s!", u->nick);
snprintf(tmpbuf+nicklen, sizeof(tmpbuf)-nicklen, "%s@%s",
u->username, u->ipaddr);
if (match_wild_nocase(s, tmpbuf))
set_cmode(sender, chan, "-b", list[i]);
if (match_wild_nocase(s, tmpbuf+nicklen))
set_cmode(sender, chan, "-b", list[i]);
}
}
free(list);
}
}
if (what & (CLEAR_USERS | CLEAR_EXCEPTS)) {
if (chan->excepts_count) {
count = chan->excepts_count;
list = smalloc(sizeof(char *) * count);
memcpy(list, chan->excepts, sizeof(char *) * count);
for (i = 0; i < count; i++) {
char *s = list[i];
if (*s == '~'
&& ((s[1] == '!' && s[2] && s[3] == ':')
|| (s[1] && s[3] == ':'))
) {
if (s[1] == '!')
s += 4;
else
s += 3;
}
if (!u || match_usermask(s, u))
set_cmode(sender, chan, "-e", list[i]);
if (u && u->ipaddr) {
char tmpbuf[BUFSIZE];
int nicklen = snprintf(tmpbuf, sizeof(tmpbuf), "%s!", u->nick);
snprintf(tmpbuf+nicklen, sizeof(tmpbuf)-nicklen, "%s@%s",
u->username, u->ipaddr);
if (match_wild_nocase(s, tmpbuf))
set_cmode(sender, chan, "-e", list[i]);
if (match_wild_nocase(s, tmpbuf+nicklen))
set_cmode(sender, chan, "-e", list[i]);
}
}
free(list);
}
}
}
/*************************************************************************/
/*
* NickServ post-IDENTIFY callback; we set user mode +a for Services
* admins if they're opered.
*/
static int do_nick_identified(User *u, int old_status)
{
if (is_oper(u) && is_services_admin(u))
send_cmd(ServerName, "SVSMODE %s +a", u->nick);
return 0;
}
/*************************************************************************/
/*
* Set-topic callback; we use this to adjust timestamps as necessary to
* force a topic change.
*/
static int do_set_topic(const char *source, Channel *c, const char *topic,
const char *setter, time_t t)
{
if (setter)
return 0;
if (t <= c->topic_time)
t = c->topic_time + 1; /* Force topic change */
c->topic_time = t;
send_cmd(source, "TOPIC %s %s %ld :%s", c->name, c->topic_setter,
(long)c->topic_time, c->topic ? c->topic : "");
return 1;
}
/*************************************************************************/
/*
* ChanServ channel mode check callback; we handle setting modes +L, +f,
* and +j. (-L/-f/-j are handled by the default handler, which just sends a
* "MODE -L/-f" as appropriate.)
*/
static int do_check_modes(Channel *c, ChannelInfo *ci, int add, int32 flag)
{
if (add) {
switch (mode_flag_to_char(flag, MODE_CHANNEL)) {
case 'L':
if (!ci->mlock_link) {
module_log("warning: removing +L from channel %s mode lock"
" (missing parameter)", ci->name);
ci->mlock_on &= ~mode_char_to_flag('L', MODE_CHANNEL);
} else {
if (!c->link || irc_stricmp(ci->mlock_link, c->link) != 0)
set_cmode(s_ChanServ, c, "+L", ci->mlock_link);
}
return 1;
case 'f':
if (!ci->mlock_flood) {
module_log("warning: removing +f from channel %s mode lock"
" (missing parameter)", ci->name);
ci->mlock_on &= ~mode_char_to_flag('f', MODE_CHANNEL);
} else {
if (!c->flood || irc_stricmp(ci->mlock_flood, c->flood) != 0)
set_cmode(s_ChanServ, c, "+f", ci->mlock_flood);
}
return 1;
case 'j':
if (sgn(ci->mlock_joinrate1) != sgn(ci->mlock_joinrate2)) {
module_log("warning: removing +j from channel %s mode lock"
" (invalid parameter: %d:%d)", ci->name,
ci->mlock_joinrate1, ci->mlock_joinrate2);
ci->mlock_on &= ~mode_char_to_flag('j', MODE_CHANNEL);
ci->mlock_joinrate1 = ci->mlock_joinrate2 = 0;
} else if (ci->mlock_joinrate1 < 0) {
if (c->joinrate1 || c->joinrate2)
set_cmode(s_ChanServ, c, "-j");
} else {
if (c->joinrate1 != ci->mlock_joinrate1
|| c->joinrate2 != ci->mlock_joinrate2
) {
char buf[BUFSIZE];
snprintf(buf, sizeof(buf), "%d:%d",
ci->mlock_joinrate1, ci->mlock_joinrate2);
set_cmode(s_ChanServ, c, "+j", buf);
}
}
return 1;
} /* switch (modechar) */
} /* if (add) */
return 0;
}
/*************************************************************************/
/*
* Callback for ChanServ channel user mode checking; we prevent all
* automatic mode changes for hiding (+I) users (since the mode changes
* would give away their presence) or service pseudoclients (+S).
*/
static int do_check_chan_user_modes(const char *source, User *user,
Channel *c, int32 modes)
{
/* Don't do anything to service pseudoclients */
if (user->mode & mode_char_to_flag('S', MODE_USER))
return 1;
/* Don't do anything to hiding users either */
return ((user->mode & usermode_hiding) ? 1 : 0);
}
/*************************************************************************/
/*
* Callback for ChanServ channel entrance checking; we enforce admin-only,
* secure-only, and no-hiding channel modes here.
*/
static int do_check_kick(User *user, const char *chan, ChannelInfo *ci,
char **mask_ret, const char **reason_ret)
{
/* Retrieve the channel's Channel record, if present */
Channel *c = get_channel(chan);
/* Don't do anything to service pseudoclients */
if (user->mode & mode_char_to_flag('S', MODE_USER))
return 2;
/* Don't let plain opers into +A (admin only) channels */
if ((((c?c->mode:0) | (ci?ci->mlock_on:0)) & chanmode_admins_only)
&& !(user->mode & usermode_admin)
) {
*mask_ret = create_mask(user, 1);
*reason_ret = getstring(user->ngi, CHAN_NOT_ALLOWED_TO_JOIN);
return 1;
}
/* Don't let users not on secure connections (-z) into +z channels */
if ((((c?c->mode:0) | (ci?ci->mlock_on:0)) & chanmode_secure_only)
&& !(user->mode & usermode_secure)
) {
*mask_ret = create_mask(user, 1);
*reason_ret = getstring(user->ngi, CHAN_NOT_ALLOWED_TO_JOIN);
return 1;
}
/* Don't let hiding users into no-hiding channels */
if ((((c?c->mode:0) | (ci?ci->mlock_on:0)) & chanmode_no_hiding)
&& (user->mode & usermode_hiding)
) {
*mask_ret = create_mask(user, 1);
*reason_ret = getstring(user->ngi, CHAN_NOT_ALLOWED_TO_JOIN);
return 1;
}
/* Let other processing continue as usual */
return 0;
}
/*************************************************************************/
/*
* ChanServ SET MLOCK callback; we handle locking modes f, j, K, and L.
*/
static int do_set_mlock(User *u, ChannelInfo *ci, int mode, int add, char **av)
{
if (!mode) {
/* Final check of new mode lock */
if ((ci->mlock_on & mode_char_to_flag('K',MODE_CHANNEL))
&& !(ci->mlock_on & CMODE_i)
) {
/* +K requires +i */
notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_REQUIRES, 'K', 'i');
return 1;
}
if (ci->mlock_link && !ci->mlock_limit) {
/* +L requires +l */
notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_REQUIRES, 'L', 'l');
return 1;
}
return 0;
}
/* Single mode set/clear */
if (add) {
switch (mode) {
case 'L':
if (!valid_chan(av[0])) {
/* Invalid channel name */
notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_LINK_BAD, mode);
return 1;
}
if (irc_stricmp(av[0], ci->name) == 0) {
/* Trying to link to the same channel */
notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_LINK_SAME, mode);
return 1;
}
ci->mlock_link = sstrdup(av[0]);
break;
case 'f': {
char *s, *t;
/* Legal format for flood mode is:
* "digits:digits"
* or "*digits:digits"
* or "[TYPE,TYPE...]:digits"
* where TYPE is 1-999 followed by one of cjkmnt possibly
* followed by # and (a letter possibly followed by digits) */
s = av[0];
if (*s == '[') {
int ok = 1;
char c;
s++;
do {
if (!strchr("0123456789", *s)) {
ok = 0;
break;
}
t = s;
s += strspn(s, "0123456789");
c = *s;
*s = 0;
if (atoi(t) < 1 || atoi(t) > 999) {
ok = 0;
break;
}
*s = c;
if (!strchr("cjkmnt", *s++)) {
ok = 0;
break;
}
if (*s == '#') {
s++;
if (!strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", *s++)) {
ok = 0;
break;
}
s += strspn(s, "0123456789");
}
if (*s != ',' && *s != ']')
ok = 0;
s++;
} while (ok && s[-1] != ']');
if (ok && *s++ != ':')
ok = 0;
if (ok && s[strspn(s,"0123456789")] == 0) {
/* String is valid */
ci->mlock_flood = sstrdup(av[0]);
break;
}
} else {
if (*s == '*')
s++;
t = strchr(s, ':');
if (t) {
t++;
if (s[strspn(s,"0123456789")] == ':'
&& t[strspn(t,"0123456789")] == 0
) {
/* String is valid */
ci->mlock_flood = sstrdup(av[0]);
break;
}
}
}
/* String is invalid */
notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_BAD_PARAM, mode);
return 1;
} /* case 'f' */
case 'j': {
int ok = 0;
char *s;
ci->mlock_joinrate1 = strtol(av[0], &s, 0);
if (ci->mlock_joinrate1 > 0 && *s == ':') {
ci->mlock_joinrate2 = strtol(s+1, &s, 0);
if (ci->mlock_joinrate2 > 0 && !*s)
ok = 1;
}
if (!ok) {
notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_BAD_PARAM, mode);
return 1;
}
break;
} /* case 'j' */
} /* switch (mode) */
} else { /* !add -> lock off */
switch (mode) {
case 'j':
ci->mlock_joinrate1 = ci->mlock_joinrate2 = -1;
break;
} /* switch (mode) */
} /* if (add) */
return 0;
}
/*************************************************************************/
/*
* Callback for sending autokills; required for autokill-supporting
* protocols.
*/
static int do_send_akill(const char *username, const char *host,
time_t expires, const char *who, const char *reason)
{
send_cmd(ServerName, "TKL + G %s %s %s %ld %ld :%s", username, host,
ServerName, (long)expires, (long)time(NULL), reason);
return 1;
}
/*************************************************************************/
/*
* Callback for removing autokills; required for autokill-supporting
* protocols.
*/
static int do_cancel_akill(const char *username, const char *host)
{
send_cmd(ServerName, "TKL - G %s %s %s", username, host, ServerName);
return 1;
}
/*************************************************************************/
/*
* Callback for sending autokill exclusions; required for
* exclusion-supporting protocols.
*/
static int do_send_exclude(const char *username, const char *host,
time_t expires, const char *who, const char *reason)
{
send_cmd(ServerName, "TKL + E %s %s %s %ld %ld :%s", username, host,
ServerName, (long)expires, (long)time(NULL), reason);
return 1;
}
/*************************************************************************/
/*
* Callback for removing autokill exclusions; required for
* exclusion-supporting protocols.
*/
static int do_cancel_exclude(const char *username, const char *host)
{
send_cmd(ServerName, "TKL - E * %s %s %s", username, host, ServerName);
return 1;
}
/*************************************************************************/
/*
* Callbacks for sending S-lines; required for S*LINE-supporting
* protocols.
*/
static int do_send_sgline(const char *mask, time_t expires, const char *who,
const char *reason)
{
char buf[BUFSIZE], *s;
s = buf;
while (*reason && s-buf < sizeof(buf)-1) {
if (*reason == ' ')
*s = '_';
else
*s = *reason;
reason++;
s++;
}
*s = 0;
send_cmd(ServerName, "SVSNLINE + %s :%s", buf, mask);
return 1;
}
static int do_send_sqline(const char *mask, time_t expires, const char *who,
const char *reason)
{
send_cmd(ServerName, "SQLINE %s :%s", mask, reason);
return 1;
}
static int do_send_szline(const char *mask, time_t expires, const char *who,
const char *reason)
{
send_cmd(ServerName, "TKL + Z * %s %s %ld %ld :%s", mask,
ServerName, (long)expires, (long)time(NULL), reason);
return 1;
}
/*************************************************************************/
/*
* Callbacks for removing S-lines; required for S*LINE-supporting
* protocols.
*/
static int do_cancel_sgline(const char *mask)
{
send_cmd(ServerName, "SVSNLINE - :%s", mask);
return 1;
}
static int do_cancel_sqline(const char *mask)
{
send_cmd(ServerName, "UNSQLINE %s", mask);
return 1;
}
static int do_cancel_szline(const char *mask)
{
send_cmd(ServerName, "TKL - Z * %s %s", mask, ServerName);
return 1;
}
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
/* Module version identifier, required for all modules. */
const int32 module_version = MODULE_VERSION_CODE;
/*
* Configuration directives for this module (required--if the module has
* no configuration directives, this consists of a single NULL entry).
* Note that we include configuration directives for SJOIN which are
* defined in sjoin.h.
*/
ConfigDirective module_config[] = {
{ "ServerNumeric", { { CD_POSINT, 0, &ServerNumeric } } },
{ "SetServerTimes", { { CD_SET, 0, &SetServerTimes },
{ CD_TIME, CF_OPTIONAL, &SVSTIMEFrequency } } },
SJOIN_CONFIG,
{ NULL }
};
/*************************************************************************/
/*
* Load-module callback, used to look up symbols and add callbacks in
* other modules.
*/
static int do_load_module(Module *mod, const char *modname)
{
if (strcmp(modname, "operserv/main") == 0) {
module_operserv = mod;
p_is_services_admin = get_module_symbol(mod, "is_services_admin");
if (!p_is_services_admin) {
module_log("warning: unable to look up symbol `is_services_admin'"
" in module `operserv/main'");
}
} else if (strcmp(modname, "operserv/akill") == 0) {
if (!add_callback(mod, "send_akill", do_send_akill))
module_log("Unable to add send_akill callback");
if (!add_callback(mod, "cancel_akill", do_cancel_akill))
module_log("Unable to add cancel_akill callback");
if (unreal_version >= 2303) {
if (!add_callback(mod, "send_exclude", do_send_exclude))
module_log("Unable to add send_exclude callback");
if (!add_callback(mod, "cancel_exclude", do_cancel_exclude))
module_log("Unable to add cancel_exclude callback");
}
} else if (strcmp(modname, "operserv/sline") == 0) {
if (!add_callback(mod, "send_sgline", do_send_sgline))
module_log("Unable to add send_sgline callback");
if (!add_callback(mod, "send_sqline", do_send_sqline))
module_log("Unable to add send_sqline callback");
if (!add_callback(mod, "send_szline", do_send_szline))
module_log("Unable to add send_szline callback");
if (!add_callback(mod, "cancel_sgline", do_cancel_sgline))
module_log("Unable to add cancel_sgline callback");
if (!add_callback(mod, "cancel_sqline", do_cancel_sqline))
module_log("Unable to add cancel_sqline callback");
if (!add_callback(mod, "cancel_szline", do_cancel_szline))
module_log("Unable to add cancel_szline callback");
} else if (strcmp(modname, "nickserv/main") == 0) {
if (!add_callback(mod, "identified", do_nick_identified))
module_log("Unable to add NickServ identified callback");
} else if (strcmp(modname, "chanserv/main") == 0) {
module_chanserv = mod;
p_s_ChanServ = get_module_symbol(mod, "s_ChanServ");
if (!p_s_ChanServ) {
/* FIXME: make a generalized logging routine for this problem? */
module_log("Unable to resolve symbol `s_ChanServ' in module"
" `chanserv/main'");
p_s_ChanServ = &ServerName;
}
if (!add_callback(mod, "check_modes", do_check_modes))
module_log("Unable to add ChanServ check_modes callback");
if (!add_callback(mod, "check_chan_user_modes",
do_check_chan_user_modes))
module_log("Unable to add ChanServ check_chan_user_modes"
" callback");
if (!add_callback(mod, "check_kick", do_check_kick))
module_log("Unable to add ChanServ check_kick callback");
if (!add_callback(mod, "SET MLOCK", do_set_mlock))
module_log("Unable to add ChanServ SET MLOCK callback");
}
return 0;
}
/*************************************************************************/
/*
* Unload-module callback, used to clear pointers to symbols in modules
* about to be unloaded.
*/
static int do_unload_module(Module *mod)
{
if (mod == module_operserv) {
module_operserv = NULL;
p_is_services_admin = NULL;
} else if (mod == module_chanserv) {
module_chanserv = NULL;
p_s_ChanServ = &ServerName;
}
return 0;
}
/*************************************************************************/
/* Module initialization routine (required). */
int init_module(Module *module_)
{
unsigned char c;
/* Copy passed-in parameter (module handle) to module-global variable. */
module = module_;
/* Set protocol information variables. */
protocol_name = "Unreal";
protocol_version = "3.1.1+";
protocol_features = PF_SZLINE | PF_SVSJOIN;
protocol_nickmax = 30;
/* Make sure the ServerNumeric configuration setting is valid. This
* check is "<0" instead of "<1" because the numeric may not be
* specified at all; conffile.c will check to ensure that 0 is not
* given. */
if (ServerNumeric < 0 || ServerNumeric > 254) {
config_error(MODULES_CONF, 0,
"ServerNumeric must be in the range 1..254");
return 0;
}
/* Register messages. Note that this must be done before calling
* init_token(), which depends on all needed messages being registered
* (in order to look up message handlers for tokens). */
if (!register_messages(unreal_messages)) {
module_log("Unable to register messages");
return 0;
}
/* Add callbacks. */
if (!add_callback(NULL, "load module", do_load_module)
|| !add_callback(NULL, "unload module", do_unload_module)
|| !add_callback(NULL, "receive message", do_receive_message)
|| !add_callback(NULL, "user create", do_user_create)
|| !add_callback(NULL, "user servicestamp change",
do_user_servicestamp_change)
|| !add_callback(NULL, "user MODE", do_user_mode)
|| !add_callback(NULL, "channel MODE", do_channel_mode)
|| !add_callback(NULL, "clear channel", do_clear_channel)
|| !add_callback(NULL, "set topic", do_set_topic)
) {
module_log("Unable to add callbacks");
return 0;
}
/* Initialize subsystems in separate files. */
if (!init_banexcept(module)
|| !init_chanprot(module)
|| !init_halfop(module)
|| !init_invitemask(module)
|| !init_sjoin(module)
|| !init_svsnick(module)
|| !init_token(module, tokens)
) {
return 0;
}
/* Initialize mode data. */
init_modes();
/* Adjust irc_stricmp() behavior (Unreal treats [ \ ] and { | } as
* distinct, unlike RFC 1459). */
irc_lowertable['['] = '[';
irc_lowertable['\\'] = '\\';
irc_lowertable[']'] = ']';
/* Adjust valid_nick() behavior (Unreal allows all characters from 'A'
* to '}' inclusive both at the beginning and in the middle of a nick). */
for (c = 'A'; c <= '}'; c++)
valid_nick_table[c] = 3;
for (c = 0; c < 32; c++)
valid_chan_table[c] = 0;
valid_chan_table[':'] = 0;
valid_chan_table[160] = 0;
/* Set up function pointers and mode strings for send.c. */
send_nick = do_send_nick;
send_nickchange = do_send_nickchange;
send_namechange = do_send_namechange;
send_server = do_send_server;
send_server_remote = do_send_server_remote;
wallops = do_wallops;
notice_all = do_notice_all;
send_channel_cmd = do_send_channel_cmd;
pseudoclient_modes = "Sqd";
enforcer_modes = "d";
/* Unreal has U:lines, so adjust the "bouncy modes" message. */
setstring(OPER_BOUNCY_MODES, OPER_BOUNCY_MODES_U_LINE);
/* Return success. */
return 1;
}
/*************************************************************************/
/*
* Module cleanup routine (required). We never permit ourselves to be
* unloaded except during final shutdown; thus we don't worry about
* restoring things to their initial state.
*/
int exit_module(int shutdown)
{
if (!shutdown) {
/* Do not allow removal of this module */
return 0;
}
/* Clean things up in reverse order. We don't worry about fixing up
* irc_lowertable and so on because we're shutting down anyway;
* however, we do need to make sure to at least remove all callbacks
* before exiting, to avoid invalid function pointers being called
* (e.g. for the unload module callback). */
if (to_svstime) {
del_timeout(to_svstime);
to_svstime = NULL;
}
exit_token();
exit_svsnick();
exit_sjoin();
exit_invitemask();
exit_halfop();
exit_chanprot();
exit_banexcept();
remove_callback(NULL, "set topic", do_set_topic);
remove_callback(NULL, "clear channel", do_clear_channel);
remove_callback(NULL, "channel MODE", do_channel_mode);
remove_callback(NULL, "user MODE", do_user_mode);
remove_callback(NULL, "user servicestamp change",
do_user_servicestamp_change);
remove_callback(NULL, "user create", do_user_create);
remove_callback(NULL, "receive message", do_receive_message);
remove_callback(NULL, "unload module", do_unload_module);
remove_callback(NULL, "load module", do_load_module);
unregister_messages(unreal_messages);
/* All finished, return success */
return 1;
}
/*************************************************************************/
syntax highlighted by Code2HTML, v. 0.9.1