/* ChanServ functions. * * (C) 2003-2011 Anope Team * Contact us at team@anope.org * * Please read COPYING and README for further details. * * Based on the original code of Epona by Lara. * Based on the original code of Services by Andy Church. */ /*************************************************************************/ #include "services.h" #include "modules.h" registered_channel_map RegisteredChannelList; static int def_levels[][2] = { { CA_AUTOOP, 5 }, { CA_AUTOVOICE, 3 }, { CA_NOJOIN, -2 }, { CA_INVITE, 5 }, { CA_AKICK, 10 }, { CA_SET, ACCESS_QOP }, { CA_UNBAN, 5 }, { CA_OPDEOP, 5 }, { CA_ACCESS_LIST, 1 }, { CA_ACCESS_CHANGE, 10 }, { CA_MEMO, 10 }, { CA_ASSIGN, ACCESS_FOUNDER }, { CA_BADWORDS, 10 }, { CA_NOKICK, 1 }, { CA_FANTASIA, 3 }, { CA_SAY, 5 }, { CA_GREET, 5 }, { CA_VOICEME, 3 }, { CA_VOICE, 5 }, { CA_GETKEY, 5 }, { CA_AUTOHALFOP, 4 }, { CA_AUTOPROTECT, 10 }, { CA_OPDEOPME, 5 }, { CA_HALFOPME, 4 }, { CA_HALFOP, 5 }, { CA_PROTECTME, 10 }, { CA_PROTECT, ACCESS_QOP }, { CA_KICKME, 5 }, { CA_KICK, 5 }, { CA_SIGNKICK, ACCESS_FOUNDER }, { CA_BANME, 5 }, { CA_BAN, 5 }, { CA_TOPIC, ACCESS_FOUNDER }, { CA_MODE, ACCESS_FOUNDER }, { CA_INFO, ACCESS_QOP }, { CA_AUTOOWNER, ACCESS_QOP }, { CA_OWNER, ACCESS_FOUNDER }, { CA_OWNERME, ACCESS_QOP }, { CA_FOUNDER, ACCESS_QOP }, { -1 } }; LevelInfo levelinfo[] = { { CA_AUTOHALFOP, "AUTOHALFOP", _("Automatic mode +h") }, { CA_AUTOOP, "AUTOOP", _("Automatic channel operator status") }, { CA_AUTOPROTECT, "AUTOPROTECT", _("Automatic mode +a") }, { CA_AUTOVOICE, "AUTOVOICE", _("Automatic mode +v") }, { CA_NOJOIN, "NOJOIN", _("Not allowed to join channel") }, { CA_SIGNKICK, "SIGNKICK", _("No signed kick when SIGNKICK LEVEL is used") }, { CA_ACCESS_LIST, "ACC-LIST", _("Allowed to view the access list") }, { CA_ACCESS_CHANGE, "ACC-CHANGE", _("Allowed to modify the access list") }, { CA_AKICK, "AKICK", _("Allowed to use AKICK command") }, { CA_SET, "SET", _("Allowed to use SET command (not FOUNDER/PASSWORD)"), }, { CA_BAN, "BAN", _("Allowed to use BAN command") }, { CA_BANME, "BANME", _("Allowed to ban him/herself") }, { CA_GETKEY, "GETKEY", _("Allowed to use GETKEY command") }, { CA_HALFOP, "HALFOP", _("Allowed to (de)halfop him/herself") }, { CA_HALFOPME, "HALFOPME", _("Allowed to (de)halfop him/herself") }, { CA_INFO, "INFO", _("Allowed to use INFO command with ALL option") }, { CA_KICK, "KICK", _("Allowed to use KICK command") }, { CA_KICKME, "KICKME", _("Allowed to kick him/herself") }, { CA_INVITE, "INVITE", _("Allowed to use INVITE command") }, { CA_OPDEOP, "OPDEOP", _("Allowed to use OP/DEOP commands") }, { CA_OPDEOPME, "OPDEOPME", _("Allowed to (de)op him/herself") }, { CA_PROTECT, "PROTECT", _("Allowed to use PROTECT/DEPROTECT commands") }, { CA_PROTECTME, "PROTECTME", _("Allowed to (de)protect him/herself"), }, { CA_TOPIC, "TOPIC", _("Allowed to use TOPIC command") }, { CA_MODE, "MODE", _("Allowed to use MODE command") }, { CA_UNBAN, "UNBAN", _("Allowed to use UNBAN command") }, { CA_VOICE, "VOICE", _("Allowed to use VOICE/DEVOICE commands") }, { CA_VOICEME, "VOICEME", _("Allowed to (de)voice him/herself") }, { CA_MEMO, "MEMO", _("Allowed to list/read channel memos") }, { CA_ASSIGN, "ASSIGN", _("Allowed to assign/unassign a bot") }, { CA_BADWORDS, "BADWORDS", _("Allowed to use BADWORDS command") }, { CA_FANTASIA, "FANTASIA", _("Allowed to use fantaisist commands") }, { CA_GREET, "GREET", _("Greet message displayed") }, { CA_NOKICK, "NOKICK", _("Never kicked by the bot's kickers") }, { CA_SAY, "SAY", _("Allowed to use SAY and ACT commands") }, { CA_AUTOOWNER, "AUTOOWNER", _("Automatic mode +q") }, { CA_OWNER, "OWNER", _("Allowed to use OWNER command") }, { CA_OWNERME, "OWNERME", _("Allowed to (de)owner him/herself") }, { CA_FOUNDER, "FOUNDER", _("Allowed to issue commands restricted to channel founders") }, { -1 } }; int levelinfo_maxwidth = 0; /*************************************************************************/ /* Returns modes for mlock in a nice way. */ Anope::string get_mlock_modes(ChannelInfo *ci, int complete) { if (!ci) return ""; Anope::string pos = "+", neg = "-", params; for (std::multimap::const_iterator it = ci->GetMLock().begin(), it_end = ci->GetMLock().end(); it != it_end; ++it) { const ModeLock &ml = it->second; ChannelMode *cm = ModeManager::FindChannelModeByName(ml.name); if (!cm || cm->Type == MODE_LIST || cm->Type == MODE_STATUS) continue; if (ml.set) pos += cm->ModeChar; else neg += cm->ModeChar; if (complete && !ml.param.empty() && cm->Type == MODE_PARAM) params += " " + ml.param; } if (pos.length() == 1) pos.clear(); if (neg.length() == 1) neg.clear(); return pos + neg + params; } /*************************************************************************/ /* Return information on memory use. Assumes pointers are valid. */ void get_chanserv_stats(long *nrec, long *memuse) { long count = 0, mem = 0; ModeLock *ml; for (registered_channel_map::const_iterator it = RegisteredChannelList.begin(), it_end = RegisteredChannelList.end(); it != it_end; ++it) { ChannelInfo *ci = it->second; ++count; mem += sizeof(*ci); if (!ci->desc.empty()) mem += ci->desc.length() + 1; mem += ci->GetAccessCount() * sizeof(ChanAccess); mem += ci->GetAkickCount() * sizeof(AutoKick); ml = ci->GetMLock(CMODE_KEY); if (ml && !ml->param.empty()) mem += ml->param.length() + 1; ml = ci->GetMLock(CMODE_FLOOD); if (ml && !ml->param.empty()) mem += ml->param.length() + 1; ml = ci->GetMLock(CMODE_REDIRECT); if (ml && !ml->param.empty()) mem += ml->param.length() + 1; if (!ci->last_topic.empty()) mem += ci->last_topic.length() + 1; if (!ci->forbidby.empty()) mem += ci->forbidby.length() + 1; if (!ci->forbidreason.empty()) mem += ci->forbidreason.length() + 1; if (ci->levels) mem += sizeof(*ci->levels) * CA_SIZE; unsigned memos = ci->memos.memos.size(); mem += memos * sizeof(Memo); for (unsigned j = 0; j < memos; ++j) if (!ci->memos.memos[j]->text.empty()) mem += ci->memos.memos[j]->text.length() + 1; if (ci->ttb) mem += sizeof(*ci->ttb) * TTB_SIZE; mem += ci->GetBadWordCount() * sizeof(BadWord); } *nrec = count; *memuse = mem; } /*************************************************************************/ /*************************************************************************/ /* ChanServ initialization. */ void cs_init() { if (!Config->s_ChanServ.empty()) ModuleManager::LoadModuleList(Config->ChanServCoreModules); } /*************************************************************************/ /* Check the current modes on a channel; if they conflict with a mode lock, * fix them. */ void check_modes(Channel *c) { if (!c) { Log() << "check_modes called with NULL values"; return; } if (c->bouncy_modes) return; /* Check for mode bouncing */ if (c->server_modecount >= 3 && c->chanserv_modecount >= 3) { ircdproto->SendGlobops(NULL, "Warning: unable to set modes on channel %s. Are your servers' U:lines configured correctly?", c->name.c_str()); Log() << "Bouncy modes on channel " << c->name; c->bouncy_modes = 1; return; } if (c->chanserv_modetime != Anope::CurTime) { c->chanserv_modecount = 0; c->chanserv_modetime = Anope::CurTime; } c->chanserv_modecount++; /* Check if the channel is registered; if not remove mode -r */ ChannelInfo *ci = c->ci; if (!ci) { if (c->HasMode(CMODE_REGISTERED)) c->RemoveMode(NULL, CMODE_REGISTERED); return; } for (std::multimap::const_iterator it = ci->GetMLock().begin(), it_end = ci->GetMLock().end(); it != it_end; ++it) { const ModeLock &ml = it->second; ChannelMode *cm = ModeManager::FindChannelModeByName(ml.name); if (!cm) continue; if (cm->Type == MODE_REGULAR) { if (!c->HasMode(cm->Name) && ml.set) c->SetMode(NULL, cm); else if (c->HasMode(cm->Name) && !ml.set) c->RemoveMode(NULL, cm); } else if (cm->Type == MODE_PARAM) { Anope::string param; c->GetParam(cm->Name, param); /* If the channel doesnt have the mode, or it does and it isn't set correctly */ if (ml.set) { if (!c->HasMode(cm->Name) || (!param.empty() && !ml.param.empty() && !param.equals_cs(ml.param))) c->SetMode(NULL, cm, ml.param); } else { if (c->HasMode(cm->Name)) c->RemoveMode(NULL, cm); } } else if (cm->Type == MODE_LIST) { if (ml.set) c->SetMode(NULL, cm, ml.param); else c->RemoveMode(NULL, cm, ml.param); } } } /*************************************************************************/ int check_valid_admin(User *user, Channel *chan, int servermode) { ChannelMode *cm; if (!chan || !chan->ci) return 1; if (!(cm = ModeManager::FindChannelModeByName(CMODE_PROTECT))) return 0; /* They will be kicked; no need to deop, no need to update our internal struct too */ if (chan->ci->HasFlag(CI_FORBIDDEN)) return 0; if (servermode && !check_access(user, chan->ci, CA_AUTOPROTECT)) { user->SendMessage(ChanServ, _("This channel has been registered with %s."), Config->s_ChanServ.c_str()); chan->RemoveMode(NULL, CMODE_PROTECT, user->nick); return 0; } return 1; } /*************************************************************************/ /* Check whether a user is allowed to be opped on a channel; if they * aren't, deop them. If serverop is 1, the +o was done by a server. * Return 1 if the user is allowed to be opped, 0 otherwise. */ int check_valid_op(User *user, Channel *chan, int servermode) { ChannelMode *owner, *protect, *halfop; if (!chan || !chan->ci) return 1; /* They will be kicked; no need to deop, no need to update our internal struct too */ if (chan->ci->HasFlag(CI_FORBIDDEN)) return 0; owner = ModeManager::FindChannelModeByName(CMODE_OWNER); protect = ModeManager::FindChannelModeByName(CMODE_PROTECT); halfop = ModeManager::FindChannelModeByName(CMODE_HALFOP); if (servermode && !check_access(user, chan->ci, CA_AUTOOP)) { user->SendMessage(ChanServ, _("This channel has been registered with %s."), Config->s_ChanServ.c_str()); if (owner) chan->RemoveMode(NULL, CMODE_OWNER, user->nick); if (protect) chan->RemoveMode(NULL, CMODE_PROTECT, user->nick); chan->RemoveMode(NULL, CMODE_OP, user->nick); if (halfop && !check_access(user, chan->ci, CA_AUTOHALFOP)) chan->RemoveMode(NULL, CMODE_HALFOP, user->nick); return 0; } return 1; } /*************************************************************************/ /* Remove all channels which have expired. */ void expire_chans() { if (!Config->CSExpire) return; for (registered_channel_map::const_iterator it = RegisteredChannelList.begin(), it_end = RegisteredChannelList.end(); it != it_end; ) { ChannelInfo *ci = it->second; ++it; bool expire = false; if (ci->HasFlag(CI_SUSPENDED)) { if (Config->CSSuspendExpire && Anope::CurTime - ci->last_used >= Config->CSSuspendExpire) expire = true; } else if (ci->HasFlag(CI_FORBIDDEN)) { if (Config->CSForbidExpire && Anope::CurTime - ci->last_used >= Config->CSForbidExpire) expire = true; } else if (!ci->c && Anope::CurTime - ci->last_used >= Config->CSExpire) expire = true; if (ci->HasFlag(CI_NO_EXPIRE)) expire = false; if (expire) { EventReturn MOD_RESULT; FOREACH_RESULT(I_OnPreChanExpire, OnPreChanExpire(ci)); if (MOD_RESULT == EVENT_STOP) continue; Anope::string extra; if (ci->HasFlag(CI_FORBIDDEN)) extra = "forbidden "; else if (ci->HasFlag(CI_SUSPENDED)) extra = "suspended "; Log(LOG_NORMAL, "chanserv/expire") << "Expiring " << extra << "channel " << ci->name << " (founder: " << (ci->founder ? ci->founder->display : "(none)") << ")"; FOREACH_MOD(I_OnChanExpire, OnChanExpire(ci)); delete ci; } } } /*************************************************************************/ // XXX this is slightly inefficient void cs_remove_nick(NickCore *nc) { for (registered_channel_map::const_iterator it = RegisteredChannelList.begin(), it_end = RegisteredChannelList.end(); it != it_end; ++it) { ChannelInfo *ci = it->second; ChanAccess *access = ci->GetAccess(nc); if (access) ci->EraseAccess(access); for (unsigned j = ci->GetAkickCount(); j > 0; --j) { AutoKick *akick = ci->GetAkick(j - 1); if (akick->HasFlag(AK_ISNICK) && akick->nc == nc) ci->EraseAkick(j - 1); } if (ci->founder == nc) { NickCore *newowner = NULL; if (ci->successor && (ci->successor->IsServicesOper() || !Config->CSMaxReg || ci->successor->channelcount < Config->CSMaxReg)) newowner = ci->successor; else { ChanAccess *highest = NULL; for (unsigned j = 0; j < ci->GetAccessCount(); ++j) { ChanAccess *ca = ci->GetAccess(j); if (!ca->nc->IsServicesOper() && Config->CSMaxReg && ca->nc->channelcount >= Config->CSMaxReg) continue; if (!highest || ca->level > highest->level) highest = ca; } if (highest) newowner = highest->nc; } if (newowner) { Log(LOG_NORMAL, "chanserv/expire") << "Transferring foundership of " << ci->name << " from deleted nick " << nc->display << " to " << newowner->display; ci->founder = newowner; ci->successor = NULL; ++newowner->channelcount; } else { Log(LOG_NORMAL, "chanserv/expire") << "Deleting channel " << ci->name << " owned by deleted nick " << nc->display; if (ModeManager::FindChannelModeByName(CMODE_REGISTERED)) { /* Maybe move this to delchan() ? */ if (ci->c && ci->c->HasMode(CMODE_REGISTERED)) ci->c->RemoveMode(NULL, CMODE_REGISTERED); } delete ci; continue; } } if (ci->successor == nc) ci->successor = NULL; } } /*************************************************************************/ ChannelInfo *cs_findchan(const Anope::string &chan) { FOREACH_MOD(I_OnFindChan, OnFindChan(chan)); registered_channel_map::const_iterator it = RegisteredChannelList.find(chan); if (it != RegisteredChannelList.end()) return it->second; return NULL; } /*************************************************************************/ /* Return 1 if the user's access level on the given channel falls into the * given category, 0 otherwise. Note that this may seem slightly confusing * in some cases: for example, check_access(..., CA_NOJOIN) returns true if * the user does _not_ have access to the channel (i.e. matches the NOJOIN * criterion). */ int check_access(User *user, ChannelInfo *ci, int what) { int level, limit; if (!user || !ci) return 0; ChanAccess *u_access = ci->GetAccess(user); level = u_access ? u_access->level : 0; limit = ci->levels[what]; // Set should never be disabled, if it is it is db-converter screwup // This all needs rewritten anyway... if (what == CA_SET && limit == ACCESS_INVALID) { ci->levels[what] = ACCESS_FOUNDER; limit = ACCESS_FOUNDER; } /* Resetting the last used time */ if (level > 0) ci->last_used = Anope::CurTime; /* Superadmin always wins. Always. */ if (user->isSuperAdmin) return what == CA_NOJOIN ? 0 : 1; /* If the access of the level we are checking is disabled, they *always* get denied */ if (limit == ACCESS_INVALID) return 0; /* If the level of the user is >= the level for "founder" of this channel and "founder" isn't disabled, they can do anything */ if (ci->levels[CA_FOUNDER] != ACCESS_INVALID && level >= ci->levels[CA_FOUNDER]) return what == CA_NOJOIN ? 0 : 1; if (what == CA_NOJOIN) return level <= ci->levels[what]; else return level >= ci->levels[what]; } /*************************************************************************/ /*********************** ChanServ private routines ***********************/ /*************************************************************************/ /* Reset channel access level values to their default state. */ void reset_levels(ChannelInfo *ci) { int i; if (!ci) { Log() << "reset_levels() called with NULL values"; return; } if (ci->levels) delete [] ci->levels; ci->levels = new int16[CA_SIZE]; for (i = 0; def_levels[i][0] >= 0; ++i) ci->levels[def_levels[i][0]] = def_levels[i][1]; } /*************************************************************************/ /** Is the user the real founder? * @param user The user * @param ci The channel * @return true or false */ bool IsFounder(User *user, ChannelInfo *ci) { if (!user || !ci) return false; if (user->isSuperAdmin) return true; if (user->Account() && user->Account() == ci->founder) return true; return false; } /*************************************************************************/ void update_cs_lastseen(User *user, ChannelInfo *ci) { ChanAccess *access; if (!ci || !user || !user->Account()) return; if (IsFounder(user, ci) || user->IsIdentified() || (user->IsRecognized() && !ci->HasFlag(CI_SECURE))) if ((access = ci->GetAccess(user))) access->last_seen = Anope::CurTime; } /*************************************************************************/ /* Returns the best ban possible for a user depending of the bantype value. */ int get_idealban(ChannelInfo *ci, User *u, Anope::string &ret) { Anope::string mask; if (!ci || !u) return 0; Anope::string vident = u->GetIdent(); switch (ci->bantype) { case 0: ret = "*!" + vident + "@" + u->GetDisplayedHost(); return 1; case 1: if (vident[0] == '~') ret = "*!*" + vident + "@" + u->GetDisplayedHost(); else ret = "*!" + vident + "@" + u->GetDisplayedHost(); return 1; case 2: ret = "*!*@" + u->GetDisplayedHost(); return 1; case 3: mask = create_mask(u); ret = "*!" + mask; return 1; default: return 0; } } /*************************************************************************/ Anope::string get_xop_level(int level) { ChannelMode *halfop = ModeManager::FindChannelModeByName(CMODE_HALFOP); if (level < ACCESS_VOP) return "Err"; else if (halfop && level < ACCESS_HOP) return "VOP"; else if (!halfop && level < ACCESS_AOP) return "VOP"; else if (halfop && level < ACCESS_AOP) return "HOP"; else if (level < ACCESS_SOP) return "AOP"; else if (level < ACCESS_QOP) return "SOP"; else if (level < ACCESS_FOUNDER) return "QOP"; else return "Founder"; } ChanServTimer::ChanServTimer(Channel *chan) : Timer(Config->CSInhabit), c(chan) { if (!ChanServ) return; if (c->ci) c->ci->SetFlag(CI_INHABIT); if (!c->ci || !c->ci->bi) ChanServ->Join(*c); else if (!c->FindUser(c->ci->bi)) c->ci->bi->Join(*c); } void ChanServTimer::Tick(time_t) { if (!c || !c->ci || !ChanServ) return; c->ci->UnsetFlag(CI_INHABIT); if (!c->ci->bi && ChanServ) ChanServ->Part(*c); else if (c->users.size() == 1 || c->users.size() < Config->BSMinUsers) c->ci->bi->Part(*c); }