/* Unreal protocol module for IRC Services. * * IRC Services is copyright (c) 1996-2007 Andrew Church. * E-mail: * 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 * [] : * * 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 * [] : * The parameter is only included if the remote server specified * NICKIP in its PROTOCTL line. * * Note that if SETHOST has not been used, is for VHP * servers but "*" for others. We send 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-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 ). */ 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; } /*************************************************************************/