/* * IRC Services is copyright (c) 1996-2005 Andrew Church. * InspIRCd 1.1 protocol module for IRC Services. * Originally developed by C. J. Edwards (Brain) * Modified for InspIRCd 1.1 by Jamie Penman-Smithson * Fixed by w00t */ #include "services.h" #include "modules.h" #include "conffile.h" #include "language.h" #include "messages.h" #include "timeout.h" #include "version.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" /*************************************************************************/ /* 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; /* 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). */ /*************************************************************************/ /****************** 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) static char **p_s_OperServ = &ServerName; #define s_OperServ (*p_s_OperServ) /*************************************************************************/ /* * 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. */ /*************************************************************************/ /************************** 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; }; /*************************************************************************/ /* New user modes: */ static const struct modedata_init new_usermodes[] = { {'g', {0x00000008}}, /* Receive globops */ {'h', {0x00000010}}, /* Helpop */ {'r', {0x00000020,0,0,0,MI_REGISTERED}}, {'x', {0x00000400}}, /* Mask hostname */ }; /*************************************************************************/ /* * 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 */ {'K', {0x00008000,0,0}}, /* No knocks */ {'V', {0x00010000,0,0}}, /* No invites */ {'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" */ {'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}}, /* Anti-rejoin mode */ {'f', {0x10000000,1,0}}, /* Anti-flooding */ {'j', {0x20000000,1,0}}, /* Anti-joinflood */ {'P', {0x40000000,0,0}}, /* Anti-CAPS */ {'g', {0x80000000,1,1,0,MI_MULTIPLE}}, /* Chanfilter */ }; /*************************************************************************/ /* 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; for (i = 0; i < lenof(new_chanmodes); i++) chanmodes[new_chanmodes[i].mode] = new_chanmodes[i].data; for (i = 0; i < lenof(new_chanusermodes); i++) chanusermodes[new_chanusermodes[i].mode] = new_chanusermodes[i].data; mode_setup(); }; /*************************************************************************/ /************************* IRC message receiving *************************/ /*************************************************************************/ static void m_sanick(char *source, int ac, char **av) { char *newav[10]; if (ac == 2 && valid_nick(av[1])) { newav[0] = av[1]; do_nick(av[0], 1, newav); } } static void m_sajoin(char *source, int ac, char **av) { char *newav[10]; if (ac > 1 && valid_chan(av[1])) { newav[0] = av[1]; do_join(av[0], 1, newav); } } static void m_sapart(char *source, int ac, char **av) { char *newav[10]; if (ac > 1 && valid_chan(av[1])) { newav[0] = av[1]; do_part(av[0], 1, newav); } } /* Handler for the NICK message. */ static void m_nick(char *source, int ac, char **av) { char *newav[10]; /* Old user changing nicks. */ if (ac == 1) { do_nick(source, ac, av); return; } /* * New user. InspIRCd's NICK message takes 8 parameters: * * : NICK + : */ if (ac != 8) { module_log("debug: NICK message: wrong number of parameters (%d) for new user", ac); return; } /* 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[1]; newav[1] = "1"; newav[2] = av[0]; newav[3] = av[4]; newav[4] = av[2]; newav[5] = source; newav[6] = av[7]; newav[7] = "0"; newav[8] = av[6]; newav[9] = av[3]; if (do_nick("", 10, newav)) { /* The user was accepted; set modes. */ newav[1] = av[5]; do_umode(newav[0], 2, newav); } } /*************************************************************************/ /*************************************************************************/ /* SETHOST/CHGHOST message handlers. */ static void m_fhost(char *source, int ac, char **av) { User *u; if (ac != 1) return; u = get_user(source); if (!u) { module_log("m_fhost: user record for %s not found", source); return; } free(u->fakehost); u->fakehost = sstrdup(av[0]); } /*************************************************************************/ /* SETNAME/CHGNAME message handlers. */ static void m_fname(char *source, int ac, char **av) { User *u; if (ac != 1) return; u = get_user(source); if (!u) { module_log("m_fname: user record for %s not found", source); return; } free(u->realname); u->realname = sstrdup(av[0]); } /* * source = server's hub; !*source indicates this is our hub. * av[0] = server's name */ static void m_server(char *source, int ac, char **av) { if (ac < 4) return; if (*av[2] > '0') { do_server("",1,av); } else { do_server(source,1,av); } } /*************************************************************************/ /* FJOIN 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_fjoin(char *source, int ac, char **av) { /* ircservices likes to reorder it's parameters... *sigh* */ char* newav[10]; /* storing the current nick */ char *curnick; /* these are used to generate the final string that is passed to ircservices' core */ int nlen = 0; char nicklist[514]; /* temporary buffer */ char prefixandnick[60]; /* temporary pointer, not used except for strtok_r */ char *lasts; *nicklist = '\0'; *prefixandnick = '\0'; if (ac < 3) { module_log("debug: SJOIN: expected >=3 params, got %d", ac); return; } if (debug) module_log("fjoin: got %s", av[2]); curnick = strtok_r(av[2], " ", &lasts); while (curnick != NULL) { /* curnick should be something like '@%,w00t' */ if (debug) module_log("fjoin: processing user %s", curnick); for (; *curnick; curnick++) { if (debug) module_log("fjoin: examining char %c", *curnick); int32 flag = cumode_prefix_to_flag(*curnick); if (flag != 0) { if (debug) module_log("fjoin: it's a prefix char"); prefixandnick[nlen++] = *curnick; continue; } else { if (debug) module_log("fjoin: not a prefix char :("); if (*curnick == ',') { if (debug) module_log("fjoin: end of prefixes reached! they are %s", prefixandnick); curnick++; strncpy(prefixandnick + nlen, curnick, sizeof(prefixandnick) - nlen); if (debug) module_log("fjoin: prefix and nickname is %s", prefixandnick); break; } else { // no such prefix if (debug) module_log("fjoin: unrecognised prefix: %c", *curnick); } } } if (debug) module_log("fjoin: appending to nicklist, which is currently %s", nicklist); /* now append to final nicklist */ strncat(nicklist, prefixandnick, 513); /* also add a space, for ircservices' core */ strncat(nicklist, " ", 513); /* and reset */ if (debug) module_log("fjoin: post-append, nicklist is %s", nicklist); curnick = strtok_r(NULL, " ", &lasts); nlen = 0; } /* * ircservices expects it's parameters in a certain order (ugh) * fudge the parameters here to keep ircservices sjoin code happy */ newav[0] = av[1]; /* timestamp */ newav[1] = av[0]; /* channel name */ newav[2] = "+"; /* channel modes */ newav[3] = nicklist; do_sjoin("", 4, newav); } /*************************************************************************/ static void m_join(char *source, int ac, char **av) { /* JOIN has two parameters, not one: JOIN */ if (ac != 2) return; do_join(source, ac, av); } /*************************************************************************/ /* SVSMODE message handler. */ static void m_fmode(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); } // :bar.example.com FMODE #channel 1157671943 +a foo /* munge and remove TS */ int size = ac-1; char* newav[size]; newav[0] = av[0]; newav[1] = av[2]; int i = 0; int j = 2; for (i = 3; i <= ac; i++) { newav[j] = av[i]; j++; } do_cmode("", size, newav); } else { if (ac < 2) return; do_umode(source, ac, av); } } static void m_opertype(char *source, int ac, char **av) { /* * OPERTYPE is equivalent to :user MODE user +o */ char* newav[2]; newav[0] = source; newav[1] = "+o"; User* u = get_user(source); if (u && !is_oper(u)) do_umode(source, 2, newav); } /* Taken from how hybrid does it */ static void m_inspircd_topic(char* source, int ac, char **av) { char *newav[4]; char timebuf[32]; if (ac != 2) return; newav[0] = av[0]; newav[1] = source; snprintf(timebuf, sizeof(timebuf), "%ld", (long)time(NULL)); newav[2] = timebuf; newav[3] = av[1]; do_topic(source, 4, newav); } static void m_ftopic(char *source, int ac, char **av) { char *newav[4]; if (ac != 4) return; newav[0] = av[0]; newav[1] = av[2]; newav[2] = av[1]; newav[3] = av[3]; do_topic(source, 4, newav); } static void m_idle(char* source, int ac, char **av) { if (ac == 1) { send_cmd(av[0],"IDLE %s %lu 0", source, (unsigned long)time(NULL)); } } static void m_endburst(char* source, int ac, char **av) { /* Later versions of InspIRCd dont apply G/Z/K lines until * they receive an ENDBURST to indicate the end of the other server's * burst. Therefore, we wait for the uplink to tell us it's done, and * then we tell it we're done too (we might not be, but meh). */ if (!ac) { send_cmd(NULL,"ENDBURST"); } } static void m_aes(char* source, int ac, char **av) { /* Abort if we see this! */ send_error("Cannot link to ircservices using AES!"); strscpy(quitmsg, "Remote server enabled AES, which we don't support.", sizeof(quitmsg)); quitting = 1; } static int has_servicesmod = 0; static int has_globopsmod = 0; static void m_capab(char* source, int ac, char **av) { /* Check they have m_globops.so in their capab list, * if they dont, abort. */ if (strcasecmp(av[0], "START") == 0) { /* reset CAPAB */ has_servicesmod = 0; has_globopsmod = 0; } else if (strcasecmp(av[0], "CAPABILITIES") == 0) { /* check for ident length */ } else if (strcasecmp(av[0], "MODULES") == 0) { if (strstr(av[1],"m_globops.so")) { has_globopsmod = 1; } if (strstr(av[1], "m_services.so")) { has_servicesmod = 1; } } else if (strcasecmp(av[0], "END") == 0) { if (has_globopsmod == 0) { send_error("m_globops.so is not loaded. This is required by ircservices"); strscpy(quitmsg, "Remote server does not have the m_globops.so module loaded, and this is required.", sizeof(quitmsg)); quitting = 1; } if (has_servicesmod == 0) { send_error("m_services.so is not loaded. This is required by ircservices"); strscpy(quitmsg, "Remote server does not have the m_services.so module loaded, and this is required.", sizeof(quitmsg)); quitting = 1; } } else { send_error("m_capab(): unknown CAPAB type %s - out of date protocol module?", av[0]); } } static void m_time(char* source, int ac, char **av) { /* Send back our local time * * : TIME * * This might actually be targetted at a server 'beyond' our own, * but because the only servers 'beyond' services are possible jupes, * its perfectly safe for us to answer all TIME queries as if they * were targetted at us, as services doesn't do routing and has only * the one uplink. */ if (ac == 2) { send_cmd(ServerName,"TIME %s %s %lu", source, av[1], (unsigned long)time(NULL)); } } /*************************************************************************/ /* List of all InspIRCd-specific messages (handlers defined above). */ static Message inspircd_messages[] = { { "JOIN", m_join }, { "FNAME", m_fname }, { "ENDBURST", m_endburst }, { "GLOBOPS", NULL }, { "CHGHOST", NULL }, { "CHGNAME", NULL }, { "AES", m_aes }, { "CAPAB", m_capab }, { "NICK", m_nick }, { "SANICK", m_sanick }, { "SAJOIN", m_sajoin }, { "SAPART", m_sapart }, { "TIME", m_time }, { "SAQUIT", NULL }, { "WATCH", NULL }, { "FHOST", m_fhost }, { "SETNAME", m_fname }, { "SILENCE", NULL }, { "FJOIN", m_fjoin }, { "SAMODE", m_fmode }, { "OPERTYPE", m_opertype }, { "FMODE", m_fmode }, { "GLINE", NULL }, { "ADDLINE", NULL }, { "SERVER", m_server }, { "ZLINE", NULL }, { "ELINE", NULL }, { "QLINE", NULL }, { "KLINE", NULL }, { "PUSH", NULL }, { "VERSION", NULL }, { "FTOPIC", m_ftopic }, { "TOPIC", m_inspircd_topic }, { "METADATA", NULL }, { "IDLE", m_idle }, { "OPERQUIT", NULL }, { NULL } }; /*************************************************************************/ /************************** IRC message sending **************************/ /*************************************************************************/ /* 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 */ send_cmd(server, "NICK %ld %s %s %s %s +%s 0.0.0.0 :%s",(long)time(NULL),nick,host,host,user,modes,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", newnick); } /*************************************************************************/ /* Send a command to change a user's "real name". */ static void do_send_namechange(const char *nick, const char *newname) { send_cmd(nick, "FNAME :%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, "SERVER %s %s 0 :%s", ServerName, RemotePassword, ServerDesc); send_cmd(NULL, "BURST"); send_cmd(ServerName, "VERSION :ircservices-%s %s :%s", version_number, ServerName, version_build); } /*************************************************************************/ /* Send a SERVER command for a remote (juped) server. */ static void do_send_server_remote(const char *server, const char *reason) { send_cmd(ServerName, "SERVER %s * 1 :%s", server, reason); send_cmd(ServerName, "VERSION :ircservices-%s %s :%s", version_number, ServerName, version_build); } /*************************************************************************/ /* 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 : s_OperServ, "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 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; } /*************************************************************************/ /* * User mode change callback; we check for inappropriate mode +-r changes * and reverse them. */ static int do_user_mode(User *user, int modechar, int add, char **av) { switch (modechar) { 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; } return 0; } /*************************************************************************/ /* * Channel mode change callback; we handle modes L/f/j/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) { char* s; channel->mode |= flag; channel->joindelay = strtol(av[0], &s, 0); } else { channel->mode &= ~flag; channel->joindelay = 0; } 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; } /*************************************************************************/ /* * NickServ post-IDENTIFY callback */ static int do_nick_identified(User *u, int old_status) { 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 + 10; /* Force topic change */ c->topic_time = t; send_cmd(s_ChanServ, "FTOPIC %s %ld %s :%s", c->name, (long)c->topic_time, c->topic_setter, c->topic ? c->topic : ""); return 1; } /*************************************************************************/ /* * ChanServ channel mode check callback; we handle setting mode +L. */ 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 (!ci->mlock_joindelay) { module_log("warning: removing +J from channel %s mode lock" " (missing parameter)", ci->name); ci->mlock_on &= ~mode_char_to_flag('J', MODE_CHANNEL); } else { if (!c->joindelay || ci->mlock_joindelay != c->joindelay) { char buf[BUFSIZE]; snprintf(buf, sizeof(buf), "%d", ci->mlock_joindelay); set_cmode(s_ChanServ, c, "+J", buf); } } 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; } /*************************************************************************/ static int do_check_chan_user_modes(const char *source, User *user, Channel *c, int32 modes) { return 0; } /*************************************************************************/ /* * Callback for ChanServ channel entrance checking */ 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 */ return 0; } /*************************************************************************/ /* * ChanServ SET MLOCK callback; we handle locking modes 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 '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': { int ok = 0; char *s; ci->mlock_joindelay = strtol(av[0], &s, 0); if (ci->mlock_joindelay > 0) { ok = 1; } if (!ok) { notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_BAD_PARAM, mode); return 1; } break; } case 'f': { int ok = 0; char *s; int r1, r2; r1 = strtol(av[0], &s, 0); if (r1 > 0 && *s == ':') { r2 = strtol(s+1, &s, 0); if (r2 > 0 && !*s) ok = 1; } if (!ok) { notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_BAD_PARAM, mode); return 1; } ci->mlock_flood = strdup(av[0]); break; } /* case 'f' */ } /* switch (mode) */ } else { switch (mode) { case 'j': ci->mlock_joinrate1 = ci->mlock_joinrate2 = -1; break; case 'J': ci->mlock_joindelay = -1; break; } /* switch (mode) */ } 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, "ADDLINE G %s@%s %s %ld %ld :%s", username, host, who, (long)time(NULL), expires ? (long)(expires-time(NULL)) : 0, 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(s_OperServ, "GLINE %s@%s", username, host); 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, "ADDLINE E %s@%s %s %ld %ld :%s", username, host, who, (long)time(NULL), expires ? (long)(expires-time(NULL)) : 0, 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(s_OperServ, "ELINE %s@%s", username, host); 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) { return 1; } static int do_send_sqline(const char *mask, time_t expires, const char *who, const char *reason) { send_cmd(ServerName, "ADDLINE Q %s %s %ld %ld :%s", mask,who,(long)time(NULL),expires ? (long)(expires-time(NULL)) : 0, reason); return 1; } static int do_send_szline(const char *mask, time_t expires, const char *who, const char *reason) { send_cmd(ServerName, "ADDLINE Z %s %s %ld %ld :%s",mask,who,(long)time(NULL),expires ? (long)(expires-time(NULL)) : 0, reason); return 1; } /*************************************************************************/ /* * Callbacks for removing S-lines; required for S*LINE-supporting * protocols. */ static int do_cancel_sgline(const char *mask) { return 1; } static int do_cancel_sqline(const char *mask) { send_cmd(s_OperServ, "QLINE %s", mask); return 1; } static int do_cancel_szline(const char *mask) { send_cmd(s_OperServ, "ZLINE %s", mask); 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[] = { 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_s_OperServ = get_module_symbol(mod, "s_OperServ"); if (!p_s_OperServ) { module_log("Unable to resolve symbol `s_OperServ' in module `chanserv/main'"); p_s_OperServ = &ServerName; } } 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 (!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_s_OperServ = &ServerName; } 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 = "InspIRCd"; protocol_version = "1.1.x"; protocol_features = PF_HALFOP | PF_CHANPROT | PF_SZLINE | PF_SVSJOIN | PF_NOQUIT | PF_CHANGENICK | PF_AKILL_EXCL; protocol_nickmax = 31; if (!register_messages(inspircd_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, "user create", do_user_create) || !add_callback(NULL, "user MODE", do_user_mode) || !add_callback(NULL, "channel MODE", do_channel_mode) || !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) ) { module_log("Unable to initialize subsystems"); return 0; } /* Initialize mode data. */ init_modes(); /* Adjust valid_nick() behavior (InspIRCd 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; /* 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 = "i"; enforcer_modes = "i"; /* 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). */ exit_svsnick(); exit_sjoin(); exit_invitemask(); exit_halfop(); exit_chanprot(); exit_banexcept(); remove_callback(NULL, "set topic", do_set_topic); remove_callback(NULL, "channel MODE", do_channel_mode); remove_callback(NULL, "user MODE", do_user_mode); remove_callback(NULL, "user create", do_user_create); remove_callback(NULL, "unload module", do_unload_module); remove_callback(NULL, "load module", do_load_module); unregister_messages(inspircd_messages); /* All finished, return success */ return 1; } /*************************************************************************/