summaryrefslogtreecommitdiff
path: root/src/chanserv.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/chanserv.cpp')
-rw-r--r--src/chanserv.cpp1066
1 files changed, 1066 insertions, 0 deletions
diff --git a/src/chanserv.cpp b/src/chanserv.cpp
new file mode 100644
index 000000000..10acd3731
--- /dev/null
+++ b/src/chanserv.cpp
@@ -0,0 +1,1066 @@
+/* ChanServ functions.
+ *
+ * (C) 2003-2010 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"
+#include "language.h"
+
+registered_channel_map RegisteredChannelList;
+
+static int def_levels[][2] = {
+ { CA_AUTOOP, 5 },
+ { CA_AUTOVOICE, 3 },
+ { CA_AUTODEOP, -1 },
+ { CA_NOJOIN, -2 },
+ { CA_INVITE, 5 },
+ { CA_AKICK, 10 },
+ { CA_SET, ACCESS_QOP },
+ { CA_CLEAR, ACCESS_FOUNDER },
+ { 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_INFO, ACCESS_QOP },
+ { CA_AUTOOWNER, ACCESS_QOP },
+ { CA_OWNER, ACCESS_FOUNDER },
+ { CA_OWNERME, ACCESS_QOP },
+ { CA_FOUNDER, ACCESS_QOP },
+ { -1 }
+};
+
+
+LevelInfo levelinfo[] = {
+ { CA_AUTODEOP, "AUTODEOP", CHAN_LEVEL_AUTODEOP },
+ { CA_AUTOHALFOP, "AUTOHALFOP", CHAN_LEVEL_AUTOHALFOP },
+ { CA_AUTOOP, "AUTOOP", CHAN_LEVEL_AUTOOP },
+ { CA_AUTOPROTECT, "AUTOPROTECT", CHAN_LEVEL_AUTOPROTECT },
+ { CA_AUTOVOICE, "AUTOVOICE", CHAN_LEVEL_AUTOVOICE },
+ { CA_NOJOIN, "NOJOIN", CHAN_LEVEL_NOJOIN },
+ { CA_SIGNKICK, "SIGNKICK", CHAN_LEVEL_SIGNKICK },
+ { CA_ACCESS_LIST, "ACC-LIST", CHAN_LEVEL_ACCESS_LIST },
+ { CA_ACCESS_CHANGE, "ACC-CHANGE", CHAN_LEVEL_ACCESS_CHANGE },
+ { CA_AKICK, "AKICK", CHAN_LEVEL_AKICK },
+ { CA_SET, "SET", CHAN_LEVEL_SET },
+ { CA_BAN, "BAN", CHAN_LEVEL_BAN },
+ { CA_BANME, "BANME", CHAN_LEVEL_BANME },
+ { CA_CLEAR, "CLEAR", CHAN_LEVEL_CLEAR },
+ { CA_GETKEY, "GETKEY", CHAN_LEVEL_GETKEY },
+ { CA_HALFOP, "HALFOP", CHAN_LEVEL_HALFOP },
+ { CA_HALFOPME, "HALFOPME", CHAN_LEVEL_HALFOPME },
+ { CA_INFO, "INFO", CHAN_LEVEL_INFO },
+ { CA_KICK, "KICK", CHAN_LEVEL_KICK },
+ { CA_KICKME, "KICKME", CHAN_LEVEL_KICKME },
+ { CA_INVITE, "INVITE", CHAN_LEVEL_INVITE },
+ { CA_OPDEOP, "OPDEOP", CHAN_LEVEL_OPDEOP },
+ { CA_OPDEOPME, "OPDEOPME", CHAN_LEVEL_OPDEOPME },
+ { CA_PROTECT, "PROTECT", CHAN_LEVEL_PROTECT },
+ { CA_PROTECTME, "PROTECTME", CHAN_LEVEL_PROTECTME },
+ { CA_TOPIC, "TOPIC", CHAN_LEVEL_TOPIC },
+ { CA_UNBAN, "UNBAN", CHAN_LEVEL_UNBAN },
+ { CA_VOICE, "VOICE", CHAN_LEVEL_VOICE },
+ { CA_VOICEME, "VOICEME", CHAN_LEVEL_VOICEME },
+ { CA_MEMO, "MEMO", CHAN_LEVEL_MEMO },
+ { CA_ASSIGN, "ASSIGN", CHAN_LEVEL_ASSIGN },
+ { CA_BADWORDS, "BADWORDS", CHAN_LEVEL_BADWORDS },
+ { CA_FANTASIA, "FANTASIA", CHAN_LEVEL_FANTASIA },
+ { CA_GREET, "GREET", CHAN_LEVEL_GREET },
+ { CA_NOKICK, "NOKICK", CHAN_LEVEL_NOKICK },
+ { CA_SAY, "SAY", CHAN_LEVEL_SAY },
+ { CA_AUTOOWNER, "AUTOOWNER", CHAN_LEVEL_AUTOOWNER },
+ { CA_OWNER, "OWNER", CHAN_LEVEL_OWNER },
+ { CA_OWNERME, "OWNERME", CHAN_LEVEL_OWNERME },
+ { CA_FOUNDER, "FOUNDER", CHAN_LEVEL_FOUNDER },
+ { -1 }
+};
+int levelinfo_maxwidth = 0;
+
+/*************************************************************************/
+
+void moduleAddChanServCmds() {
+ ModuleManager::LoadModuleList(Config.ChanServCoreModules);
+}
+
+/* *INDENT-ON* */
+/*************************************************************************/
+
+/* Returns modes for mlock in a nice way. */
+
+char *get_mlock_modes(ChannelInfo * ci, int complete)
+{
+ static char res[BUFSIZE];
+ char *end, *value;
+ ChannelMode *cm;
+ ChannelModeParam *cmp;
+ std::map<char, ChannelMode *>::iterator it;
+ std::string param;
+
+ memset(&res, '\0', sizeof(res));
+ end = res;
+
+ if (ci->GetMLockCount(true) || ci->GetMLockCount(false))
+ {
+ if (ci->GetMLockCount(true))
+ {
+ *end++ = '+';
+
+ for (it = ModeManager::ChannelModesByChar.begin(); it != ModeManager::ChannelModesByChar.end(); ++it)
+ {
+ cm = it->second;
+
+ if (ci->HasMLock(cm->Name, true))
+ *end++ = it->first;
+ }
+ }
+
+ if (ci->GetMLockCount(false))
+ {
+ *end++ = '-';
+
+ for (it = ModeManager::ChannelModesByChar.begin(); it != ModeManager::ChannelModesByChar.end(); ++it)
+ {
+ cm = it->second;
+
+ if (ci->HasMLock(cm->Name, false))
+ *end++ = it->first;
+ }
+ }
+
+ if (ci->GetMLockCount(true) && complete)
+ {
+ for (it = ModeManager::ChannelModesByChar.begin(); it != ModeManager::ChannelModesByChar.end(); ++it)
+ {
+ cm = it->second;
+
+ if (cm->Type == MODE_PARAM)
+ {
+ cmp = dynamic_cast<ChannelModeParam *>(cm);
+
+ ci->GetParam(cmp->Name, param);
+
+ if (!param.empty())
+ {
+ value = const_cast<char *>(param.c_str());
+
+ *end++ = ' ';
+ while (*value)
+ *end++ = *value++;
+ }
+ }
+ }
+ }
+ }
+
+ return res;
+}
+
+/*************************************************************************/
+
+/* Return information on memory use. Assumes pointers are valid. */
+
+void get_chanserv_stats(long *nrec, long *memuse)
+{
+ long count = 0, mem = 0;
+ std::string param;
+
+ for (registered_channel_map::const_iterator it = RegisteredChannelList.begin(); it != RegisteredChannelList.end(); ++it)
+ {
+ ChannelInfo *ci = it->second;
+
+ count++;
+ mem += sizeof(*ci);
+ if (ci->desc)
+ mem += strlen(ci->desc) + 1;
+ if (ci->url)
+ mem += strlen(ci->url) + 1;
+ if (ci->email)
+ mem += strlen(ci->email) + 1;
+ mem += ci->GetAccessCount() * sizeof(ChanAccess);
+ mem += ci->GetAkickCount() * sizeof(AutoKick);
+
+ if (ci->GetParam(CMODE_KEY, param))
+ mem += param.length() + 1;
+
+ if (ci->GetParam(CMODE_FLOOD, param))
+ mem += param.length() + 1;
+
+ if (ci->GetParam(CMODE_REDIRECT, param))
+ mem += param.length() + 1;
+
+ if (ci->last_topic)
+ mem += strlen(ci->last_topic) + 1;
+ if (ci->entry_message)
+ mem += strlen(ci->entry_message) + 1;
+ if (ci->forbidby)
+ mem += strlen(ci->forbidby) + 1;
+ if (ci->forbidreason)
+ mem += strlen(ci->forbidreason) + 1;
+ if (ci->levels)
+ mem += sizeof(*ci->levels) * CA_SIZE;
+ mem += ci->memos.memos.size() * sizeof(Memo);
+ for (unsigned j = 0; j < ci->memos.memos.size(); j++)
+ {
+ if (ci->memos.memos[j]->text)
+ mem += strlen(ci->memos.memos[j]->text) + 1;
+ }
+ if (ci->ttb)
+ mem += sizeof(*ci->ttb) * TTB_SIZE;
+ mem += ci->GetBadWordCount() * sizeof(BadWord);
+ }
+ *nrec = count;
+ *memuse = mem;
+}
+
+/*************************************************************************/
+/*************************************************************************/
+
+/* ChanServ initialization. */
+
+void cs_init()
+{
+ moduleAddChanServCmds();
+}
+
+/*************************************************************************/
+
+/* Main ChanServ routine. */
+
+void chanserv(User *u, const std::string &buf)
+{
+ if (!u || buf.empty())
+ return;
+
+ if (buf.find("\1PING ", 0, 6) != std::string::npos && buf[buf.length() - 1] == '\1')
+ {
+ std::string command = buf;
+ command.erase(command.begin());
+ command.erase(command.end());
+ ircdproto->SendCTCP(ChanServ, u->nick.c_str(), "%s", command.c_str());
+ }
+ else
+ {
+ mod_run_cmd(ChanServ, u, buf);
+ }
+}
+
+/*************************************************************************/
+
+/* Check the current modes on a channel; if they conflict with a mode lock,
+ * fix them.
+ */
+
+void check_modes(Channel *c)
+{
+ time_t t = time(NULL);
+ ChannelInfo *ci;
+ ChannelMode *cm;
+ std::map<char, ChannelMode *>::iterator it;
+ std::string param, ciparam;
+
+ if (!c)
+ {
+ Alog(LOG_DEBUG) << "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());
+ Alog() << Config.s_ChanServ << ": Bouncy modes on channel " << c->name;
+ c->bouncy_modes = 1;
+ return;
+ }
+
+ if (c->chanserv_modetime != t)
+ {
+ c->chanserv_modecount = 0;
+ c->chanserv_modetime = t;
+ }
+ c->chanserv_modecount++;
+
+ /* Check if the channel is registered; if not remove mode -r */
+ if (!(ci = c->ci))
+ {
+ if (c->HasMode(CMODE_REGISTERED))
+ {
+ c->RemoveMode(NULL, CMODE_REGISTERED);
+ }
+ return;
+ }
+
+ for (it = ModeManager::ChannelModesByChar.begin(); it != ModeManager::ChannelModesByChar.end(); ++it)
+ {
+ cm = it->second;
+
+ /* If this channel does not have the mode and the mode is mlocked */
+ if (cm->Type == MODE_REGULAR && !c->HasMode(cm->Name) && ci->HasMLock(cm->Name, true))
+ {
+ c->SetMode(NULL, cm);
+
+ /* Add the eventual parameter and modify the Channel structure */
+ if (cm->Type == MODE_PARAM)
+ {
+ if (ci->GetParam(cm->Name, param))
+ c->SetMode(NULL, cm, param);
+ }
+ else
+ c->SetMode(NULL, cm);
+ }
+ /* If this is a param mode and its mlocked, check to ensure it is set and set to the correct value */
+ else if (cm->Type == MODE_PARAM && ci->HasMLock(cm->Name, true))
+ {
+ c->GetParam(cm->Name, param);
+ ci->GetParam(cm->Name, ciparam);
+
+ /* If the channel doesnt have the mode, or it does and it isn't set correctly */
+ if (!c->HasMode(cm->Name) || (!param.empty() && !ciparam.empty() && param != ciparam))
+ {
+ c->SetMode(NULL, cm, ciparam);
+ }
+ }
+ }
+
+ for (it = ModeManager::ChannelModesByChar.begin(); it != ModeManager::ChannelModesByChar.end(); ++it)
+ {
+ cm = it->second;
+
+ /* If the channel has the mode */
+ if (c->HasMode(cm->Name) && ci->HasMLock(cm->Name, false))
+ {
+ /* Add the eventual parameter */
+ if (cm->Type == MODE_PARAM)
+ {
+ ChannelModeParam *cmp = dynamic_cast<ChannelModeParam *>(cm);
+
+ if (!cmp->MinusNoArg)
+ {
+ if (c->GetParam(cmp->Name, param))
+ c->RemoveMode(NULL, cm, param);
+ }
+ }
+ else
+ c->RemoveMode(NULL, cm);
+ }
+ }
+}
+
+/*************************************************************************/
+
+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))
+ {
+ notice_lang(Config.s_ChanServ, user, CHAN_IS_REGISTERED, Config.s_ChanServ);
+ chan->RemoveMode(NULL, CMODE_PROTECT, user->nick);
+ return 0;
+ }
+
+ if (check_access(user, chan->ci, CA_AUTODEOP))
+ {
+ 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))
+ {
+ notice_lang(Config.s_ChanServ, user, CHAN_IS_REGISTERED, Config.s_ChanServ);
+
+ 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;
+ }
+
+ if (check_access(user, chan->ci, CA_AUTODEOP))
+ {
+ chan->RemoveMode(NULL, CMODE_OP, user->nick);
+
+ if (owner)
+ chan->RemoveMode(NULL, CMODE_OWNER, user->nick);
+ if (protect)
+ chan->RemoveMode(NULL, CMODE_PROTECT, user->nick);
+ if (halfop)
+ chan->RemoveMode(NULL, CMODE_HALFOP, user->nick);
+
+ return 0;
+ }
+
+ return 1;
+}
+
+/*************************************************************************/
+
+/* Record the current channel topic in the ChannelInfo structure. */
+
+void record_topic(const char *chan)
+{
+ Channel *c;
+ ChannelInfo *ci;
+
+ if (readonly)
+ return;
+
+ c = findchan(chan);
+ if (!c || !(ci = c->ci))
+ return;
+
+ if (ci->last_topic)
+ delete [] ci->last_topic;
+
+ if (c->topic)
+ ci->last_topic = sstrdup(c->topic);
+ else
+ ci->last_topic = NULL;
+
+ ci->last_topic_setter = c->topic_setter;
+ ci->last_topic_time = c->topic_time;
+}
+
+/*************************************************************************/
+
+/* Restore the topic in a channel when it's created, if we should. */
+
+void restore_topic(const char *chan)
+{
+ Channel *c = findchan(chan);
+ ChannelInfo *ci;
+
+ if (!c || !(ci = c->ci))
+ return;
+ /* We can be sure that the topic will be in sync when we return -GD */
+ c->topic_sync = 1;
+ if (!(ci->HasFlag(CI_KEEPTOPIC))) {
+ /* We need to reset the topic here, since it's currently empty and
+ * should be updated with a TOPIC from the IRCd soon. -GD
+ */
+ ci->last_topic = NULL;
+ ci->last_topic_setter = whosends(ci)->nick;
+ ci->last_topic_time = time(NULL);
+ return;
+ }
+ if (c->topic)
+ delete [] c->topic;
+ if (ci->last_topic) {
+ c->topic = sstrdup(ci->last_topic);
+ c->topic_setter = ci->last_topic_setter;
+ c->topic_time = ci->last_topic_time;
+ } else {
+ c->topic = NULL;
+ c->topic_setter = whosends(ci)->nick;
+ }
+ if (ircd->join2set) {
+ if (whosends(ci) == ChanServ) {
+ ircdproto->SendJoin(ChanServ, chan, c->creation_time);
+ c->SetMode(NULL, CMODE_OP, Config.s_ChanServ);
+ }
+ }
+ ircdproto->SendTopic(whosends(ci), c, c->topic_setter.c_str(), c->topic ? c->topic : "");
+ if (ircd->join2set) {
+ if (whosends(ci) == ChanServ) {
+ ircdproto->SendPart(ChanServ, c, NULL);
+ }
+ }
+}
+
+/*************************************************************************/
+
+/* See if the topic is locked on the given channel, and return 1 (and fix
+ * the topic) if so. */
+
+int check_topiclock(Channel * c, time_t topic_time)
+{
+ ChannelInfo *ci;
+
+ if (!c)
+ {
+ Alog(LOG_DEBUG) << "check_topiclock called with NULL values";
+ return 0;
+ }
+
+ if (!(ci = c->ci) || !(ci->HasFlag(CI_TOPICLOCK)))
+ return 0;
+
+ if (c->topic)
+ delete [] c->topic;
+ if (ci->last_topic) {
+ c->topic = sstrdup(ci->last_topic);
+ c->topic_setter = ci->last_topic_setter;
+ } else {
+ c->topic = NULL;
+ /* Bot assigned & Symbiosis ON?, the bot will set the topic - doc */
+ /* Altough whosends() also checks for Config.BSMinUsers -GD */
+ c->topic_setter = whosends(ci)->nick;
+ }
+
+ if (ircd->topictsforward) {
+ /* Because older timestamps are rejected */
+ /* Some how the topic_time from do_topic is 0 set it to current + 1 */
+ if (!topic_time) {
+ c->topic_time = time(NULL) + 1;
+ } else {
+ c->topic_time = topic_time + 1;
+ }
+ } else {
+ /* If no last topic, we can't use last topic time! - doc */
+ if (ci->last_topic)
+ c->topic_time = ci->last_topic_time;
+ else
+ c->topic_time = time(NULL) + 1;
+ }
+
+ if (ircd->join2set) {
+ if (whosends(ci) == ChanServ) {
+ ircdproto->SendJoin(ChanServ, c->name.c_str(), c->creation_time);
+ c->SetMode(NULL, CMODE_OP, Config.s_ChanServ);
+ }
+ }
+
+ ircdproto->SendTopic(whosends(ci), c, c->topic_setter.c_str(), c->topic ? c->topic : "");
+
+ if (ircd->join2set) {
+ if (whosends(ci) == ChanServ) {
+ ircdproto->SendPart(ChanServ, c, NULL);
+ }
+ }
+ return 1;
+}
+
+/*************************************************************************/
+
+/* Remove all channels which have expired. */
+
+void expire_chans()
+{
+ if (!Config.CSExpire)
+ return;
+
+ time_t now = time(NULL);
+
+ for (registered_channel_map::const_iterator it = RegisteredChannelList.begin(); it != RegisteredChannelList.end();)
+ {
+ ChannelInfo *ci = it->second;
+ ++it;
+
+ if (!ci->c && now - ci->last_used >= Config.CSExpire && !ci->HasFlag(CI_FORBIDDEN) && !ci->HasFlag(CI_NO_EXPIRE) && !ci->HasFlag(CI_SUSPENDED))
+ {
+ EventReturn MOD_RESULT;
+ FOREACH_RESULT(I_OnPreChanExpire, OnPreChanExpire(ci));
+ if (MOD_RESULT == EVENT_STOP)
+ continue;
+
+ char *chname = sstrdup(ci->name.c_str());
+ Alog() << "Expiring channel " << ci->name << " (founder: " << (ci->founder ? ci->founder->display : "(none)") << " )";
+ delete ci;
+ FOREACH_MOD(I_OnChanExpire, OnChanExpire(chname));
+ delete [] chname;
+ }
+ }
+}
+
+/*************************************************************************/
+
+// XXX this is slightly inefficient
+void cs_remove_nick(const NickCore * nc)
+{
+ int j;
+ ChanAccess *ca;
+ AutoKick *akick;
+
+ for (registered_channel_map::const_iterator it = RegisteredChannelList.begin(); it != RegisteredChannelList.end(); ++it)
+ {
+ ChannelInfo *ci = it->second;
+
+ if (ci->founder == nc) {
+ if (ci->successor) {
+ NickCore *nc2 = ci->successor;
+ if (!nc2->IsServicesOper() && Config.CSMaxReg && nc2->channelcount >= Config.CSMaxReg) {
+ Alog() << Config.s_ChanServ << ": Successor (" << nc2->display << " ) of " << ci->name << " owns too many channels, deleting channel",
+ delete ci;
+ continue;
+ } else {
+ Alog() << Config.s_ChanServ << ": Transferring foundership of " << ci->name << " from deleted nick " << nc->display << " to successor " << nc2->display;
+ ci->founder = nc2;
+ ci->successor = NULL;
+ nc2->channelcount++;
+ }
+ } else {
+ Alog() << Config.s_ChanServ << ": 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;
+
+ for (j = ci->GetAccessCount(); j > 0; --j)
+ {
+ ca = ci->GetAccess(j - 1);
+
+ if (ca->nc == nc)
+ ci->EraseAccess(j - 1);
+ }
+
+ for (j = ci->GetAkickCount(); j > 0; --j)
+ {
+ akick = ci->GetAkick(j - 1);
+ if (akick->HasFlag(AK_ISNICK) && akick->nc == nc)
+ ci->EraseAkick(j - 1);
+ }
+ }
+}
+
+/*************************************************************************/
+
+ChannelInfo *cs_findchan(const char *chan)
+{
+ return cs_findchan(ci::string(chan));
+}
+
+ChannelInfo *cs_findchan(const std::string &chan)
+{
+ return cs_findchan(ci::string(chan.c_str()));
+}
+
+ChannelInfo *cs_findchan(const ci::string &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;
+ int limit;
+
+ if (!user || !ci) {
+ return 0;
+ }
+
+ level = get_access(user, ci);
+ limit = ci->levels[what];
+
+ /* Resetting the last used time */
+ if (level > 0)
+ ci->last_used = time(NULL);
+
+ /* Superadmin always wins. Always. */
+ if (user->isSuperAdmin)
+ return (what == CA_AUTODEOP || 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_AUTODEOP || what == CA_NOJOIN ? 0 : 1);
+
+ /* Hacks to make flags work */
+ if (what == CA_AUTODEOP && (ci->HasFlag(CI_SECUREOPS)) && level == 0)
+ return 1;
+
+ if (what == CA_AUTODEOP || 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)
+ {
+ Alog(LOG_DEBUG) << "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;
+}
+
+
+/** Return the access level for the user on the channel.
+ * If the channel doesn't exist, the user isn't on the access list, or the
+ * channel is CI_SECURE and the user isn't identified, return 0
+ * @param user The user
+ * @param ci The cahnnel
+ * @return The level, or 0
+ */
+int get_access(User *user, ChannelInfo *ci)
+{
+ ChanAccess *access = NULL;
+
+ if (!ci || !user)
+ return 0;
+
+ /* SuperAdmin always has highest level */
+ if (user->isSuperAdmin)
+ return ACCESS_SUPERADMIN;
+
+ if (IsFounder(user, ci))
+ return ACCESS_FOUNDER;
+
+ if (user->IsIdentified())
+ {
+ access = ci->GetAccess(user->Account());
+ if (access)
+ return access->level;
+ }
+ else
+ {
+ NickAlias *na = findnick(user->nick);
+ if (na)
+ access = ci->GetAccess(na->nc);
+ if (access && user->IsRecognized() && !(ci->HasFlag(CI_SECURE)))
+ return access->level;
+ }
+
+ return 0;
+}
+
+/*************************************************************************/
+
+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->Account())))
+ access->last_seen = time(NULL);
+}
+
+/*************************************************************************/
+
+/* Returns the best ban possible for an user depending of the bantype
+ value. */
+
+int get_idealban(ChannelInfo * ci, User * u, char *ret, int retlen)
+{
+ char *mask;
+
+ if (!ci || !u || !ret || retlen == 0)
+ return 0;
+
+ std::string vident = u->GetIdent();
+
+ switch (ci->bantype) {
+ case 0:
+ snprintf(ret, retlen, "*!%s@%s", vident.c_str(),
+ u->GetDisplayedHost().c_str());
+ return 1;
+ case 1:
+ if (vident[0] == '~')
+ snprintf(ret, retlen, "*!*%s@%s",
+ vident.c_str(), u->GetDisplayedHost().c_str());
+ else
+ snprintf(ret, retlen, "*!%s@%s",
+ vident.c_str(), u->GetDisplayedHost().c_str());
+
+ return 1;
+ case 2:
+ snprintf(ret, retlen, "*!*@%s", u->GetDisplayedHost().c_str());
+ return 1;
+ case 3:
+ mask = create_mask(u);
+ snprintf(ret, retlen, "*!%s", mask);
+ delete [] mask;
+ return 1;
+
+ default:
+ return 0;
+ }
+}
+
+
+/*************************************************************************/
+
+int get_access_level(ChannelInfo * ci, NickAlias * na)
+{
+ ChanAccess *access;
+
+ if (!ci || !na)
+ return 0;
+
+ if (na->nc == ci->founder)
+ return ACCESS_FOUNDER;
+
+ access = ci->GetAccess(na->nc);
+
+ if (!access)
+ return 0;
+ else
+ return access->level;
+}
+
+int get_access_level(ChannelInfo *ci, NickCore *nc)
+{
+ if (!ci || !nc)
+ return 0;
+
+ if (nc == ci->founder)
+ return ACCESS_FOUNDER;
+
+ ChanAccess *access = ci->GetAccess(nc);
+
+ if (!access)
+ return 0;
+ else
+ return access->level;
+}
+
+const char *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";
+}
+
+/*************************************************************************/
+/*********************** ChanServ command routines ***********************/
+/*************************************************************************/
+
+/* Is the mask stuck? */
+
+AutoKick *is_stuck(ChannelInfo * ci, const char *mask)
+{
+ if (!ci)
+ return NULL;
+
+ for (unsigned i = 0; i < ci->GetAkickCount(); ++i)
+ {
+ AutoKick *akick = ci->GetAkick(i);
+
+ if (akick->HasFlag(AK_ISNICK) || !akick->HasFlag(AK_STUCK))
+ continue;
+
+ if (Anope::Match(akick->mask, mask, false))
+ return akick;
+
+ if (ircd->reversekickcheck)
+ if (Anope::Match(mask, akick->mask, false))
+ return akick;
+ }
+
+ return NULL;
+}
+
+/* Ban the stuck mask in a safe manner. */
+
+void stick_mask(ChannelInfo * ci, AutoKick * akick)
+{
+ Entry *ban;
+
+ if (!ci) {
+ return;
+ }
+
+ if (ci->c->bans && ci->c->bans->entries != 0) {
+ for (ban = ci->c->bans->entries; ban; ban = ban->next) {
+ /* If akick is already covered by a wider ban.
+ Example: c->bans[i] = *!*@*.org and akick->u.mask = *!*@*.epona.org */
+ if (entry_match_mask(ban, akick->mask.c_str(), 0))
+ return;
+
+ if (ircd->reversekickcheck) {
+ /* If akick is wider than a ban already in place.
+ Example: c->bans[i] = *!*@irc.epona.org and akick->u.mask = *!*@*.epona.org */
+ if (Anope::Match(ban->mask, akick->mask.c_str(), false))
+ return;
+ }
+ }
+ }
+
+ /* Falling there means set the ban */
+ ci->c->SetMode(NULL, CMODE_BAN, akick->mask.c_str());
+}
+
+/* Ban the stuck mask in a safe manner. */
+
+void stick_all(ChannelInfo * ci)
+{
+ if (!ci)
+ return;
+
+ for (unsigned i = 0; i < ci->GetAkickCount(); ++i)
+ {
+ AutoKick *akick = ci->GetAkick(i);
+
+ if (akick->HasFlag(AK_ISNICK) || !akick->HasFlag(AK_STUCK))
+ continue;
+
+ ci->c->SetMode(NULL, CMODE_BAN, akick->mask.c_str());
+ }
+}
+
+ChanServTimer::ChanServTimer(Channel *chan) : Timer(Config.CSInhabit), c(chan)
+{
+ if (c->ci)
+ c->ci->SetFlag(CI_INHABIT);
+}
+
+void ChanServTimer::Tick(time_t)
+{
+ if (!c->ci)
+ return;
+
+ c->ci->UnsetFlag(CI_INHABIT);
+
+ /* If the channel has users again, don't part it and halt */
+ if (!c->users.empty())
+ return;
+
+ ircdproto->SendPart(ChanServ, c, NULL);
+
+ /* Now delete the channel as it is empty */
+ if (!c->HasFlag(CH_PERSIST) && !c->ci->HasFlag(CI_PERSIST))
+ delete c;
+}
+