/* 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. * * $Id$ * */ /*************************************************************************/ #include "services.h" #include "pseudo.h" /*************************************************************************/ /* *INDENT-OFF* */ ChannelInfo *chanlists[256]; 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 }, { -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 }, { -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::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(cm); ci->GetParam(cmp->Name, param); if (!param.empty()) { value = const_cast(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; unsigned i, j; ChannelInfo *ci; std::string param; for (i = 0; i < 256; i++) { for (ci = chanlists[i]; ci; ci = ci->next) { 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 (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, char *buf) { char *cmd, *s; cmd = strtok(buf, " "); if (!cmd) { return; } else if (stricmp(cmd, "\1PING") == 0) { if (!(s = strtok(NULL, ""))) { *s = 0; } ircdproto->SendCTCP(findbot(Config.s_ChanServ), u->nick.c_str(), "PING %s", s); } else { mod_run_cmd(Config.s_ChanServ, u, CHANSERV, cmd); } } /*************************************************************************/ /* 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::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(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; } /*************************************************************************/ /* Check whether a user should be opped on a channel, and if so, do it. * Return 1 if the user was opped, 0 otherwise. (Updates the channel's * last used time if the user was opped.) */ int check_should_op(User * user, char *chan) { ChannelInfo *ci = cs_findchan(chan); if (!ci || (ci->HasFlag(CI_FORBIDDEN)) || *chan == '+') return 0; if ((ci->HasFlag(CI_SECURE)) && !user->IsIdentified()) return 0; if (check_access(user, ci, CA_AUTOOP)) { ci->c->SetMode(NULL, CMODE_OP, user->nick); return 1; } return 0; } /*************************************************************************/ /* Check whether a user should be voiced on a channel, and if so, do it. * Return 1 if the user was voiced, 0 otherwise. */ int check_should_voice(User * user, char *chan) { ChannelInfo *ci = cs_findchan(chan); if (!ci || (ci->HasFlag(CI_FORBIDDEN)) || *chan == '+') return 0; if ((ci->HasFlag(CI_SECURE)) && !user->IsIdentified()) return 0; if (check_access(user, ci, CA_AUTOVOICE)) { ci->c->SetMode(NULL, CMODE_VOICE, user->nick); return 1; } return 0; } /*************************************************************************/ int check_should_halfop(User * user, char *chan) { ChannelInfo *ci = cs_findchan(chan); if (!ci || (ci->HasFlag(CI_FORBIDDEN)) || *chan == '+') return 0; if (check_access(user, ci, CA_AUTOHALFOP)) { ci->c->SetMode(NULL, CMODE_HALFOP, user->nick); return 1; } return 0; } /*************************************************************************/ int check_should_owner(User * user, char *chan) { ChannelInfo *ci = cs_findchan(chan); if (!ci || !ci->c || ci->HasFlag(CI_FORBIDDEN) || *chan == '+') return 0; if ((ci->HasFlag(CI_SECUREFOUNDER) && IsRealFounder(user, ci)) || (!ci->HasFlag(CI_SECUREFOUNDER) && IsFounder(user, ci))) { ci->c->SetMode(NULL, CMODE_OP, user->nick); ci->c->SetMode(NULL, CMODE_OWNER, user->nick); return 1; } return 0; } /*************************************************************************/ int check_should_protect(User * user, char *chan) { ChannelInfo *ci = cs_findchan(chan); if (!ci || !ci->c || ci->HasFlag(CI_FORBIDDEN) || *chan == '+') return 0; if (check_access(user, ci, CA_AUTOPROTECT)) { ci->c->SetMode(NULL, CMODE_OWNER, user->nick); ci->c->SetMode(NULL, CMODE_PROTECT, user->nick); return 1; } return 0; } /*************************************************************************/ /* 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) == findbot(Config.s_ChanServ)) { ircdproto->SendJoin(findbot(Config.s_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) == findbot(Config.s_ChanServ)) { ircdproto->SendPart(findbot(Config.s_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) == findbot(Config.s_ChanServ)) { ircdproto->SendJoin(findbot(Config.s_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) == findbot(Config.s_ChanServ)) { ircdproto->SendPart(findbot(Config.s_ChanServ), c, NULL); } } return 1; } /*************************************************************************/ /* Remove all channels which have expired. */ void expire_chans() { ChannelInfo *ci, *next; int i; time_t now = time(NULL); if (!Config.CSExpire) return; for (i = 0; i < 256; i++) { for (ci = chanlists[i]; ci; ci = next) { next = ci->next; 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; } } } } /*************************************************************************/ /* Remove a (deleted or expired) nickname from all channel lists. */ void cs_remove_nick(const NickCore * nc) { int i, j; ChannelInfo *ci, *next; ChanAccess *ca; AutoKick *akick; for (i = 0; i < 256; i++) { for (ci = chanlists[i]; ci; ci = next) { next = ci->next; 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->in_use && ca->nc == nc) ci->EraseAccess(j - 1); } for (j = ci->GetAkickCount(); j > 0; --j) { akick = ci->GetAkick(j - 1); if (akick->InUse && akick->HasFlag(AK_ISNICK) && akick->nc == nc) ci->EraseAkick(akick); } } } } /*************************************************************************/ /* Return the ChannelInfo structure for the given channel, or NULL if the * channel isn't registered. */ ChannelInfo *cs_findchan(const std::string &chan) { ChannelInfo *ci; if (chan.empty()) { Alog(LOG_DEBUG) << "cs_findchan() called with NULL values"; return NULL; } for (ci = chanlists[static_cast(tolower(chan[1]))]; ci; ci = ci->next) { if (ci::string(ci->name.c_str()) == chan) return ci; } 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); if (limit == ACCESS_INVALID) return 0; if (limit > ACCESS_FOUNDER) return 1; if (limit == ACCESS_FOUNDER) return (what == CA_AUTODEOP || what == CA_NOJOIN ? !IsRealFounder(user, ci) : IsRealFounder(user, ci)); /* 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 ***********************/ /*************************************************************************/ /* Insert a channel alphabetically into the database. */ void alpha_insert_chan(ChannelInfo * ci) { ChannelInfo *ptr, *prev; if (!ci) { Alog(LOG_DEBUG) << "alpha_insert_chan() called with NULL values"; return; } const char *chan = ci->name.c_str(); for (prev = NULL, ptr = chanlists[static_cast(tolower(chan[1]))]; ptr != NULL && stricmp(ptr->name.c_str(), chan) < 0; prev = ptr, ptr = ptr->next); ci->prev = prev; ci->next = ptr; if (!prev) chanlists[static_cast(tolower(chan[1]))] = ci; else prev->next = ci; if (ptr) ptr->prev = ci; } /* 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 a channel founder? (owner) * @param user The user * @param ci The channel * @return true or false */ bool IsFounder(User *user, ChannelInfo *ci) { ChanAccess *access = NULL; if (!user || !ci) return false; if (IsRealFounder(user, ci)) return true; if (user->Account()) access = ci->GetAccess(user->Account()); else { NickAlias *na = findnick(user->nick); if (na) access = ci->GetAccess(na->nc); } /* If they're QOP+ and theyre identified or theyre recognized and the channel isn't secure */ if (access && access->level >= ACCESS_QOP && (user->Account() || (user->IsRecognized() && !(ci->HasFlag(CI_SECURE))))) return true; return false; } /** Is the user the real founder? * @param user The user * @param ci The channel * @return true or false */ bool IsRealFounder(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; } 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->InUse || 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->InUse || (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(findbot(Config.s_ChanServ), c, NULL); /* Now delete the channel as it is empty */ if (!c->HasFlag(CH_PERSIST) && !c->ci->HasFlag(CI_PERSIST)) delete c; }