/* * ================================================================= * Filename: operjoin.c * Description: /join & /sajoin operoverride enhancement * Author: AngryWolf * Documentation: operjoin.txt (comes with the package) * ================================================================= */ #include "config.h" #include "struct.h" #include "common.h" #include "sys.h" #include "numeric.h" #include "msg.h" #include "channel.h" #include #include #include #include #include #ifdef _WIN32 #include #endif #include #include "h.h" #ifdef STRIPBADWORDS #include "badwords.h" #endif #ifdef _WIN32 #include "version.h" #endif /* ================================================================= */ #ifdef ABORT_COMPILATION #undef ABORT_COMPILATION #endif #ifdef NO_OPEROVERRIDE #error "*** NO_OPEROVERRIDE detected ***" #define ABORT_COMPILATION #endif #ifdef OPEROVERRIDE_VERIFY #error "*** OPEROVERRIDE_VERIFY detected ***" #define ABORT_COMPILATION #endif #ifdef ABORT_COMPILATION #error "This module is useless for you; compilation aborted" #endif extern void sendto_one(aClient *to, char *pattern, ...); extern void sendto_realops(char *pattern, ...); extern void sendto_serv_butone_token(aClient *one, char *prefix, char *command, char *token, char *pattern, ...); extern aChannel *get_channel(aClient *, char *, int); #ifndef HOOKTYPE_REHASH_COMPLETE extern char modebuf[MAXMODEPARAMS*2+1], parabuf[504]; #endif #define IsSkoAdmin(sptr) (IsAdmin(sptr) || IsNetAdmin(sptr) || IsSAdmin(sptr)) #define DelOverride(cmd, ovr) if (ovr && CommandExists(cmd)) CmdoverrideDel(ovr); ovr = NULL #define DelHook(x) if (x) HookDel(x); x = NULL #define Sender (ssptr ? ssptr : sptr) /* sender of SJOIN or JOIN */ #define SenderName (ssptr ? ssptr->name : parv[0]) /* name of sender */ static Cmdoverride *AddOverride(char *msg, iFP cb); static int override_sajoin(Cmdoverride *, aClient *, aClient *, int, char *[]); static int override_join(Cmdoverride *, aClient *, aClient *, int, char *[]); static int override_cycle(Cmdoverride *, aClient *, aClient *, int, char *[]); static int m_myjoin(aClient *ssptr, aClient *cptr, aClient *sptr, int parc, char *parv[]); #ifdef HOOKTYPE_REHASH_COMPLETE static int cb_rehash_complete(); #endif static char *PartFmt2 = ":%s PART %s :%s"; Cmdoverride *OvrJoin, *OvrCycle, *OvrSajoin; #ifdef HOOKTYPE_REHASH_COMPLETE #ifndef _WIN32 Hook *HookRehashComplete; #endif aCommand *CmdPart; #endif #ifndef STATIC_LINKING static ModuleInfo *MyModInfo; #define MyMod MyModInfo->handle #define SAVE_MODINFO MyModInfo = modinfo; #else #define MyMod NULL #define SAVE_MODINFO #endif /* ================================================================= */ ModuleHeader MOD_HEADER(operjoin) = { "operjoin", "$Id: operjoin.c,v 5.1 2004/03/23 19:56:37 angrywolf Exp $", "Join & sajoin override", "3.2-b8-1", NULL }; DLLFUNC int MOD_INIT(operjoin)(ModuleInfo *modinfo) { SAVE_MODINFO #if defined(HOOKTYPE_REHASH_COMPLETE) && !defined(_WIN32) HookRehashComplete = HookAddEx(modinfo->handle, HOOKTYPE_REHASH_COMPLETE, cb_rehash_complete); #endif return MOD_SUCCESS; } DLLFUNC int MOD_LOAD(operjoin)(int module_load) { #ifdef HOOKTYPE_REHASH_COMPLETE int ret; #endif OvrJoin = AddOverride("join", override_join); OvrCycle = AddOverride("cycle", override_cycle); OvrSajoin = AddOverride("sajoin", override_sajoin); #ifdef HOOKTYPE_REHASH_COMPLETE ret = cb_rehash_complete(); #endif #ifdef HOOKTYPE_REHASH_COMPLETE if (!OvrJoin || !OvrCycle || !OvrSajoin || !ret) #else if (!OvrJoin || !OvrCycle || !OvrSajoin) #endif return MOD_FAILED; return MOD_SUCCESS; } DLLFUNC int MOD_UNLOAD(operjoin)(int module_unload) { #if defined(HOOKTYPE_REHASH_COMPLETE) && !defined(_WIN32) DelHook(HookRehashComplete); #endif DelOverride("join", OvrJoin); DelOverride("cycle", OvrCycle); DelOverride("sajoin", OvrSajoin); return MOD_SUCCESS; } #ifdef HOOKTYPE_REHASH_COMPLETE static int cb_rehash_complete() { if (!(CmdPart = find_Command_simple("part"))) { config_error("[\2operjoin\2] command PART not found"); return 0; } return 1; } #endif /* ================================================================= */ static Cmdoverride *AddOverride(char *msg, iFP cb) { Cmdoverride *ovr = CmdoverrideAdd(MyMod, msg, cb); #ifndef STATIC_LINKING if (ModuleGetError(MyMod) != MODERR_NOERROR || !ovr) #else if (!ovr) #endif { #ifndef STATIC_LINKING config_error("Error replacing command %s when loading module %s: %s", msg, MOD_HEADER(operjoin).name, ModuleGetErrorStr(MyMod)); #else config_error("Error replacing command %s when loading module %s", msg, MOD_HEADER(operjoin).name); #endif return NULL; } return ovr; } /* ================================================================= */ static u_int is_invited(aClient *sptr, aChannel *chptr) { Link *lp; for (lp = sptr->user->invited; lp; lp = lp->next) if (lp->value.chptr == chptr) return 1; return 0; } static void send_override_notice(aClient *ssptr, aClient *sptr, aChannel *chptr, char mode) { if (ssptr) { /* * Complication between sendto_realops and sendto_snomask, when an IRCop * uses SAJOIN that overrides a specific channel mode. Actually * OperOverride messages go to EYES snomask, but SJOIN notices go to all IRCops. * Someone not having snomask +e may get confused in this situation. */ sendto_serv_butone_token(&me, me.name, MSG_GLOBOPS, TOK_GLOBOPS, ":*** OperOverride -- %s (%s@%s) used SAJOIN to make %s join %s (overriding +%c).", ssptr->name, ssptr->user->username, ssptr->user->realhost, sptr->name, chptr->chname, mode); sendto_realops("*** OperOverride -- %s (%s@%s) used SAJOIN to make %s join %s (overriding +%c).", ssptr->name, ssptr->user->username, ssptr->user->realhost, sptr->name, chptr->chname, mode); #ifdef LOG_OVERRIDE ircd_log(LOG_OVERRIDE, "OVERRIDE: %s (%s@%s) used SAJOIN to make %s join %s (Overriding +%c)", ssptr->name, ssptr->user->username, ssptr->user->realhost, sptr->name, chptr->chname, mode); #endif } else { sendto_snomask(SNO_EYES, "*** OperOverride -- %s (%s@%s) joined %s (overriding +%c).", sptr->name, sptr->user->username, sptr->user->realhost, chptr->chname, mode); #ifdef LOG_OVERRIDE ircd_log(LOG_OVERRIDE, "OVERRIDE: %s (%s@%s) joined %s (Overriding +%c)", sptr->name, sptr->user->username, sptr->user->realhost, chptr->chname, mode); #endif } } static void operoverride_message(aClient *ssptr, aClient *cptr, aClient *sptr, aChannel *chptr, char *key) { u_int over = 1; if (!chptr) /* SAJOIN 0 */ { sendto_serv_butone_token(&me, me.name, MSG_GLOBOPS, TOK_GLOBOPS, ":%s used SAJOIN to make %s part all channels", ssptr->name, sptr->name); sendto_realops("%s used SAJOIN to make %s part all channels", ssptr->name, sptr->name); #ifdef LOG_SACMDS ircd_log(LOG_SACMDS, "SAJOIN: %s used SAJOIN to make %s part all channels", ssptr->name, sptr->name); #endif return; } if (ssptr) sendto_one(sptr, ":%s %s %s :*** You were forced to join %s", me.name, IsWebTV(sptr) ? "PRIVMSG" : "NOTICE", sptr->name, chptr->chname); if (is_invited(sptr, chptr)) over = 0; else { #ifdef EXBTYPE_BAN if (is_banned(sptr, chptr, BANCHK_JOIN)) #else if (is_banned(cptr, sptr, chptr)) #endif send_override_notice(ssptr, sptr, chptr, 'b'); else if (*chptr->mode.key && (BadPtr(key) || strcmp(chptr->mode.key, key))) send_override_notice(ssptr, sptr, chptr, 'k'); else if (chptr->mode.mode & MODE_INVITEONLY) send_override_notice(ssptr, sptr, chptr, 'i'); else if ((chptr->mode.mode & MODE_ONLYSECURE) && !(sptr->umodes & UMODE_SECURE)) send_override_notice(ssptr, sptr, chptr, 'z'); else if (chptr->mode.limit && chptr->users >= chptr->mode.limit) send_override_notice(ssptr, sptr, chptr, 'l'); else if ((chptr->mode.mode & MODE_RGSTRONLY) && !IsARegNick(sptr)) send_override_notice(ssptr, sptr, chptr, 'R'); else over = 0; } if (ssptr && !over) { sendto_serv_butone_token(&me, me.name, MSG_GLOBOPS, TOK_GLOBOPS, ":%s used SAJOIN to make %s join %s", ssptr->name, sptr->name, chptr->chname); sendto_realops("%s used SAJOIN to make %s join %s", ssptr->name, sptr->name, chptr->chname); #ifdef LOG_SACMDS ircd_log(LOG_SACMDS, "SAJOIN: %s used SAJOIN to make %s join %s", ssptr->name, sptr->name, chptr->chname); #endif } } static int oper_can_join(aClient *cptr, aClient *sptr, aChannel *chptr) { Ban *banned; if ((chptr->mode.mode & MODE_ADMONLY) && !IsSkoAdmin(sptr)) return (ERR_ADMONLY); #ifdef EXBTYPE_BAN banned = is_banned(sptr, chptr, BANCHK_JOIN); #else banned = is_banned(cptr, sptr, chptr); #endif /* Admin, Coadmin, Netadmin, and SAdmin can still walk +b in +O */ if (!IsAdmin(sptr) && !IsCoAdmin(sptr) && !IsNetAdmin(sptr) && !IsSAdmin(sptr) && banned && (chptr->mode.mode & MODE_OPERONLY)) return (ERR_BANNEDFROMCHAN); /* Only NetAdmin/SAdmin can walk +b in +A */ if (!IsNetAdmin(sptr) && !IsSAdmin(sptr) && banned && (chptr->mode.mode & MODE_ADMONLY)) return (ERR_BANNEDFROMCHAN); return 0; } /* ================================================================= */ /* * override_join * * Allows IRCops to join a channel by overriding channel rules if needed. */ static int override_join(Cmdoverride *ovr, aClient *cptr, aClient *sptr, int parc, char *parv[]) { /* sender must be at least a global operator with operflag +v */ if (!MyConnect(sptr) || !IsPerson(sptr) || !IsOper(sptr) || !OPCanOverride(sptr)) return CallCmdoverride(ovr, cptr, sptr, parc, parv); else return m_myjoin(NULL, cptr, sptr, parc, parv); } /* * override_cycle * * Uses override_join instead of m_join */ static int override_cycle(Cmdoverride *ovr, aClient *cptr, aClient *sptr, int parc, char *parv[]) { char channels[1024]; #ifdef HOOKTYPE_REHASH_COMPLETE if (!CmdPart) return CallCmdoverride(ovr, cptr, sptr, parc, parv); #endif if (IsServer(sptr)) return 0; if (parc < 2) return 0; parv[2] = "cycling"; strncpyzt(channels, parv[1], 1020); #ifdef HOOKTYPE_REHASH_COMPLETE CmdPart->func(cptr, sptr, 3, parv); #else (void) m_part(cptr, sptr, 3, parv); #endif parv[1] = channels; parv[2] = NULL; return override_join(ovr, cptr, sptr, 2, parv); } /* * override_sajoin * * Same as m_sajoin, but forces a user to join the given channels, even if * not allowed (overrides channel settings). */ static int override_sajoin(Cmdoverride *ovr, aClient *cptr, aClient *sptr, int parc, char *parv[]) { aClient *acptr; if (!IsPerson(sptr) || !IsOper(sptr) || (MyClient(sptr) && !OPCanOverride(sptr))) return CallCmdoverride(ovr, cptr, sptr, parc, parv); if (!IsSAdmin(sptr) && !IsULine(sptr)) { sendto_one(sptr, err_str(ERR_NOPRIVILEGES), me.name, parv[0]); return 0; } if (parc < 3 || BadPtr(parv[2])) { sendto_one(sptr, err_str(ERR_NEEDMOREPARAMS), me.name, parv[0], MSG_SAJOIN); return 0; } if (!(acptr = find_person(parv[1], NULL))) { sendto_one(sptr, err_str(ERR_NOSUCHNICK), me.name, parv[0], parv[1]); return 0; } /* * Notice messages will be sent later (merged into operoverride_message). * With this we can be sure we don't send weird SJOIN notices to opers and * unexpected error messages to the user being forced to join invalid * channels. */ if (MyClient(acptr)) { parv[0] = acptr->name; /* Why parv[1] ? */ parv[1] = parv[2]; m_myjoin(sptr, acptr, acptr, 2, parv); } else sendto_one(acptr, ":%s SAJOIN %s %s", parv[0], parv[1], parv[2]); return 0; } /* * m_myjoin * * Same as do_join, but with heavy modifications. Comments have been kept. * * ssptr: the sender of the sajoin command (local client, can be NULL) * sptr: local client joining channel(s) */ static int m_myjoin(aClient *ssptr, aClient *cptr, aClient *sptr, int parc, char *parv[]) { Membership *lp; aChannel *chptr; char jbuf[BUFSIZE]; char *name, *key = NULL; char *p = NULL, *p2 = NULL; int i, flags = 0; Hook *h; if (parc < 2 || *parv[1] == '\0') { sendto_one(Sender, err_str(ERR_NEEDMOREPARAMS), me.name, SenderName, "JOIN"); return 0; } *jbuf = '\0'; /* ** Rebuild list of channels joined to be the actual result of the ** JOIN. Note that "JOIN 0" is the destructive problem. */ for (i = 0, name = strtoken(&p, parv[1], ","); name; name = strtoken(&p, NULL, ",")) { /* pathological case only on longest channel name. ** If not dealt with here, causes desynced channel ops ** since ChannelExists() doesn't see the same channel ** as one being joined. cute bug. Oct 11 1997, Dianora/comstud ** Copied from Dianora's "hybrid 5" ircd. */ if (strlen(name) > CHANNELLEN) /* same thing is done in get_channel() */ name[CHANNELLEN] = '\0'; clean_channelname(name); if (check_channelmask(Sender, Sender, name) == -1) continue; if (*name == '0' && !atoi(name)) { (void)strcpy(jbuf, "0"); i = 1; continue; } else if (!IsChannelName(name)) { sendto_one(Sender, err_str(ERR_NOSUCHCHANNEL), me.name, SenderName, name); continue; } if (*jbuf) (void)strlcat(jbuf, ",", sizeof jbuf); (void)strlncat(jbuf, name, sizeof jbuf, sizeof(jbuf) - i - 1); i += strlen(name) + 1; } /* This strcpy should be safe since jbuf contains the "filtered" * result of parv[1] which should never be larger than the source. */ (void)strcpy(parv[1], jbuf); p = NULL; if (parv[2]) key = strtoken(&p2, parv[2], ","); parv[2] = NULL; /* for m_names call later, parv[parc] must == NULL */ for (name = strtoken(&p, jbuf, ","); name; key = (key) ? strtoken(&p2, NULL, ",") : NULL, name = strtoken(&p, NULL, ",")) { /* ** JOIN 0 sends out a part for all channels a user ** has joined. */ if (*name == '0' && !atoi(name)) { while ((lp = sptr->user->channel)) { chptr = lp->chptr; sendto_channel_butserv(chptr, sptr, PartFmt2, parv[0], chptr->chname, "Left all channels"); RunHook4(HOOKTYPE_LOCAL_PART, cptr, sptr, chptr, "Left all channels"); if (ssptr) operoverride_message(ssptr, cptr, sptr, NULL, NULL); remove_user_from_channel(sptr, chptr); } sendto_serv_butone_token(cptr, parv[0], MSG_JOIN, TOK_JOIN, "0"); continue; } flags = (ChannelExists(name)) ? CHFL_DEOPPED : CHFL_CHANOP; if (!(chptr = get_channel(sptr, name, CREATE))) continue; if ((lp = find_membership_link(sptr->user->channel, chptr))) { if (ssptr) sendto_one(ssptr, err_str(ERR_USERONCHANNEL), me.name, parv[0], sptr->name, chptr->chname); continue; } if (!ssptr) { #ifdef HOOK_ALLOW i = HOOK_CONTINUE; for (h = Hooks[HOOKTYPE_PRE_LOCAL_JOIN]; h; h = h->next) { i = (*(h->func.intfunc))(sptr,chptr,parv); if (i == HOOK_DENY || i == HOOK_ALLOW) break; } /* Denied, get out now! */ if (i == HOOK_DENY) { /* Rejected... if we just created a new chan we should destroy it too. -- Syzop */ if (!chptr->users) sub1_from_channel(chptr); continue; } /* If they are allowed, don't check can_join */ if (i != HOOK_ALLOW && (i = oper_can_join(cptr, sptr, chptr))) { if (i != -1) sendto_one(sptr, err_str(i), me.name, parv[0], name); continue; } #else if ((i = oper_can_join(cptr, sptr, chptr))) { if (i != -1) sendto_one(sptr, err_str(i), me.name, parv[0], name); continue; } i = 0; for (h = Hooks[HOOKTYPE_PRE_LOCAL_JOIN]; h; h = h->next) { if((*(h->func.intfunc))(sptr,chptr,parv) > 0) { i = 1; break; } } if (i) { /* Rejected... if we just created a new chan we should destroy it too. -- Syzop */ if (!chptr->users) sub1_from_channel(chptr); continue; } #endif } operoverride_message(ssptr, cptr, sptr, chptr, key); #ifdef LOG_OVERRIDE join_channel(chptr, cptr, sptr, flags); #else /* ** Complete user entry to the new channel (if any) */ add_user_to_channel(chptr, sptr, flags); /* ** notify all other users on the new channel */ if (chptr->mode.mode & MODE_AUDITORIUM) { sendto_one(sptr, ":%s!%s@%s JOIN :%s", sptr->name, sptr->user->username, GetHost(sptr), chptr->chname); sendto_chanops_butone(NULL, chptr, ":%s!%s@%s JOIN :%s", sptr->name, sptr->user->username, GetHost(sptr), chptr->chname); } else sendto_channel_butserv(chptr, sptr, ":%s JOIN :%s", parv[0], chptr->chname); sendto_serv_butone_token_opt(cptr, OPT_NOT_SJ3, parv[0], MSG_JOIN, TOK_JOIN, "%s", chptr->chname); #ifdef JOIN_INSTEAD_OF_SJOIN_ON_REMOTEJOIN if (!(flags & CHFL_CHANOP)) sendto_serv_butone_token_opt(cptr, OPT_SJ3, parv[0], MSG_JOIN, TOK_JOIN, "%s", chptr->chname); else { #endif /* I _know_ that the "@%s " look a bit wierd with the space and all .. but its to get around a SJOIN bug --stskeeps */ sendto_serv_butone_token_opt(cptr, OPT_SJ3|OPT_SJB64, me.name, MSG_SJOIN, TOK_SJOIN, "%B %s :%s%s ", chptr->creationtime, chptr->chname, flags & CHFL_CHANOP ? "@" : "", sptr->name); sendto_serv_butone_token_opt(cptr, OPT_SJ3|OPT_NOT_SJB64, me.name, MSG_SJOIN, TOK_SJOIN, "%li %s :%s%s ", chptr->creationtime, chptr->chname, flags & CHFL_CHANOP ? "@" : "", sptr->name); #ifdef JOIN_INSTEAD_OF_SJOIN_ON_REMOTEJOIN } #endif /* ** Make a (temporal) creationtime, if someone joins ** during a net.reconnect : between remote join and ** the mode with TS. --Run */ if (chptr->creationtime == 0) { chptr->creationtime = TStime(); sendto_serv_butone_token(cptr, me.name, MSG_MODE, TOK_MODE, "%s + %lu", chptr->chname, chptr->creationtime); } del_invite(sptr, chptr); if (flags & CHFL_CHANOP) sendto_serv_butone_token_opt(cptr, OPT_NOT_SJ3, me.name, MSG_MODE, TOK_MODE, "%s +o %s %lu", chptr->chname, parv[0], chptr->creationtime); if (chptr->topic) { sendto_one(sptr, rpl_str(RPL_TOPIC), me.name, parv[0], name, chptr->topic); sendto_one(sptr, rpl_str(RPL_TOPICWHOTIME), me.name, parv[0], name, chptr->topic_nick, chptr->topic_time); } if (chptr->users == 1 && MODES_ON_JOIN) { chptr->mode.mode = MODES_ON_JOIN; #ifdef NEWCHFLOODPROT if (iConf.modes_on_join.floodprot.per) { chptr->mode.floodprot = MyMalloc(sizeof(ChanFloodProt)); memcpy(chptr->mode.floodprot, &iConf.modes_on_join.floodprot, sizeof(ChanFloodProt)); } #else chptr->mode.kmode = iConf.modes_on_join.kmode; chptr->mode.per = iConf.modes_on_join.per; chptr->mode.msgs = iConf.modes_on_join.msgs; #endif *modebuf = *parabuf = 0; channel_modes(sptr, modebuf, parabuf, chptr); /* This should probably be in the SJOIN stuff */ sendto_serv_butone_token(&me, me.name, MSG_MODE, TOK_MODE, "%s %s %s %lu", chptr->chname, modebuf, parabuf, chptr->creationtime); sendto_one(sptr, ":%s MODE %s %s %s", me.name, chptr->chname, modebuf, parabuf); } parv[1] = chptr->chname; (void)m_names(cptr, sptr, 2, parv); RunHook4(HOOKTYPE_LOCAL_JOIN, cptr, sptr, chptr, parv); #ifdef NEWCHFLOODPROT /* I'll explain this only once: * 1. if channel is +f * 2. local client OR synced server * 3. then, increase floodcounter * 4. if we reached the limit AND only if source was a local client.. do the action (+i). * Nr 4 is done because otherwise you would have a noticeflood with 'joinflood detected' * from all servers. */ if (chptr->mode.floodprot && (MyClient(sptr) || sptr->srvptr->serv->flags.synced) && do_chanflood(chptr->mode.floodprot, FLD_JOIN) && MyClient(sptr)) { do_chanflood_action(chptr, FLD_JOIN, "join"); } #endif #endif } return 0; }