diff options
Diffstat (limited to 'modules/commands')
136 files changed, 24964 insertions, 0 deletions
diff --git a/modules/commands/bs_assign.cpp b/modules/commands/bs_assign.cpp new file mode 100644 index 000000000..803368994 --- /dev/null +++ b/modules/commands/bs_assign.cpp @@ -0,0 +1,168 @@ +/* BotServ core 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 "module.h" + +class CommandBSAssign : public Command +{ + public: + CommandBSAssign(Module *creator) : Command(creator, "botserv/assign", 2, 2) + { + this->SetDesc(_("Assigns a bot to a channel")); + this->SetSyntax(_("\037channel\037 \037nick\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &chan = params[0]; + const Anope::string &nick = params[1]; + + User *u = source.u; + + if (readonly) + { + source.Reply(BOT_ASSIGN_READONLY); + return; + } + + ChannelInfo *ci = cs_findchan(params[0]); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + BotInfo *bi = findbot(nick); + if (!bi) + { + source.Reply(BOT_DOES_NOT_EXIST, nick.c_str()); + return; + } + + if (ci->botflags.HasFlag(BS_NOBOT) || (!ci->HasPriv(u, CA_ASSIGN) && !u->HasPriv("botserv/administration"))) + { + source.Reply(ACCESS_DENIED); + return; + } + + if (bi->HasFlag(BI_PRIVATE) && !u->HasCommand("botserv/botserv/assign/private")) + { + source.Reply(ACCESS_DENIED); + return; + } + + if (ci->bi == bi) + { + source.Reply(_("Bot \002%s\002 is already assigned to channel \002%s\002."), ci->bi->nick.c_str(), chan.c_str()); + return; + } + + bool override = !ci->HasPriv(u, CA_ASSIGN); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "for " << bi->nick; + + bi->Assign(u, ci); + source.Reply(_("Bot \002%s\002 has been assigned to %s."), bi->nick.c_str(), ci->name.c_str()); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Assigns a bot pointed out by nick to the channel chan. You\n" + "can then configure the bot for the channel so it fits\n" + "your needs.")); + return true; + } +}; + +class CommandBSUnassign : public Command +{ + public: + CommandBSUnassign(Module *creator) : Command(creator, "botserv/unassign", 1, 1) + { + this->SetDesc(_("Unassigns a bot from a channel")); + this->SetSyntax(_("\037channel\037 \037nick\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + + if (readonly) + { + source.Reply(BOT_ASSIGN_READONLY); + return; + } + + ChannelInfo *ci = cs_findchan(params[0]); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + if (!u->HasPriv("botserv/administration") && !ci->HasPriv(u, CA_ASSIGN)) + { + source.Reply(ACCESS_DENIED); + return; + } + + if (!ci->bi) + { + source.Reply(BOT_NOT_ASSIGNED); + return; + } + + if (ci->HasFlag(CI_PERSIST) && !ModeManager::FindChannelModeByName(CMODE_PERM)) + { + source.Reply(_("You can not unassign bots while persist is set on the channel.")); + return; + } + + bool override = !ci->HasPriv(u, CA_ASSIGN); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "for " << ci->bi->nick; + + ci->bi->UnAssign(u, ci); + source.Reply(_("There is no bot assigned to %s anymore."), ci->name.c_str()); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Unassigns a bot from a channel. When you use this command,\n" + "the bot won't join the channel anymore. However, bot\n" + "configuration for the channel is kept, so you will always\n" + "be able to reassign a bot later without have to reconfigure\n" + "it entirely.")); + return true; + } +}; + +class BSAssign : public Module +{ + CommandBSAssign commandbsassign; + CommandBSUnassign commandbsunassign; + + public: + BSAssign(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandbsassign(this), commandbsunassign(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandbsassign); + ModuleManager::RegisterService(&commandbsunassign); + } +}; + +MODULE_INIT(BSAssign) diff --git a/modules/commands/bs_badwords.cpp b/modules/commands/bs_badwords.cpp new file mode 100644 index 000000000..a27dca2d4 --- /dev/null +++ b/modules/commands/bs_badwords.cpp @@ -0,0 +1,331 @@ +/* BotServ core 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 "module.h" + +class BadwordsListCallback : public NumberList +{ + CommandSource &source; + ChannelInfo *ci; + bool SentHeader; + public: + BadwordsListCallback(CommandSource &_source, ChannelInfo *_ci, const Anope::string &list) : NumberList(list, false), source(_source), ci(_ci), SentHeader(false) + { + } + + ~BadwordsListCallback() + { + if (!SentHeader) + source.Reply(_("No matching entries on %s bad words list."), ci->name.c_str()); + } + + void HandleNumber(unsigned Number) + { + if (!Number || Number > ci->GetBadWordCount()) + return; + + if (!SentHeader) + { + SentHeader = true; + source.Reply(_("Bad words list for %s:\n" + " Num Word Type"), ci->name.c_str()); + } + + DoList(source, Number - 1, ci->GetBadWord(Number - 1)); + } + + static void DoList(CommandSource &source, unsigned Number, BadWord *bw) + { + source.Reply(_(" %3d %-30s %s"), Number + 1, bw->word.c_str(), bw->type == BW_SINGLE ? "(SINGLE)" : (bw->type == BW_START ? "(START)" : (bw->type == BW_END ? "(END)" : ""))); + } +}; + +class BadwordsDelCallback : public NumberList +{ + CommandSource &source; + ChannelInfo *ci; + Command *c; + unsigned Deleted; + bool override; + public: + BadwordsDelCallback(CommandSource &_source, ChannelInfo *_ci, Command *_c, const Anope::string &list) : NumberList(list, true), source(_source), ci(_ci), c(_c), Deleted(0), override(false) + { + if (!ci->HasPriv(source.u, CA_BADWORDS) && source.u->HasPriv("botserv/administration")) + this->override = true; + } + + ~BadwordsDelCallback() + { + if (!Deleted) + source.Reply(_("No matching entries on %s bad words list."), ci->name.c_str()); + else if (Deleted == 1) + source.Reply(_("Deleted 1 entry from %s bad words list."), ci->name.c_str()); + else + source.Reply(_("Deleted %d entries from %s bad words list."), Deleted, ci->name.c_str()); + } + + void HandleNumber(unsigned Number) + { + if (!Number || Number > ci->GetBadWordCount()) + return; + + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source.u, c, ci) << "DEL " << ci->GetBadWord(Number - 1)->word; + ++Deleted; + ci->EraseBadWord(Number - 1); + } +}; + +class CommandBSBadwords : public Command +{ + private: + void DoList(CommandSource &source, ChannelInfo *ci, const Anope::string &word) + { + bool override = !ci->HasPriv(source.u, CA_BADWORDS); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source.u, this, ci) << "LIST"; + + if (!ci->GetBadWordCount()) + source.Reply(_("%s bad words list is empty."), ci->name.c_str()); + else if (!word.empty() && word.find_first_not_of("1234567890,-") == Anope::string::npos) + { + BadwordsListCallback list(source, ci, word); + list.Process(); + } + else + { + bool SentHeader = false; + + for (unsigned i = 0, end = ci->GetBadWordCount(); i < end; ++i) + { + BadWord *bw = ci->GetBadWord(i); + + if (!word.empty() && !Anope::Match(bw->word, word)) + continue; + + if (!SentHeader) + { + SentHeader = true; + source.Reply(_("Bad words list for %s:\n" + " Num Word Type"), ci->name.c_str()); + + } + + BadwordsListCallback::DoList(source, i, bw); + } + + if (!SentHeader) + source.Reply(_("No matching entries on %s bad words list."), ci->name.c_str()); + } + + return; + } + + void DoAdd(CommandSource &source, ChannelInfo *ci, const Anope::string &word) + { + size_t pos = word.rfind(' '); + BadWordType type = BW_ANY; + Anope::string realword = word; + + if (pos != Anope::string::npos) + { + Anope::string opt = word.substr(pos + 1); + if (!opt.empty()) + { + if (opt.equals_ci("SINGLE")) + type = BW_SINGLE; + else if (opt.equals_ci("START")) + type = BW_START; + else if (opt.equals_ci("END")) + type = BW_END; + } + realword = word.substr(0, pos); + } + + if (ci->GetBadWordCount() >= Config->BSBadWordsMax) + { + source.Reply(_("Sorry, you can only have %d bad words entries on a channel."), Config->BSBadWordsMax); + return; + } + + for (unsigned i = 0, end = ci->GetBadWordCount(); i < end; ++i) + { + BadWord *bw = ci->GetBadWord(i); + + if (!bw->word.empty() && ((Config->BSCaseSensitive && realword.equals_cs(bw->word)) || (!Config->BSCaseSensitive && realword.equals_ci(bw->word)))) + { + source.Reply(_("\002%s\002 already exists in %s bad words list."), bw->word.c_str(), ci->name.c_str()); + return; + } + } + + bool override = !ci->HasPriv(source.u, CA_BADWORDS); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source.u, this, ci) << "ADD " << realword; + ci->AddBadWord(realword, type); + + source.Reply(_("\002%s\002 added to %s bad words list."), realword.c_str(), ci->name.c_str()); + + return; + } + + void DoDelete(CommandSource &source, ChannelInfo *ci, const Anope::string &word) + { + /* Special case: is it a number/list? Only do search if it isn't. */ + if (!word.empty() && isdigit(word[0]) && word.find_first_not_of("1234567890,-") == Anope::string::npos) + { + BadwordsDelCallback list(source, ci, this, word); + list.Process(); + } + else + { + unsigned i, end; + BadWord *badword; + + for (i = 0, end = ci->GetBadWordCount(); i < end; ++i) + { + badword = ci->GetBadWord(i); + + if (word.equals_ci(badword->word)) + break; + } + + if (i == end) + { + source.Reply(_("\002%s\002 not found on %s bad words list."), word.c_str(), ci->name.c_str()); + return; + } + + bool override = !ci->HasPriv(source.u, CA_BADWORDS); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source.u, this, ci) << "DEL " << badword->word; + + source.Reply(_("\002%s\002 deleted from %s bad words list."), badword->word.c_str(), ci->name.c_str()); + + ci->EraseBadWord(i); + } + + return; + } + + void DoClear(CommandSource &source, ChannelInfo *ci) + { + bool override = !ci->HasPriv(source.u, CA_BADWORDS); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source.u, this, ci) << "CLEAR"; + + ci->ClearBadWords(); + source.Reply(_("Bad words list is now empty.")); + return; + } + public: + CommandBSBadwords(Module *creator) : Command(creator, "botserv/badwords", 2, 3) + { + this->SetDesc(_("Maintains bad words list")); + this->SetSyntax(_("\037channel\037 ADD \037word\037 [\037SINGLE\037 | \037START\037 | \037END\037]")); + this->SetSyntax(_("\037channel\037 DEL {\037word\037 | \037entry-num\037 | \037list\037}")); + this->SetSyntax(_("\037channel\037 LIST [\037mask\037 | \037list\037]")); + this->SetSyntax(_("\037channel\037 CLEAR")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &cmd = params[1]; + const Anope::string &word = params.size() > 2 ? params[2] : ""; + User *u = source.u; + bool need_args = cmd.equals_ci("LIST") || cmd.equals_ci("CLEAR"); + + if (!need_args && word.empty()) + { + this->OnSyntaxError(source, cmd); + return; + } + + ChannelInfo *ci = cs_findchan(params[0]); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + + if (!ci->HasPriv(u, CA_BADWORDS) && (!need_args || !u->HasPriv("botserv/administration"))) + { + source.Reply(ACCESS_DENIED); + return; + } + + if (readonly) + { + source.Reply(_("Sorry, channel bad words list modification is temporarily disabled.")); + return; + } + + if (cmd.equals_ci("ADD")) + return this->DoAdd(source, ci, word); + else if (cmd.equals_ci("DEL")) + return this->DoDelete(source, ci, word); + else if (cmd.equals_ci("LIST")) + return this->DoList(source, ci, word); + else if (cmd.equals_ci("CLEAR")) + return this->DoClear(source, ci); + else + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Maintains the \002bad words list\002 for a channel. The bad\n" + "words list determines which words are to be kicked\n" + "when the bad words kicker is enabled. For more information,\n" + "type \002%s%s HELP KICK %s\002.\n" + " \n" + "The \002ADD\002 command adds the given word to the\n" + "badword list. If SINGLE is specified, a kick will be\n" + "done only if a user says the entire word. If START is \n" + "specified, a kick will be done if a user says a word\n" + "that starts with \037word\037. If END is specified, a kick\n" + "will be done if a user says a word that ends with\n" + "\037word\037. If you don't specify anything, a kick will\n" + "be issued every time \037word\037 is said by a user.\n" + " \n"), Config->UseStrictPrivMsgString.c_str(), source.owner->nick.c_str(), source.command.c_str()); + source.Reply(_("The \002DEL\002 command removes the given word from the\n" + "bad words list. If a list of entry numbers is given, those\n" + "entries are deleted. (See the example for LIST below.)\n" + " \n" + "The \002LIST\002 command displays the bad words list. If\n" + "a wildcard mask is given, only those entries matching the\n" + "mask are displayed. If a list of entry numbers is given,\n" + "only those entries are shown; for example:\n" + " \002#channel LIST 2-5,7-9\002\n" + " Lists bad words entries numbered 2 through 5 and\n" + " 7 through 9.\n" + " \n" + "The \002CLEAR\002 command clears all entries of the\n" + "bad words list.")); + return true; + } +}; + +class BSBadwords : public Module +{ + CommandBSBadwords commandbsbadwords; + + public: + BSBadwords(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandbsbadwords(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandbsbadwords); + } +}; + +MODULE_INIT(BSBadwords) diff --git a/modules/commands/bs_bot.cpp b/modules/commands/bs_bot.cpp new file mode 100644 index 000000000..7500d1926 --- /dev/null +++ b/modules/commands/bs_bot.cpp @@ -0,0 +1,414 @@ +/* BotServ core 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 "module.h" + +class CommandBSBot : public Command +{ + private: + void DoAdd(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &nick = params[1]; + const Anope::string &user = params[2]; + const Anope::string &host = params[3]; + const Anope::string &real = params[4]; + BotInfo *bi; + + if (findbot(nick)) + { + source.Reply(_("Bot \002%s\002 already exists."), nick.c_str()); + return; + } + + if (nick.length() > Config->NickLen) + { + source.Reply(_("Bot Nicks may only contain valid nick characters.")); + return; + } + + if (user.length() > Config->UserLen) + { + source.Reply(_("Bot Idents may only contain %d characters."), Config->UserLen); + return; + } + + if (host.length() > Config->HostLen) + { + source.Reply(_("Bot Hosts may only contain %d characters."), Config->HostLen); + return; + } + + /* Check the nick is valid re RFC 2812 */ + if (isdigit(nick[0]) || nick[0] == '-') + { + source.Reply(_("Bot Nicks may only contain valid nick characters.")); + return; + } + + for (unsigned i = 0, end = nick.length(); i < end && i < Config->NickLen; ++i) + if (!isvalidnick(nick[i])) + { + source.Reply(_("Bot Nicks may only contain valid nick characters.")); + return; + } + + /* check for hardcored ircd forbidden nicks */ + if (!ircdproto->IsNickValid(nick)) + { + source.Reply(_("Bot Nicks may only contain valid nick characters.")); + return; + } + + /* Check the host is valid re RFC 2812 */ + if (!isValidHost(host, 3)) + { + source.Reply(_("Bot Hosts may only contain valid host characters.")); + return; + } + + for (unsigned i = 0, end = user.length(); i < end && i < Config->UserLen; ++i) + if (!isalnum(user[i])) + { + source.Reply(_("Bot Idents may only contain valid characters."), Config->UserLen); + return; + } + + /* We check whether the nick is registered, and inform the user + * if so. You need to drop the nick manually before you can use + * it as a bot nick from now on -GD + */ + if (findnick(nick)) + { + source.Reply(NICK_ALREADY_REGISTERED, nick.c_str()); + return; + } + + bi = new BotInfo(nick, user, host, real); + + Log(LOG_ADMIN, source.u, this) << "ADD " << bi->GetMask() << " " << bi->realname; + + source.Reply(_("%s!%s@%s (%s) added to the bot list."), bi->nick.c_str(), bi->GetIdent().c_str(), bi->host.c_str(), bi->realname.c_str()); + + FOREACH_MOD(I_OnBotCreate, OnBotCreate(bi)); + return; + } + + void DoChange(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &oldnick = params[1]; + const Anope::string &nick = params.size() > 2 ? params[2] : ""; + const Anope::string &user = params.size() > 3 ? params[3] : ""; + const Anope::string &host = params.size() > 4 ? params[4] : ""; + const Anope::string &real = params.size() > 5 ? params[5] : ""; + BotInfo *bi; + + if (oldnick.empty() || nick.empty()) + { + this->OnSyntaxError(source, "CHANGE"); + return; + } + + if (!(bi = findbot(oldnick))) + { + source.Reply(BOT_DOES_NOT_EXIST, oldnick.c_str()); + return; + } + + if (!oldnick.equals_ci(nick) && nickIsServices(oldnick, false)) + { + source.Reply(BOT_DOES_NOT_EXIST, oldnick.c_str()); + return; + } + + if (nick.length() > Config->NickLen) + { + source.Reply(_("Bot Nicks may only contain valid nick characters.")); + return; + } + + if (!user.empty() && user.length() > Config->UserLen) + { + source.Reply(_("Bot Idents may only contain %d characters."), Config->UserLen); + return; + } + + if (!host.empty() && host.length() > Config->HostLen) + { + source.Reply(_("Bot Hosts may only contain %d characters."), Config->HostLen); + return; + } + + if (!oldnick.equals_ci(nick) && nickIsServices(nick, false)) + { + source.Reply(BOT_DOES_NOT_EXIST, oldnick.c_str()); + return; + } + + /* Checks whether there *are* changes. + * Case sensitive because we may want to change just the case. + * And we must finally check that the nick is not already + * taken by another bot. + */ + if (nick.equals_cs(bi->nick) && (!user.empty() ? user.equals_cs(bi->GetIdent()) : 1) && (!host.empty() ? host.equals_cs(bi->host) : 1) && (!real.empty() ? real.equals_cs(bi->realname) : 1)) + { + source.Reply(_("Old info is equal to the new one.")); + return; + } + + /* Check the nick is valid re RFC 2812 */ + if (isdigit(nick[0]) || nick[0] == '-') + { + source.Reply(_("Bot Nicks may only contain valid nick characters.")); + return; + } + + for (unsigned i = 0, end = nick.length(); i < end && i < Config->NickLen; ++i) + if (!isvalidnick(nick[i])) + { + source.Reply(_("Bot Nicks may only contain valid nick characters.")); + return; + } + + /* check for hardcored ircd forbidden nicks */ + if (!ircdproto->IsNickValid(nick)) + { + source.Reply(_("Bot Nicks may only contain valid nick characters.")); + return; + } + + if (!host.empty() && !isValidHost(host, 3)) + { + source.Reply(_("Bot Hosts may only contain valid host characters.")); + return; + } + + if (!user.empty()) + for (unsigned i = 0, end = user.length(); i < end && i < Config->UserLen; ++i) + if (!isalnum(user[i])) + { + source.Reply(_("Bot Idents may only contain valid characters."), Config->UserLen); + return; + } + + if (!nick.equals_ci(bi->nick) && findbot(nick)) + { + source.Reply(_("Bot \002%s\002 already exists."), nick.c_str()); + return; + } + + if (!nick.equals_ci(bi->nick)) + { + /* We check whether the nick is registered, and inform the user + * if so. You need to drop the nick manually before you can use + * it as a bot nick from now on -GD + */ + if (findnick(nick)) + { + source.Reply(NICK_ALREADY_REGISTERED, nick.c_str()); + return; + } + + /* The new nick is really different, so we remove the Q line for the old nick. */ + if (ircd->sqline) + { + XLine x(bi->nick); + ircdproto->SendSQLineDel(&x); + } + + /* Add a Q line for the new nick */ + XLine x(nick, "Reserved for services"); + ircdproto->SendSQLine(NULL, &x); + } + + if (!user.empty()) + ircdproto->SendQuit(bi, "Quit: Be right back"); + else + { + ircdproto->SendChangeBotNick(bi, nick); + } + + if (!nick.equals_cs(bi->nick)) + bi->SetNewNick(nick); + + if (!user.empty() && !user.equals_cs(bi->GetIdent())) + bi->SetIdent(user); + if (!host.empty() && !host.equals_cs(bi->host)) + bi->host = host; + if (!real.empty() && !real.equals_cs(bi->realname)) + bi->realname = real; + + if (!user.empty()) + { + ircdproto->SendClientIntroduction(bi, ircd->pseudoclient_mode); + bi->RejoinAll(); + } + + source.Reply(_("Bot \002%s\002 has been changed to %s!%s@%s (%s)"), oldnick.c_str(), bi->nick.c_str(), bi->GetIdent().c_str(), bi->host.c_str(), bi->realname.c_str()); + Log(LOG_ADMIN, source.u, this) << "CHANGE " << oldnick << " to " << bi->GetMask() << " " << bi->realname; + + FOREACH_MOD(I_OnBotChange, OnBotChange(bi)); + return; + } + + void DoDel(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &nick = params[1]; + BotInfo *bi; + + if (nick.empty()) + { + this->OnSyntaxError(source, "DEL"); + return; + } + + if (!(bi = findbot(nick))) + { + source.Reply(BOT_DOES_NOT_EXIST, nick.c_str()); + return; + } + + if (nickIsServices(nick, false)) + { + source.Reply(BOT_DOES_NOT_EXIST, nick.c_str()); + return; + } + + FOREACH_MOD(I_OnBotDelete, OnBotDelete(bi)); + + Log(LOG_ADMIN, source.u, this) << "DEL " << bi->nick; + + source.Reply(_("Bot \002%s\002 has been deleted."), nick.c_str()); + delete bi; + return; + } + public: + CommandBSBot(Module *creator) : Command(creator, "botserv/bot", 1, 6) + { + this->SetDesc(_("Maintains network bot list")); + this->SetSyntax(_("\002ADD \037nick\037 \037user\037 \037host\037 \037real\037\002")); + this->SetSyntax(_("\002CHANGE \037oldnick\037 \037newnick\037 [\037user\037 [\037host\037 [\037real\037]]]\002")); + this->SetSyntax(_("\002DEL \037nick\037\002")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &cmd = params[0]; + User *u = source.u; + + if (readonly) + { + source.Reply(_("Sorry, bot modification is temporarily disabled.")); + return; + } + + if (cmd.equals_ci("ADD")) + { + // ADD nick user host real - 5 + if (!u->HasCommand("botserv/bot/add")) + { + source.Reply(ACCESS_DENIED); + return; + } + + if (params.size() < 5) + { + this->OnSyntaxError(source, "ADD"); + return; + } + + std::vector<Anope::string> tempparams = params; + // ADD takes less params than CHANGE, so we need to take 6 if given and append it with a space to 5. + if (tempparams.size() >= 6) + tempparams[4] = tempparams[4] + " " + tempparams[5]; + + return this->DoAdd(source, tempparams); + } + else if (cmd.equals_ci("CHANGE")) + { + // CHANGE oldn newn user host real - 6 + // but only oldn and newn are required + if (!u->HasCommand("botserv/bot/change")) + { + source.Reply(ACCESS_DENIED); + return; + } + + if (params.size() < 3) + { + this->OnSyntaxError(source, "CHANGE"); + return; + } + + return this->DoChange(source, params); + } + else if (cmd.equals_ci("DEL")) + { + // DEL nick + if (!u->HasCommand("botserv/bot/del")) + { + source.Reply(ACCESS_DENIED); + return; + } + + if (params.size() < 1) + { + this->OnSyntaxError(source, "DEL"); + return; + } + + return this->DoDel(source, params); + } + else + this->OnSyntaxError(source, ""); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Allows Services Operators to create, modify, and delete\n" + "bots that users will be able to use on their own\n" + "channels.\n" + " \n" + "\002BOT ADD\002 adds a bot with the given nickname, username,\n" + "hostname and realname. Since no integrity checks are done \n" + "for these settings, be really careful.\n" + "\002BOT CHANGE\002 allows to change nickname, username, hostname\n" + "or realname of a bot without actually delete it (and all\n" + "the data associated with it).\n" + "\002BOT DEL\002 removes the given bot from the bot list. \n" + " \n" + "\002Note\002: you cannot create a bot that has a nick that is\n" + "currently registered. If an unregistered user is currently\n" + "using the nick, they will be killed.")); + return true; + } +}; + +class BSBot : public Module +{ + CommandBSBot commandbsbot; + + public: + BSBot(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandbsbot(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandbsbot); + } +}; + +MODULE_INIT(BSBot) diff --git a/modules/commands/bs_botlist.cpp b/modules/commands/bs_botlist.cpp new file mode 100644 index 000000000..ee7e4d2d8 --- /dev/null +++ b/modules/commands/bs_botlist.cpp @@ -0,0 +1,91 @@ +/* BotServ core 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 "module.h" + +class CommandBSBotList : public Command +{ + public: + CommandBSBotList(Module *creator) : Command(creator, "botserv/botlist", 0, 0) + { + this->SetDesc(_("Lists available bots")); + this->SetSyntax(""); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + unsigned count = 0; + + for (Anope::insensitive_map<BotInfo *>::const_iterator it = BotListByNick.begin(), it_end = BotListByNick.end(); it != it_end; ++it) + { + BotInfo *bi = it->second; + + if (!bi->HasFlag(BI_PRIVATE)) + { + if (!count) + source.Reply(_("Bot list:")); + ++count; + source.Reply(" %-15s (%s@%s)", bi->nick.c_str(), bi->GetIdent().c_str(), bi->host.c_str()); + } + } + + if (u->HasCommand("botserv/botserv/botlist") && count < BotListByNick.size()) + { + source.Reply(_("Bots reserved to IRC operators:")); + + for (Anope::insensitive_map<BotInfo *>::const_iterator it = BotListByNick.begin(), it_end = BotListByNick.end(); it != it_end; ++it) + { + BotInfo *bi = it->second; + + if (bi->HasFlag(BI_PRIVATE)) + { + source.Reply(" %-15s (%s@%s)", bi->nick.c_str(), bi->GetIdent().c_str(), bi->host.c_str()); + ++count; + } + } + } + + if (!count) + source.Reply(_("There are no bots available at this time.\n" + "Ask a Services Operator to create one!")); + else + source.Reply(_("%d bots available."), count); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Lists all available bots on this network.")); + return true; + } +}; + +class BSBotList : public Module +{ + CommandBSBotList commandbsbotlist; + + public: + BSBotList(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandbsbotlist(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandbsbotlist); + } +}; + +MODULE_INIT(BSBotList) diff --git a/modules/commands/bs_control.cpp b/modules/commands/bs_control.cpp new file mode 100644 index 000000000..16a5b372a --- /dev/null +++ b/modules/commands/bs_control.cpp @@ -0,0 +1,158 @@ +/* BotServ core 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 "module.h" + +class CommandBSSay : public Command +{ + public: + CommandBSSay(Module *creator) : Command(creator, "botserv/say", 2, 2) + { + this->SetDesc(_("Makes the bot say the given text on the given channel")); + this->SetSyntax(_("\037channel\037 \037text\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &text = params[1]; + + User *u = source.u; + + ChannelInfo *ci = cs_findchan(params[0]); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + if (!ci->HasPriv(u, CA_SAY)) + { + source.Reply(ACCESS_DENIED); + return; + } + + if (!ci->bi) + { + source.Reply(BOT_NOT_ASSIGNED); + return; + } + + if (!ci->c || !ci->c->FindUser(ci->bi)) + { + source.Reply(BOT_NOT_ON_CHANNEL, ci->name.c_str()); + return; + } + + if (text[0] == '\001') + { + this->OnSyntaxError(source, ""); + return; + } + + ircdproto->SendPrivmsg(ci->bi, ci->name, "%s", text.c_str()); + ci->bi->lastmsg = Anope::CurTime; + + // XXX need a way to find if someone is overriding this + Log(LOG_COMMAND, u, this, ci) << text; + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Makes the bot say the given text on the given channel.")); + return true; + } +}; + +class CommandBSAct : public Command +{ + public: + CommandBSAct(Module *creator) : Command(creator, "botserv/act", 2, 2) + { + this->SetDesc(_("Makes the bot do the equivalent of a \"/me\" command")); + this->SetSyntax(_("\037channel\037 \037text\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + Anope::string message = params[1]; + + ChannelInfo *ci = cs_findchan(params[0]); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + if (!ci->HasPriv(u, CA_SAY)) + { + source.Reply(ACCESS_DENIED); + return; + } + + if (!ci->bi) + { + source.Reply(BOT_NOT_ASSIGNED); + return; + } + + if (!ci->c || !ci->c->FindUser(ci->bi)) + { + source.Reply(BOT_NOT_ON_CHANNEL, ci->name.c_str()); + return; + } + + size_t i = 0; + while ((i = message.find(1)) && i != Anope::string::npos) + message.erase(i, 1); + + ircdproto->SendAction(ci->bi, ci->name, "%s", message.c_str()); + ci->bi->lastmsg = Anope::CurTime; + + // XXX Need to be able to find if someone is overriding this. + Log(LOG_COMMAND, u, this, ci) << message; + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Makes the bot do the equivalent of a \"/me\" command\n" + "on the given channel using the given text.")); + return true; + } +}; + +class BSControl : public Module +{ + CommandBSSay commandbssay; + CommandBSAct commandbsact; + + public: + BSControl(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandbssay(this), commandbsact(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandbssay); + ModuleManager::RegisterService(&commandbsact); + } +}; + +MODULE_INIT(BSControl) diff --git a/modules/commands/bs_info.cpp b/modules/commands/bs_info.cpp new file mode 100644 index 000000000..b48a0b296 --- /dev/null +++ b/modules/commands/bs_info.cpp @@ -0,0 +1,244 @@ +/* BotServ core 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 "module.h" + +class CommandBSInfo : public Command +{ + private: + void send_bot_channels(CommandSource &source, BotInfo *bi) + { + Anope::string buf; + for (registered_channel_map::const_iterator it = RegisteredChannelList.begin(), it_end = RegisteredChannelList.end(); it != it_end; ++it) + { + ChannelInfo *ci = it->second; + + if (ci->bi == bi) + { + if (buf.length() + ci->name.length() > 300) + { + source.Reply("%s", buf.c_str()); + buf.clear(); + } + buf += " " + ci->name + " "; + } + } + + if (!buf.empty()) + source.Reply("%s", buf.c_str()); + return; + } + public: + CommandBSInfo(Module *creator) : Command(creator, "botserv/info", 1, 1) + { + this->SetDesc(_("Allows you to see BotServ information about a channel or a bot")); + this->SetSyntax(_("\002INFO {\037chan\037 | \037nick\037}\002")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &query = params[0]; + + bool need_comma = false; + char buf[BUFSIZE], *end; + + User *u = source.u; + BotInfo *bi = findbot(query); + ChannelInfo *ci; + if (bi) + { + source.Reply(_("Information for bot \002%s\002:"), bi->nick.c_str()); + source.Reply(_(" Mask : %s@%s"), bi->GetIdent().c_str(), bi->host.c_str()); + source.Reply(_(" Real name : %s"), bi->realname.c_str()); + source.Reply(_(" Created : %s"), do_strftime(bi->created).c_str()); + source.Reply(_(" Options : %s"), bi->HasFlag(BI_PRIVATE) ? _("Private") : _("None")); + source.Reply(_(" Used on : %d channel(s)"), bi->chancount); + + if (u->HasPriv("botserv/administration")) + this->send_bot_channels(source, bi); + } + else if ((ci = cs_findchan(query))) + { + if (!ci->HasPriv(u, CA_FOUNDER) && !u->HasPriv("botserv/administration")) + { + source.Reply(ACCESS_DENIED); + return; + } + + source.Reply(CHAN_INFO_HEADER, ci->name.c_str()); + if (ci->bi) + source.Reply(_(" Bot nick : %s"), ci->bi->nick.c_str()); + else + source.Reply(_(" Bot nick : not assigned yet.")); + + if (ci->botflags.HasFlag(BS_KICK_BADWORDS)) + { + if (ci->ttb[TTB_BADWORDS]) + source.Reply(_(" Bad words kicker : %s (%d kick(s) to ban)"), ENABLED, ci->ttb[TTB_BADWORDS]); + else + source.Reply(_(" Bad words kicker : %s"), ENABLED); + } + else + source.Reply(_(" Bad words kicker : %s"), DISABLED); + if (ci->botflags.HasFlag(BS_KICK_BOLDS)) + { + if (ci->ttb[TTB_BOLDS]) + source.Reply(_(" Bolds kicker : %s (%d kick(s) to ban)"), ENABLED, ci->ttb[TTB_BOLDS]); + else + source.Reply(_(" Bolds kicker : %s"), ENABLED); + } + else + source.Reply(_(" Bolds kicker : %s"), DISABLED); + if (ci->botflags.HasFlag(BS_KICK_CAPS)) + { + if (ci->ttb[TTB_CAPS]) + source.Reply(_(" Caps kicker : %s (%d kick(s) to ban; minimum %d/%d%%)"), ENABLED, ci->ttb[TTB_CAPS], ci->capsmin, ci->capspercent); + else + source.Reply(_(" Caps kicker : %s (minimum %d/%d%%)"), ENABLED, ci->capsmin, ci->capspercent); + } + else + source.Reply(_(" Caps kicker : %s"), DISABLED); + if (ci->botflags.HasFlag(BS_KICK_COLORS)) + { + if (ci->ttb[TTB_COLORS]) + source.Reply(_(" Colors kicker : %s (%d kick(s) to ban)"), ENABLED, ci->ttb[TTB_COLORS]); + else + source.Reply(_(" Colors kicker : %s"), ENABLED); + } + else + source.Reply(_(" Colors kicker : %s"), DISABLED); + if (ci->botflags.HasFlag(BS_KICK_FLOOD)) + { + if (ci->ttb[TTB_FLOOD]) + source.Reply(_(" Flood kicker : %s (%d kick(s) to ban; %d lines in %ds)"), ENABLED, ci->ttb[TTB_FLOOD], ci->floodlines, ci->floodsecs); + else + source.Reply(_(" Flood kicker : %s (%d lines in %ds)"), ENABLED, ci->floodlines, ci->floodsecs); + } + else + source.Reply(_(" Flood kicker : %s"), DISABLED); + if (ci->botflags.HasFlag(BS_KICK_REPEAT)) + { + if (ci->ttb[TTB_REPEAT]) + source.Reply(_(" Repeat kicker : %s (%d kick(s) to ban; %d times)"), ENABLED, ci->ttb[TTB_REPEAT], ci->repeattimes); + else + source.Reply(_(" Repeat kicker : %s (%d times)"), ENABLED, ci->repeattimes); + } + else + source.Reply(_(" Repeat kicker : %s"), DISABLED); + if (ci->botflags.HasFlag(BS_KICK_REVERSES)) + { + if (ci->ttb[TTB_REVERSES]) + source.Reply(_(" Reverses kicker : %s (%d kick(s) to ban)"), ENABLED, ci->ttb[TTB_REVERSES]); + else + source.Reply(_(" Reverses kicker : %s"), ENABLED); + } + else + source.Reply(_(" Reverses kicker : %s"), DISABLED); + if (ci->botflags.HasFlag(BS_KICK_UNDERLINES)) + { + if (ci->ttb[TTB_UNDERLINES]) + source.Reply(_(" Underlines kicker : %s (%d kick(s) to ban)"), ENABLED, ci->ttb[TTB_UNDERLINES]); + else + source.Reply(_(" Underlines kicker : %s"), ENABLED); + } + else + source.Reply(_(" Underlines kicker : %s"), DISABLED); + if (ci->botflags.HasFlag(BS_KICK_ITALICS)) + { + if (ci->ttb[TTB_ITALICS]) + source.Reply(_(" Italics kicker : %s (%d kick(s) to ban)"), ENABLED, ci->ttb[TTB_ITALICS]); + else + source.Reply(_(" Italics kicker : %s"), ENABLED); + } + else + source.Reply(_(" Italics kicker : %s"), DISABLED); + if (ci->botflags.HasFlag(BS_KICK_AMSGS)) + { + if (ci->ttb[TTB_AMSGS]) + source.Reply(_(" AMSG kicker : %s (%d kick(s) to ban)"), ENABLED, ci->ttb[TTB_AMSGS]); + else + source.Reply(_(" AMSG kicker : %s"), ENABLED); + } + else + source.Reply(_(" AMSG kicker : %s"), DISABLED); + + if (ci->botflags.HasFlag(BS_MSG_PRIVMSG)) + source.Reply(_(" Fantasy reply : %s"), "PRIVMSG"); + else if (ci->botflags.HasFlag(BS_MSG_NOTICE)) + source.Reply(_(" Fantasy reply : %s"), "NOTICE"); + else if (ci->botflags.HasFlag(BS_MSG_NOTICEOPS)) + source.Reply(_(" Fantasy reply : %s"), "NOTICEOPS"); + + end = buf; + *end = 0; + if (ci->botflags.HasFlag(BS_DONTKICKOPS)) + { + end += snprintf(end, sizeof(buf) - (end - buf), "%s", _("Ops protection")); + need_comma = true; + } + if (ci->botflags.HasFlag(BS_DONTKICKVOICES)) + { + end += snprintf(end, sizeof(buf) - (end - buf), "%s%s", need_comma ? ", " : "", _("Voices protection")); + need_comma = true; + } + if (ci->botflags.HasFlag(BS_FANTASY)) + { + end += snprintf(end, sizeof(buf) - (end - buf), "%s%s", need_comma ? ", " : "", _("Fantasy")); + need_comma = true; + } + if (ci->botflags.HasFlag(BS_GREET)) + { + end += snprintf(end, sizeof(buf) - (end - buf), "%s%s", need_comma ? ", " : "", _("Greet")); + need_comma = true; + } + if (ci->botflags.HasFlag(BS_NOBOT)) + { + end += snprintf(end, sizeof(buf) - (end - buf), "%s%s", need_comma ? ", " : "", _("No bot")); + need_comma = true; + } + source.Reply(_(" Options : %s"), *buf ? buf : _("None")); + } + else + source.Reply(_("\002%s\002 is not a valid bot or registered channel."), query.c_str()); + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Allows you to see %s information about a channel or a bot.\n" + "If the parameter is a channel, then you'll get information\n" + "such as enabled kickers. If the parameter is a nick,\n" + "you'll get information about a bot, such as creation\n" + "time or number of channels it is on."), source.owner->nick.c_str()); + return true; + } +}; + +class BSInfo : public Module +{ + CommandBSInfo commandbsinfo; + + public: + BSInfo(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandbsinfo(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandbsinfo); + } +}; + +MODULE_INIT(BSInfo) diff --git a/modules/commands/bs_kick.cpp b/modules/commands/bs_kick.cpp new file mode 100644 index 000000000..3f433f773 --- /dev/null +++ b/modules/commands/bs_kick.cpp @@ -0,0 +1,1011 @@ +/* BotServ core 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 "module.h" + +class CommandBSKick : public Command +{ + public: + CommandBSKick(Module *creator) : Command(creator, "botserv/kick", 3, 6) + { + this->SetDesc(_("Configures kickers")); + this->SetSyntax(_("\037channel\037 \037option\037 {\037ON|\037} [\037settings\037]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &chan = params[0]; + const Anope::string &option = params[1]; + const Anope::string &value = params[2]; + const Anope::string &ttb = params.size() > 3 ? params[3] : ""; + + User *u = source.u; + ChannelInfo *ci = cs_findchan(params[0]); + + if (readonly) + source.Reply(_("Sorry, kicker configuration is temporarily disabled.")); + else if (ci == NULL) + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + else if (chan.empty() || option.empty() || value.empty()) + this->OnSyntaxError(source, ""); + else if (!value.equals_ci("ON") && !value.equals_ci("OFF")) + this->OnSyntaxError(source, ""); + else if (!ci->HasPriv(u, CA_SET) && !u->HasPriv("botserv/administration")) + source.Reply(ACCESS_DENIED); + else if (!ci->bi) + source.Reply(BOT_NOT_ASSIGNED); + else + { + bool override = !ci->HasPriv(u, CA_SET); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << option << " " << value; + + if (option.equals_ci("BADWORDS")) + { + if (value.equals_ci("ON")) + { + if (!ttb.empty()) + { + try + { + ci->ttb[TTB_BADWORDS] = convertTo<int16>(ttb); + if (ci->ttb[TTB_BADWORDS] < 0) + throw ConvertException(); + } + catch (const ConvertException &) + { + /* reset the value back to 0 - TSL */ + ci->ttb[TTB_BADWORDS] = 0; + source.Reply(_("\002%s\002 cannot be taken as times to ban."), ttb.c_str()); + return; + } + } + else + ci->ttb[TTB_BADWORDS] = 0; + + ci->botflags.SetFlag(BS_KICK_BADWORDS); + if (ci->ttb[TTB_BADWORDS]) + source.Reply(_("Bot will now kick \002bad words\002, and will place a ban after \n" + "%d kicks for the same user. Use the BADWORDS command\n" + "to add or remove a bad word."), ci->ttb[TTB_BADWORDS]); + else + source.Reply(_("Bot will now kick \002bad words\002. Use the BADWORDS command\n" + "to add or remove a bad word.")); + } + else + { + ci->botflags.UnsetFlag(BS_KICK_BADWORDS); + source.Reply(_("Bot won't kick \002bad words\002 anymore.")); + } + } + else if (option.equals_ci("BOLDS")) + { + if (value.equals_ci("ON")) + { + if (!ttb.empty()) + { + try + { + ci->ttb[TTB_BOLDS] = convertTo<int16>(ttb); + if (ci->ttb[TTB_BOLDS] < 0) + throw ConvertException(); + } + catch (const ConvertException &) + { + ci->ttb[TTB_BOLDS] = 0; + source.Reply(_("\002%s\002 cannot be taken as times to ban."), ttb.c_str()); + return; + } + } + else + ci->ttb[TTB_BOLDS] = 0; + ci->botflags.SetFlag(BS_KICK_BOLDS); + if (ci->ttb[TTB_BOLDS]) + source.Reply(_("Bot will now kick \002bolds\002, and will place a ban after\n%d kicks to the same user."), ci->ttb[TTB_BOLDS]); + else + source.Reply(_("Bot will now kick \002bolds\002.")); + } + else + { + ci->botflags.UnsetFlag(BS_KICK_BOLDS); + source.Reply(_("Bot won't kick \002bolds\002 anymore.")); + } + } + else if (option.equals_ci("CAPS")) + { + if (value.equals_ci("ON")) + { + Anope::string min = params.size() > 4 ? params[4] : ""; + Anope::string percent = params.size() > 5 ? params[5] : ""; + + if (!ttb.empty()) + { + try + { + ci->ttb[TTB_CAPS] = convertTo<int16>(ttb); + if (ci->ttb[TTB_CAPS] < 0) + throw ConvertException(); + } + catch (const ConvertException &) + { + ci->ttb[TTB_CAPS] = 0; + source.Reply(_("\002%s\002 cannot be taken as times to ban."), ttb.c_str()); + return; + } + } + else + ci->ttb[TTB_CAPS] = 0; + + ci->capsmin = 10; + try + { + ci->capsmin = convertTo<int16>(min); + } + catch (const ConvertException &) { } + if (ci->capsmin < 1) + ci->capsmin = 10; + + ci->capspercent = 25; + try + { + ci->capspercent = convertTo<int16>(percent); + } + catch (const ConvertException &) { } + if (ci->capspercent < 1 || ci->capspercent > 100) + ci->capspercent = 25; + + ci->botflags.SetFlag(BS_KICK_CAPS); + if (ci->ttb[TTB_CAPS]) + source.Reply(_("Bot will now kick \002caps\002 (they must constitute at least\n" + "%d characters and %d%% of the entire message), and will \n" + "place a ban after %d kicks for the same user."), ci->capsmin, ci->capspercent, ci->ttb[TTB_CAPS]); + else + source.Reply(_("Bot will now kick \002caps\002 (they must constitute at least\n" + "%d characters and %d%% of the entire message)."), ci->capsmin, ci->capspercent); + } + else + { + ci->botflags.UnsetFlag(BS_KICK_CAPS); + source.Reply(_("Bot won't kick \002caps\002 anymore.")); + } + } + else if (option.equals_ci("COLORS")) + { + if (value.equals_ci("ON")) + { + if (!ttb.empty()) + { + try + { + ci->ttb[TTB_COLORS] = convertTo<int16>(ttb); + if (ci->ttb[TTB_COLORS] < 1) + throw ConvertException(); + } + catch (const ConvertException &) + { + ci->ttb[TTB_COLORS] = 0; + source.Reply(_("\002%s\002 cannot be taken as times to ban."), ttb.c_str()); + return; + } + } + else + ci->ttb[TTB_COLORS] = 0; + + ci->botflags.SetFlag(BS_KICK_COLORS); + if (ci->ttb[TTB_COLORS]) + source.Reply(_("Bot will now kick \002colors\002, and will place a ban after %d\nkicks for the same user."), ci->ttb[TTB_COLORS]); + else + source.Reply(_("Bot will now kick \002colors\002.")); + } + else + { + ci->botflags.UnsetFlag(BS_KICK_COLORS); + source.Reply(_("Bot won't kick \002colors\002 anymore.")); + } + } + else if (option.equals_ci("FLOOD")) + { + if (value.equals_ci("ON")) + { + Anope::string lines = params.size() > 4 ? params[4] : ""; + Anope::string secs = params.size() > 5 ? params[5] : ""; + + if (!ttb.empty()) + { + try + { + ci->ttb[TTB_FLOOD] = convertTo<int16>(ttb); + if (ci->ttb[TTB_FLOOD] < 1) + throw ConvertException(); + } + catch (const ConvertException &) + { + ci->ttb[TTB_FLOOD] = 0; + source.Reply(_("\002%s\002 cannot be taken as times to ban."), ttb.c_str()); + return; + } + } + else + ci->ttb[TTB_FLOOD] = 0; + + ci->floodlines = 6; + try + { + ci->floodlines = convertTo<int16>(lines); + } + catch (const ConvertException &) { } + if (ci->floodlines < 2) + ci->floodlines = 6; + + ci->floodsecs = 10; + try + { + ci->floodsecs = convertTo<int16>(secs); + } + catch (const ConvertException &) { } + if (ci->floodsecs < 1) + ci->floodsecs = 10; + if (ci->floodsecs > Config->BSKeepData) + ci->floodsecs = Config->BSKeepData; + + ci->botflags.SetFlag(BS_KICK_FLOOD); + if (ci->ttb[TTB_FLOOD]) + source.Reply(_("Bot will now kick \002flood\002 (%d lines in %d seconds and\nwill place a ban after %d kicks for the same user."), ci->floodlines, ci->floodsecs, ci->ttb[TTB_FLOOD]); + else + source.Reply(_("Bot will now kick \002flood\002 (%d lines in %d seconds)."), ci->floodlines, ci->floodsecs); + } + else + { + ci->botflags.UnsetFlag(BS_KICK_FLOOD); + source.Reply(_("Bot won't kick \002flood\002 anymore.")); + } + } + else if (option.equals_ci("REPEAT")) + { + if (value.equals_ci("ON")) + { + Anope::string times = params.size() > 4 ? params[4] : ""; + + if (!ttb.empty()) + { + try + { + ci->ttb[TTB_REPEAT] = convertTo<int16>(ttb); + if (ci->ttb[TTB_REPEAT] < 0) + throw ConvertException(); + } + catch (const ConvertException &) + { + ci->ttb[TTB_REPEAT] = 0; + source.Reply(_("\002%s\002 cannot be taken as times to ban."), ttb.c_str()); + return; + } + } + else + ci->ttb[TTB_REPEAT] = 0; + + ci->repeattimes = 3; + try + { + ci->repeattimes = convertTo<int16>(times); + } + catch (const ConvertException &) { } + if (ci->repeattimes < 2) + ci->repeattimes = 3; + + ci->botflags.SetFlag(BS_KICK_REPEAT); + if (ci->ttb[TTB_REPEAT]) + source.Reply(_("Bot will now kick \002repeats\002 (users that say %d times\n" + "the same thing), and will place a ban after %d \n" + "kicks for the same user."), ci->repeattimes, ci->ttb[TTB_REPEAT]); + else + source.Reply(_("Bot will now kick \002repeats\002 (users that say %d times\n" + "the same thing)."), ci->repeattimes); + } + else + { + ci->botflags.UnsetFlag(BS_KICK_REPEAT); + source.Reply(_("Bot won't kick \002repeats\002 anymore.")); + } + } + else if (option.equals_ci("REVERSES")) + { + if (value.equals_ci("ON")) + { + if (!ttb.empty()) + { + try + { + ci->ttb[TTB_REVERSES] = convertTo<int16>(ttb); + if (ci->ttb[TTB_REVERSES] < 0) + throw ConvertException(); + } + catch (const ConvertException &) + { + ci->ttb[TTB_REVERSES] = 0; + source.Reply(_("\002%s\002 cannot be taken as times to ban."), ttb.c_str()); + return; + } + } + else + ci->ttb[TTB_REVERSES] = 0; + ci->botflags.SetFlag(BS_KICK_REVERSES); + if (ci->ttb[TTB_REVERSES]) + source.Reply(_("Bot will now kick \002reverses\002, and will place a ban after %d\nkicks for the same user."), ci->ttb[TTB_REVERSES]); + else + source.Reply(_("Bot will now kick \002reverses\002.")); + } + else + { + ci->botflags.UnsetFlag(BS_KICK_REVERSES); + source.Reply(_("Bot won't kick \002reverses\002 anymore.")); + } + } + else if (option.equals_ci("UNDERLINES")) + { + if (value.equals_ci("ON")) + { + if (!ttb.empty()) + { + try + { + ci->ttb[TTB_UNDERLINES] = convertTo<int16>(ttb); + if (ci->ttb[TTB_REVERSES] < 0) + throw ConvertException(); + } + catch (const ConvertException &) + { + ci->ttb[TTB_UNDERLINES] = 0; + source.Reply(_("\002%s\002 cannot be taken as times to ban."), ttb.c_str()); + return; + } + } + else + ci->ttb[TTB_UNDERLINES] = 0; + + ci->botflags.SetFlag(BS_KICK_UNDERLINES); + if (ci->ttb[TTB_UNDERLINES]) + source.Reply(_("Bot will now kick \002underlines\002, and will place a ban after %d\nkicks for the same user."), ci->ttb[TTB_UNDERLINES]); + else + source.Reply(_("Bot will now kick \002underlines\002.")); + } + else + { + ci->botflags.UnsetFlag(BS_KICK_UNDERLINES); + source.Reply(_("Bot won't kick \002underlines\002 anymore.")); + } + } + else if (option.equals_ci("ITALICS")) + { + if (value.equals_ci("ON")) + { + if (!ttb.empty()) + { + try + { + ci->ttb[TTB_ITALICS] = convertTo<int16>(ttb); + if (ci->ttb[TTB_ITALICS] < 0) + throw ConvertException(); + } + catch (const ConvertException &) + { + ci->ttb[TTB_ITALICS] = 0; + source.Reply(_("\002%s\002 cannot be taken as times to ban."), ttb.c_str()); + return; + } + } + else + ci->ttb[TTB_ITALICS] = 0; + + ci->botflags.SetFlag(BS_KICK_ITALICS); + if (ci->ttb[TTB_ITALICS]) + source.Reply(_("Bot will now kick \002italics\002, and will place a ban after\n%d kicks for the same user."), ci->ttb[TTB_ITALICS]); + else + source.Reply(_("Bot will now kick \002italics\002.")); + } + else + { + ci->botflags.UnsetFlag(BS_KICK_ITALICS); + source.Reply(_("Bot won't kick \002italics\002 anymore.")); + } + } + else if (option.equals_ci("AMSGS")) + { + if (value.equals_ci("ON")) + { + if (!ttb.empty()) + { + try + { + ci->ttb[TTB_AMSGS] = convertTo<int16>(ttb); + if (ci->ttb[TTB_AMSGS] < 0) + throw ConvertException(); + } + catch (const ConvertException &) + { + ci->ttb[TTB_AMSGS] = 0; + source.Reply(_("\002%s\002 cannot be taken as times to ban."), ttb.c_str()); + return; + } + } + else + ci->ttb[TTB_AMSGS] = 0; + + ci->botflags.SetFlag(BS_KICK_AMSGS); + if (ci->ttb[TTB_AMSGS]) + source.Reply(_("Bot will now kick for \002amsgs\002, and will place a ban after %d\nkicks for the same user."), ci->ttb[TTB_AMSGS]); + else + source.Reply(_("Bot will now kick for \002amsgs\002")); + } + else + { + ci->botflags.UnsetFlag(BS_KICK_AMSGS); + source.Reply(_("Bot won't kick for \002amsgs\002 anymore.")); + } + } + else + source.Reply(UNKNOWN_OPTION, Config->UseStrictPrivMsgString.c_str(), option.c_str(), this->name.c_str()); + } + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + if (subcommand.empty()) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Configures bot kickers. \037option\037 can be one of:\n" + " \n" + " AMSGS Sets if the bot kicks for amsgs\n" + " BOLDS Sets if the bot kicks bolds\n" + " BADWORDS Sets if the bot kicks bad words\n" + " CAPS Sets if the bot kicks caps\n" + " COLORS Sets if the bot kicks colors\n" + " FLOOD Sets if the bot kicks flooding users\n" + " REPEAT Sets if the bot kicks users who repeat\n" + " themselves\n" + " REVERSES Sets if the bot kicks reverses\n" + " UNDERLINES Sets if the bot kicks underlines\n" + " ITALICS Sets if the bot kicks italics\n" + " \n" + "Type \002%s%s HELP KICK \037option\037\002 for more information\n" + "on a specific option.\n" + " \n" + "Note: access to this command is controlled by the\n" + "level SET."), Config->UseStrictPrivMsgString.c_str(), source.owner->nick.c_str()); + } + else if (subcommand.equals_ci("BADWORDS")) + source.Reply(_("Syntax: \002\037#channel\037 BADWORDS {\037ON|OFF\037} [\037ttb\037]\002\n" + "Sets the bad words kicker on or off. When enabled, this\n" + "option tells the bot to kick users who say certain words\n" + "on the channels.\n" + "You can define bad words for your channel using the\n" + "\002BADWORDS\002 command. Type \002%s%s HELP BADWORDS\002 for\n" + "more information.\n" + "ttb is the number of times a user can be kicked\n" + "before it get banned. Don't give ttb to disable\n" + "the ban system once activated."), Config->UseStrictPrivMsgString.c_str(), source.owner->nick.c_str()); + else if (subcommand.equals_ci("BOLDS")) + source.Reply(_("Syntax: \002\037channel\037 BOLDS {\037ON|OFF\037} [\037ttb\037]\002\n" + "Sets the bolds kicker on or off. When enabled, this\n" + "option tells the bot to kick users who use bolds.\n" + "ttb is the number of times a user can be kicked\n" + "before it get banned. Don't give ttb to disable\n" + "the ban system once activated.")); + else if (subcommand.equals_ci("CAPS")) + source.Reply(_("Syntax: \002\037channel\037 CAPS {\037ON|OFF\037} [\037ttb\037 [\037min\037 [\037percent\037]]]\002\n" + "Sets the caps kicker on or off. When enabled, this\n" + "option tells the bot to kick users who are talking in\n" + "CAPS.\n" + "The bot kicks only if there are at least \002min\002 caps\n" + "and they constitute at least \002percent\002%% of the total \n" + "text line (if not given, it defaults to 10 characters\n" + "and 25%%).\n" + "ttb is the number of times a user can be kicked\n" + "before it get banned. Don't give ttb to disable\n" + "the ban system once activated.")); + else if (subcommand.equals_ci("COLORS")) + source.Reply(_("Syntax: \002\037channel\037 COLORS {\037ON|OFF\037} [\037ttb\037]\002\n" + "Sets the colors kicker on or off. When enabled, this\n" + "option tells the bot to kick users who use colors.\n" + "ttb is the number of times a user can be kicked\n" + "before it get banned. Don't give ttb to disable\n" + "the ban system once activated.")); + else if (subcommand.equals_ci("FLOOD")) + source.Reply(_("Syntax: \002\037channel\037 FLOOD {\037ON|OFF\037} [\037ttb\037 [\037ln\037 [\037secs\037]]]\002\n" + "Sets the flood kicker on or off. When enabled, this\n" + "option tells the bot to kick users who are flooding\n" + "the channel using at least \002ln\002 lines in \002secs\002 seconds\n" + "(if not given, it defaults to 6 lines in 10 seconds).\n" + " \n" + "ttb is the number of times a user can be kicked\n" + "before it get banned. Don't give ttb to disable\n" + "the ban system once activated.")); + else if (subcommand.equals_ci("REPEAT")) + source.Reply(_("Syntax: \002\037#channel\037 REPEAT {\037ON|OFF\037} [\037ttb\037 [\037num\037]]\002\n" + "Sets the repeat kicker on or off. When enabled, this\n" + "option tells the bot to kick users who are repeating\n" + "themselves \002num\002 times (if num is not given, it\n" + "defaults to 3).\n" + "ttb is the number of times a user can be kicked\n" + "before it get banned. Don't give ttb to disable\n" + "the ban system once activated.")); + else if (subcommand.equals_ci("REVERSES")) + source.Reply(_("Syntax: \002\037channel\037 REVERSES {\037ON|OFF\037} [\037ttb\037]\002\n" + "Sets the reverses kicker on or off. When enabled, this\n" + "option tells the bot to kick users who use reverses.\n" + "ttb is the number of times a user can be kicked\n" + "before it get banned. Don't give ttb to disable\n" + "the ban system once activated.")); + else if (subcommand.equals_ci("UNDERLINES")) + source.Reply(_("Syntax: \002\037channel\037 UNDERLINES {\037ON|OFF\037} [\037ttb\037]\002\n" + "Sets the underlines kicker on or off. When enabled, this\n" + "option tells the bot to kick users who use underlines.\n" + "ttb is the number of times a user can be kicked\n" + "before it get banned. Don't give ttb to disable\n" + "the ban system once activated.")); + else if (subcommand.equals_ci("ITALICS")) + source.Reply(_("Syntax: \002\037channel\037 ITALICS {\037ON|OFF\037} [\037ttb\037]\002\n" + "Sets the italics kicker on or off. When enabled, this\n" + "option tells the bot to kick users who use italics.\n" + "ttb is the number of times a user can be kicked\n" + "before it get banned. Don't give ttb to disable\n" + "the ban system once activated.")); + else if (subcommand.equals_ci("AMSGS")) + source.Reply(_("Syntax: \002\037channel\037 AMSGS {\037ON|OFF\037} [\037ttb\037]\002\n" + "Sets the amsg kicker on or off. When enabled, the bot will\n" + "kick users who send the same message to multiple channels\n" + "where BotServ bots are.\n" + "Ttb is the number of times a user can be kicked\n" + "before it get banned. Don't give ttb to disable\n" + "the ban system once activated.")); + else + return false; + + return true; + } +}; + +struct BanData +{ + Anope::string mask; + time_t last_use; + int16 ttb[TTB_SIZE]; + + BanData() + { + this->Clear(); + } + + void Clear() + { + last_use = 0; + for (int i = 0; i < TTB_SIZE; ++i) + this->ttb[i] = 0; + } +}; + +struct UserData +{ + UserData() + { + this->Clear(); + } + + void Clear() + { + last_use = last_start = Anope::CurTime; + lines = times = 0; + lastline.clear(); + } + + /* Data validity */ + time_t last_use; + + /* for flood kicker */ + int16 lines; + time_t last_start; + + /* for repeat kicker */ + Anope::string lastline; + Anope::string lasttarget; + int16 times; +}; + + +class BanDataPurger : public CallBack +{ + public: + BanDataPurger(Module *owner) : CallBack(owner, 300, Anope::CurTime, true) { } + + void Tick(time_t) + { + Log(LOG_DEBUG) << "bs_main: Running bandata purger"; + + for (channel_map::iterator it = ChannelList.begin(), it_end = ChannelList.end(); it != it_end; ++it) + { + Channel *c = it->second; + + std::map<Anope::string, BanData> bandata; + if (c->GetExtRegular("bs_main_bandata", bandata)) + { + for (std::map<Anope::string, BanData>::iterator it2 = bandata.begin(), it2_end = bandata.end(); it2 != it2_end;) + { + const Anope::string &user = it->first; + BanData *bd = &it2->second; + ++it2; + + if (Anope::CurTime - bd->last_use > Config->BSKeepData) + { + bandata.erase(user); + continue; + } + } + + if (bandata.empty()) + c->Shrink("bs_main_bandata"); + } + } + } +}; + +class BSKick : public Module +{ + CommandBSKick commandbskick; + BanDataPurger purger; + + BanData *GetBanData(User *u, Channel *c) + { + std::map<Anope::string, BanData> bandatamap; + if (!c->GetExtRegular("bs_main_bandata", bandatamap)); + c->Extend("bs_main_bandata", new ExtensibleItemRegular<std::map<Anope::string, BanData> >(bandatamap)); + c->GetExtRegular("bs_main_bandata", bandatamap); + + BanData *bd = &bandatamap[u->GetMask()]; + if (bd->last_use && Anope::CurTime - bd->last_use > Config->BSKeepData) + bd->Clear(); + bd->last_use = Anope::CurTime; + return bd; + } + + UserData *GetUserData(User *u, Channel *c) + { + UserData *ud = NULL; + UserContainer *uc = c->FindUser(u); + if (uc != NULL && !uc->GetExtPointer("bs_main_userdata", ud)) + { + ud = new UserData(); + uc->Extend("bs_main_userdata", new ExtensibleItemPointer<UserData>(ud)); + } + return ud; + } + + void check_ban(ChannelInfo *ci, User *u, int ttbtype) + { + /* Don't ban ulines */ + if (u->server->IsULined()) + return; + + BanData *bd = this->GetBanData(u, ci->c); + + ++bd->ttb[ttbtype]; + if (ci->ttb[ttbtype] && bd->ttb[ttbtype] >= ci->ttb[ttbtype]) + { + /* Should not use == here because bd->ttb[ttbtype] could possibly be > ci->ttb[ttbtype] + * if the TTB was changed after it was not set (0) before and the user had already been + * kicked a few times. Bug #1056 - Adam */ + Anope::string mask; + + bd->ttb[ttbtype] = 0; + + get_idealban(ci, u, mask); + + if (ci->c) + ci->c->SetMode(NULL, CMODE_BAN, mask); + FOREACH_MOD(I_OnBotBan, OnBotBan(u, ci, mask)); + } + } + + void bot_kick(ChannelInfo *ci, User *u, const char *message, ...) + { + va_list args; + char buf[1024]; + + if (!ci || !ci->bi || !ci->c || !u || u->server->IsULined()) + return; + + Anope::string fmt = translate(u, message); + va_start(args, message); + vsnprintf(buf, sizeof(buf), fmt.c_str(), args); + va_end(args); + + ci->c->Kick(ci->bi, u, "%s", buf); + } + + public: + BSKick(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandbskick(this), purger(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandbskick); + + ModuleManager::Attach(I_OnPrivmsg, this); + } + + ~BSKick() + { + for (channel_map::const_iterator cit = ChannelList.begin(), cit_end = ChannelList.end(); cit != cit_end; ++cit) + { + cit->second->Shrink("bs_main_userdata"); + cit->second->Shrink("bs_main_bandata"); + } + } + + void OnPrivmsg(User *u, Channel *c, Anope::string &msg) + { + /* Now we can make kicker stuff. We try to order the checks + * from the fastest one to the slowest one, since there's + * no need to process other kickers if a user is kicked before + * the last kicker check. + * + * But FIRST we check whether the user is protected in any + * way. + */ + ChannelInfo *ci = c->ci; + if (ci == NULL) + return; + + bool Allow = true; + if (ci->HasPriv(u, CA_NOKICK)) + Allow = false; + else if (ci->botflags.HasFlag(BS_DONTKICKOPS) && (c->HasUserStatus(u, CMODE_HALFOP) || c->HasUserStatus(u, CMODE_OP) || c->HasUserStatus(u, CMODE_PROTECT) || c->HasUserStatus(u, CMODE_OWNER))) + Allow = false; + else if (ci->botflags.HasFlag(BS_DONTKICKVOICES) && c->HasUserStatus(u, CMODE_VOICE)) + Allow = false; + + if (Allow) + { + Anope::string realbuf = msg; + + /* If it's a /me, cut the CTCP part because the ACTION will cause + * problems with the caps or badwords kicker + */ + if (realbuf.substr(0, 8).equals_ci("\1ACTION ") && realbuf[realbuf.length() - 1] == '\1') + { + realbuf.erase(0, 8); + realbuf.erase(realbuf.length() - 1); + } + + if (realbuf.empty()) + return; + + /* Bolds kicker */ + if (ci->botflags.HasFlag(BS_KICK_BOLDS) && realbuf.find(2) != Anope::string::npos) + { + check_ban(ci, u, TTB_BOLDS); + bot_kick(ci, u, _("Don't use bolds on this channel!")); + return; + } + + /* Color kicker */ + if (ci->botflags.HasFlag(BS_KICK_COLORS) && realbuf.find(3) != Anope::string::npos) + { + check_ban(ci, u, TTB_COLORS); + bot_kick(ci, u, _("Don't use colors on this channel!")); + return; + } + + /* Reverses kicker */ + if (ci->botflags.HasFlag(BS_KICK_REVERSES) && realbuf.find(22) != Anope::string::npos) + { + check_ban(ci, u, TTB_REVERSES); + bot_kick(ci, u, _("Don't use reverses on this channel!")); + return; + } + + /* Italics kicker */ + if (ci->botflags.HasFlag(BS_KICK_ITALICS) && realbuf.find(29) != Anope::string::npos) + { + check_ban(ci, u, TTB_ITALICS); + bot_kick(ci, u, _("Don't use italics on this channel!")); + return; + } + + /* Underlines kicker */ + if (ci->botflags.HasFlag(BS_KICK_UNDERLINES) && realbuf.find(31) != Anope::string::npos) + { + check_ban(ci, u, TTB_UNDERLINES); + bot_kick(ci, u, _("Don't use underlines on this channel!")); + return; + } + + /* Caps kicker */ + if (ci->botflags.HasFlag(BS_KICK_CAPS) && realbuf.length() >= ci->capsmin) + { + int i = 0, l = 0; + + for (unsigned j = 0, end = realbuf.length(); j < end; ++j) + { + if (isupper(realbuf[j])) + ++i; + else if (islower(realbuf[j])) + ++l; + } + + /* i counts uppercase chars, l counts lowercase chars. Only + * alphabetic chars (so islower || isupper) qualify for the + * percentage of caps to kick for; the rest is ignored. -GD + */ + + if ((i || l) && i >= ci->capsmin && i * 100 / (i + l) >= ci->capspercent) + { + check_ban(ci, u, TTB_CAPS); + bot_kick(ci, u, _("Turn caps lock OFF!")); + return; + } + } + + /* Bad words kicker */ + if (ci->botflags.HasFlag(BS_KICK_BADWORDS)) + { + bool mustkick = false; + + /* Normalize the buffer */ + Anope::string nbuf = normalizeBuffer(realbuf); + + for (unsigned i = 0, end = ci->GetBadWordCount(); i < end; ++i) + { + BadWord *bw = ci->GetBadWord(i); + + if (bw->type == BW_ANY && ((Config->BSCaseSensitive && nbuf.find(bw->word) != Anope::string::npos) || (!Config->BSCaseSensitive && nbuf.find_ci(bw->word) != Anope::string::npos))) + mustkick = true; + else if (bw->type == BW_SINGLE) + { + size_t len = bw->word.length(); + + if ((Config->BSCaseSensitive && bw->word.equals_cs(nbuf)) || (!Config->BSCaseSensitive && bw->word.equals_ci(nbuf))) + mustkick = true; + else if (nbuf.find(' ') == len && ((Config->BSCaseSensitive && bw->word.equals_cs(nbuf)) || (!Config->BSCaseSensitive && bw->word.equals_ci(nbuf)))) + mustkick = true; + else + { + if (nbuf.rfind(' ') == nbuf.length() - len - 1 && ((Config->BSCaseSensitive && nbuf.find(bw->word) == nbuf.length() - len) || (!Config->BSCaseSensitive && nbuf.find_ci(bw->word) == nbuf.length() - len))) + mustkick = true; + else + { + Anope::string wordbuf = " " + bw->word + " "; + + if ((Config->BSCaseSensitive && nbuf.find(wordbuf) != Anope::string::npos) || (!Config->BSCaseSensitive && nbuf.find_ci(wordbuf) != Anope::string::npos)) + mustkick = true; + } + } + } + else if (bw->type == BW_START) + { + size_t len = bw->word.length(); + + if ((Config->BSCaseSensitive && nbuf.substr(0, len).equals_cs(bw->word)) || (!Config->BSCaseSensitive && nbuf.substr(0, len).equals_ci(bw->word))) + mustkick = true; + else + { + Anope::string wordbuf = " " + bw->word; + + if ((Config->BSCaseSensitive && nbuf.find(wordbuf) != Anope::string::npos) || (!Config->BSCaseSensitive && nbuf.find_ci(wordbuf) != Anope::string::npos)) + mustkick = true; + } + } + else if (bw->type == BW_END) + { + size_t len = bw->word.length(); + + if ((Config->BSCaseSensitive && nbuf.substr(nbuf.length() - len).equals_cs(bw->word)) || (!Config->BSCaseSensitive && nbuf.substr(nbuf.length() - len).equals_ci(bw->word))) + mustkick = true; + else + { + Anope::string wordbuf = bw->word + " "; + + if ((Config->BSCaseSensitive && nbuf.find(wordbuf) != Anope::string::npos) || (!Config->BSCaseSensitive && nbuf.find_ci(wordbuf) != Anope::string::npos)) + mustkick = true; + } + } + + if (mustkick) + { + check_ban(ci, u, TTB_BADWORDS); + if (Config->BSGentleBWReason) + bot_kick(ci, u, _("Watch your language!")); + else + bot_kick(ci, u, _("Don't use the word \"%s\" on this channel!"), bw->word.c_str()); + + return; + } + } + + UserData *ud = NULL; + + /* Flood kicker */ + if (ci->botflags.HasFlag(BS_KICK_FLOOD)) + { + ud = GetUserData(u, c); + if (ud) + { + if (Anope::CurTime - ud->last_start > ci->floodsecs) + { + ud->last_start = Anope::CurTime; + ud->lines = 0; + } + + ++ud->lines; + if (ud->lines >= ci->floodlines) + { + check_ban(ci, u, TTB_FLOOD); + bot_kick(ci, u, _("Stop flooding!")); + return; + } + } + } + + /* Repeat kicker */ + if (ci->botflags.HasFlag(BS_KICK_REPEAT)) + { + if (!ud) + ud = GetUserData(u, c); + if (ud) + { + + if (!ud->lastline.empty() && !ud->lastline.equals_ci(realbuf)) + { + ud->lastline = realbuf; + ud->times = 0; + } + else + { + if (ud->lastline.empty()) + ud->lastline = realbuf; + ++ud->times; + } + + if (ud->times >= ci->repeattimes) + { + check_ban(ci, u, TTB_REPEAT); + bot_kick(ci, u, _("Stop repeating yourself!")); + return; + } + } + } + + if (ud && ud->lastline.equals_ci(realbuf) && !ud->lasttarget.empty() && !ud->lasttarget.equals_ci(ci->name)) + { + for (UChannelList::iterator it = u->chans.begin(); it != u->chans.end();) + { + Channel *chan = (*it)->chan; + ++it; + + if (chan->ci != NULL && chan->ci->botflags.HasFlag(BS_KICK_AMSGS) && !chan->ci->HasPriv(u, CA_NOKICK)) + { + check_ban(chan->ci, u, TTB_AMSGS); + bot_kick(chan->ci, u, _("Don't use AMSGs!")); + } + } + } + + if (ud) + ud->lasttarget = ci->name; + } + } + } +}; + +MODULE_INIT(BSKick) diff --git a/modules/commands/bs_set.cpp b/modules/commands/bs_set.cpp new file mode 100644 index 000000000..ccd1bb615 --- /dev/null +++ b/modules/commands/bs_set.cpp @@ -0,0 +1,307 @@ +/* BotServ core 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 "module.h" + +class CommandBSSet : public Command +{ + public: + CommandBSSet(Module *creator) : Command(creator, "botserv/set", 3, 3) + { + this->SetDesc(_("Configures bot options")); + this->SetSyntax(_("\037(channel | bot)\037 \037option\037 \037settings\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &chan = params[0]; + const Anope::string &option = params[1]; + const Anope::string &value = params[2]; + + User *u = source.u; + ChannelInfo *ci; + + if (readonly) + source.Reply(_("Sorry, bot option setting is temporarily disabled.")); + else if (u->HasCommand("botserv/botserv/set/private") && option.equals_ci("PRIVATE")) + { + BotInfo *bi; + + if (!(bi = findbot(chan))) + { + source.Reply(BOT_DOES_NOT_EXIST, chan.c_str()); + return; + } + + if (value.equals_ci("ON")) + { + bi->SetFlag(BI_PRIVATE); + source.Reply(_("Private mode of bot %s is now \002on\002."), bi->nick.c_str()); + } + else if (value.equals_ci("OFF")) + { + bi->UnsetFlag(BI_PRIVATE); + source.Reply(_("Private mode of bot %s is now \002off\002."), bi->nick.c_str()); + } + else + this->OnSyntaxError(source, "PRIVATE"); + return; + } + else if (!(ci = cs_findchan(chan))) + source.Reply(CHAN_X_NOT_REGISTERED, chan.c_str()); + else if (!u->HasPriv("botserv/administration") && !ci->HasPriv(u, CA_SET)) + source.Reply(ACCESS_DENIED); + else + { + bool override = !ci->HasPriv(u, CA_SET); + Log(override ? LOG_ADMIN : LOG_COMMAND, u, this, ci) << option << " " << value; + + if (option.equals_ci("DONTKICKOPS")) + { + if (value.equals_ci("ON")) + { + ci->botflags.SetFlag(BS_DONTKICKOPS); + source.Reply(_("Bot \002won't kick ops\002 on channel %s."), ci->name.c_str()); + } + else if (value.equals_ci("OFF")) + { + ci->botflags.UnsetFlag(BS_DONTKICKOPS); + source.Reply(_("Bot \002will kick ops\002 on channel %s."), ci->name.c_str()); + } + else + this->OnSyntaxError(source, "DONTKICKOPS"); + } + else if (option.equals_ci("DONTKICKVOICES")) + { + if (value.equals_ci("ON")) + { + ci->botflags.SetFlag(BS_DONTKICKVOICES); + source.Reply(_("Bot \002won't kick voices\002 on channel %s."), ci->name.c_str()); + } + else if (value.equals_ci("OFF")) + { + ci->botflags.UnsetFlag(BS_DONTKICKVOICES); + source.Reply(_("Bot \002will kick voices\002 on channel %s."), ci->name.c_str()); + } + else + this->OnSyntaxError(source, "DONTKICKVOICE"); + } + else if (option.equals_ci("FANTASY")) + { + if (value.equals_ci("ON")) + { + ci->botflags.SetFlag(BS_FANTASY); + source.Reply(_("Fantasy mode is now \002on\002 on channel %s."), ci->name.c_str()); + } + else if (value.equals_ci("OFF")) + { + ci->botflags.UnsetFlag(BS_FANTASY); + source.Reply(_("Fantasy mode is now \002off\002 on channel %s."), ci->name.c_str()); + } + else + this->OnSyntaxError(source, "FANTASY"); + } + else if (option.equals_ci("GREET")) + { + if (value.equals_ci("ON")) + { + ci->botflags.SetFlag(BS_GREET); + source.Reply(_("Greet mode is now \002on\002 on channel %s."), ci->name.c_str()); + } + else if (value.equals_ci("OFF")) + { + ci->botflags.UnsetFlag(BS_GREET); + source.Reply(_("Greet mode is now \002off\002 on channel %s."), ci->name.c_str()); + } + else + this->OnSyntaxError(source, "GREET"); + } + else if (u->HasCommand("botserv/botserv/set/nobot") && option.equals_ci("NOBOT")) + { + if (value.equals_ci("ON")) + { + ci->botflags.SetFlag(BS_NOBOT); + if (ci->bi) + ci->bi->UnAssign(u, ci); + source.Reply(_("No Bot mode is now \002on\002 on channel %s."), ci->name.c_str()); + } + else if (value.equals_ci("OFF")) + { + ci->botflags.UnsetFlag(BS_NOBOT); + source.Reply(_("No Bot mode is now \002off\002 on channel %s."), ci->name.c_str()); + } + else + this->OnSyntaxError(source, "NOBOT"); + } + else if (option.equals_ci("MSG")) + { + if (value.equals_ci("OFF")) + { + ci->botflags.UnsetFlag(BS_MSG_PRIVMSG); + ci->botflags.UnsetFlag(BS_MSG_NOTICE); + ci->botflags.UnsetFlag(BS_MSG_NOTICEOPS); + source.Reply(_("Fantasy replies will no longer be sent to %s."), ci->name.c_str()); + } + else if (value.equals_ci("PRIVMSG")) + { + ci->botflags.SetFlag(BS_MSG_PRIVMSG); + ci->botflags.UnsetFlag(BS_MSG_NOTICE); + ci->botflags.UnsetFlag(BS_MSG_NOTICEOPS); + source.Reply(_("Fantasy replies will be sent via PRIVMSG to %s."), ci->name.c_str()); + } + else if (value.equals_ci("NOTICE")) + { + ci->botflags.UnsetFlag(BS_MSG_PRIVMSG); + ci->botflags.SetFlag(BS_MSG_NOTICE); + ci->botflags.UnsetFlag(BS_MSG_NOTICEOPS); + source.Reply(_("Fantasy replies will be sent via NOTICE to %s."), ci->name.c_str()); + } + else if (value.equals_ci("NOTICEOPS")) + { + ci->botflags.UnsetFlag(BS_MSG_PRIVMSG); + ci->botflags.UnsetFlag(BS_MSG_NOTICE); + ci->botflags.SetFlag(BS_MSG_NOTICEOPS); + source.Reply(_("Fantasy replies will be sent via NOTICE to channel ops on %s."), ci->name.c_str()); + } + else + this->OnSyntaxError(source, "MSG"); + } + else + source.Reply(UNKNOWN_OPTION, option.c_str(), Config->UseStrictPrivMsgString.c_str(), source.owner->nick.c_str(), this->name.c_str()); + } + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + if (subcommand.empty()) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Configures bot options. \037option\037 can be one of:\n" + " \n" + " DONTKICKOPS To protect ops against bot kicks\n" + " DONTKICKVOICES To protect voices against bot kicks\n" + " GREET Enable greet messages\n" + " FANTASY Enable fantaisist commands\n" + " MSG Configure how fantasy commands should be replied to\n" + " \n" + "Type \002%s%s HELP SET \037option\037\002 for more information\n" + "on a specific option.\n" + "Note: access to this command is controlled by the\n" + "level SET."), Config->UseStrictPrivMsgString.c_str(), source.owner->nick.c_str()); + User *u = source.u; + if (u->IsServicesOper()) + source.Reply(_("These options are reserved to Services Operators:\n" + " \n" + " NOBOT Prevent a bot from being assigned to \n" + " a channel\n" + " PRIVATE Prevent a bot from being assigned by\n" + " non IRC operators")); + } + else if (subcommand.equals_ci("DONTKICKOPS")) + source.Reply(_("Syntax: \002SET \037channel\037 DONTKICKOPS {\037ON|OFF\037}\n" + " \n" + "Enables or disables \002ops protection\002 mode on a channel.\n" + "When it is enabled, ops won't be kicked by the bot\n" + "even if they don't match the NOKICK level.")); + else if (subcommand.equals_ci("DONTKICKVOICES")) + source.Reply(_("Syntax: \002SET \037channel\037 DONTKICKVOICES {\037ON|OFF\037}\n" + " \n" + "Enables or disables \002voices protection\002 mode on a channel.\n" + "When it is enabled, voices won't be kicked by the bot\n" + "even if they don't match the NOKICK level.")); + else if (subcommand.equals_ci("FANTASY")) + source.Reply(_("Syntax: \002SET \037channel\037 FANTASY {\037ON|OFF\037}\n" + "Enables or disables \002fantasy\002 mode on a channel.\n" + "When it is enabled, users will be able to use\n" + "commands !op, !deop, !voice, !devoice,\n" + "!kick, !kb, !unban, !seen on a channel (find how \n" + "to use them; try with or without nick for each, \n" + "and with a reason for some?).\n" + " \n" + "Note that users wanting to use fantaisist\n" + "commands MUST have enough level for both\n" + "the FANTASIA and another level depending\n" + "of the command if required (for example, to use \n" + "!op, user must have enough access for the OPDEOP\n" + "level).")); + else if (subcommand.equals_ci("GREET")) + source.Reply(_("Syntax: \002SET \037channel\037 GREET {\037ON|OFF\037}\n" + " \n" + "Enables or disables \002greet\002 mode on a channel.\n" + "When it is enabled, the bot will display greet\n" + "messages of users joining the channel, provided\n" + "they have enough access to the channel.")); + else if (subcommand.equals_ci("NOBOT")) + source.Reply(_("Syntax: \002SET \037channel\037 NOBOT {\037ON|OFF\037}\002\n" + " \n" + "This option makes a channel be unassignable. If a bot \n" + "is already assigned to the channel, it is unassigned\n" + "automatically when you enable the option.")); + else if (subcommand.equals_ci("PRIVATE")) + source.Reply(_("Syntax: \002SET \037bot-nick\037 PRIVATE {\037ON|OFF\037}\002\n" + "This option prevents a bot from being assigned to a\n" + "channel by users that aren't IRC operators.")); + else if (subcommand.equals_ci("MSG")) + source.Reply(_("Syntax: \002SET \037channel\037 MSG {\037OFF|PRIVMSG|NOTICE|NOTICEOPS\037}\002\n" + " \n" + "Configures how fantasy commands should be returned to the channel. Off disables\n" + "fantasy from replying to the channel. Privmsg, notice, and noticeops message the\n" + "channel, notice the channel, and notice the channel ops respectively.\n" + " \n" + "Note that replies over one line will not use this setting to prevent spam, and will\n" + "go directly to the user who executed it.")); + else + return false; + + return true; + } + + void OnSyntaxError(CommandSource &source, const Anope::string &subcommand) + { + if (subcommand.empty()) + Command::OnSyntaxError(source, ""); + else if (subcommand.equals_ci("PRIVATE")) + this->SendSyntax(source, "\037botname\037 PRIVATE {\037ON|OFF\037}"); + else if (subcommand.equals_ci("DONTKICKOPS")) + this->SendSyntax(source, "\037channel\037 DONTKICKOPS {\037ON|OFF\037}"); + else if (subcommand.equals_ci("DONTKICKVOICES")) + this->SendSyntax(source, "\037channel\037 DONTKICKVOICES {\037ON|OFF\037}"); + else if (subcommand.equals_ci("FANTASY")) + this->SendSyntax(source, "\037channel\037 FANTASY {\037ON|OFF\037}"); + else if (subcommand.equals_ci("GREET")) + this->SendSyntax(source, "\037channel\037 GREET {\037ON|OFF\037}"); + else if (subcommand.equals_ci("MSG")) + this->SendSyntax(source, "\037botname\037 NOBOT {\037ON|OFF\037}"); + else if (subcommand.equals_ci("NOBOT")) + this->SendSyntax(source, "\037botname\037 NOBOT {\037ON|OFF\037}"); + } +}; + +class BSSet : public Module +{ + CommandBSSet commandbsset; + + public: + BSSet(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandbsset(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandbsset); + } +}; + +MODULE_INIT(BSSet) diff --git a/modules/commands/cs_access.cpp b/modules/commands/cs_access.cpp new file mode 100644 index 000000000..777b0c176 --- /dev/null +++ b/modules/commands/cs_access.cpp @@ -0,0 +1,915 @@ +/* ChanServ core 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 "module.h" + +enum +{ + ACCESS_INVALID = -10000, + ACCESS_FOUNDER = 10001 +}; + +static struct AccessLevels +{ + ChannelAccess priv; + int default_level; + Anope::string config_name; + Anope::string name; + Anope::string desc; +} defaultLevels[] = { + { CA_ACCESS_CHANGE, 10, "level_change", "ACC-CHANGE", _("Allowed to modify the access list") }, + { CA_ACCESS_LIST, 1, "level_list", "ACC-LIST", _("Allowed to view the access list") }, + { CA_AKICK, 10, "level_akick", "AKICK", _("Allowed to use AKICK command") }, + { CA_ASSIGN, ACCESS_FOUNDER, "level_assign", "ASSIGN", _("Allowed to assign/unassign a bot") }, + { CA_AUTOHALFOP, 4, "level_autohalfop", "AUTOHALFOP", _("Automatic mode +h") }, + { CA_AUTOOP, 5, "level_autoop", "AUTOOP", _("Automatic channel operator status") }, + { CA_AUTOOWNER, 10000, "level_autoowner", "AUTOOWNER", _("Automatic mode +q") }, + { CA_AUTOPROTECT, 10, "level_autoprotect", "AUTOPROTECT", _("Automatic mode +a") }, + { CA_AUTOVOICE, 3, "level_autovoice", "AUTOVOICE", _("Automatic mode +v") }, + { CA_BADWORDS, 10, "level_badwords", "BADWORDS", _("Allowed to modify channel badwords list") }, + { CA_BAN, 4, "level_ban", "BAN", _("Allowed to use ban users") }, + { CA_FANTASIA, 3, "level_fantasia", "FANTASIA", _("Allowed to use fantaisist commands") }, + { CA_FOUNDER, ACCESS_FOUNDER, "level_founder", "FOUNDER", _("Allowed to issue commands restricted to channel founders") }, + { CA_GETKEY, 5, "level_getkey", "GETKEY", _("Allowed to use GETKEY command") }, + { CA_GREET, 5, "level_greet", "GREET", _("Greet message displayed") }, + { CA_HALFOP, 5, "level_halfop", "HALFOP", _("Allowed to (de)halfop users") }, + { CA_HALFOPME, 4, "level_halfopme", "HALFOPME", _("Allowed to (de)halfop him/herself") }, + { CA_INFO, 10000, "level_info", "INFO", _("Allowed to use INFO command with ALL option") }, + { CA_INVITE, 5, "level_invite", "INVITE", _("Allowed to use the INVITE command") }, + { CA_KICK, 4, "level_kick", "KICK", _("Allowed to use the KICK command") }, + { CA_MEMO, 10, "level_memo", "MEMO", _("Allowed to read channel memos") }, + { CA_MODE, 5, "level_mode", "MODE", _("Allowed to change channel modes") }, + { CA_NOKICK, 1, "level_nokick", "NOKICK", _("Never kicked by the bot's kickers") }, + { CA_OPDEOP, 5, "level_opdeop", "OPDEOP", _("Allowed to (de)op users") }, + { CA_OPDEOPME, 5, "level_opdeopme", "OPDEOPME", _("Allowed to (de)op him/herself") }, + { CA_OWNER, ACCESS_FOUNDER, "level_owner", "OWNER", _("Allowed to use (de)owner users") }, + { CA_OWNERME, 10000, "level_ownerme", "OWNERME", _("Allowed to (de)owner him/herself") }, + { CA_PROTECT, 10000, "level_protect", "PROTECT", _("Allowed to (de)protect users") }, + { CA_PROTECTME, 10, "level_protectme", "PROTECTME", _("Allowed to (de)protect him/herself"), }, + { CA_SAY, 5, "level_say", "SAY", _("Allowed to use SAY and ACT commands") }, + { CA_SIGNKICK, ACCESS_FOUNDER, "level_signkick", "SIGNKICK", _("No signed kick when SIGNKICK LEVEL is used") }, + { CA_SET, 10000, "level_set", "SET", _("Allowed to set channel settings") }, + { CA_TOPIC, 5, "level_topic", "TOPIC", _("Allowed to change channel topics") }, + { CA_UNBAN, 4, "level_unban", "UNBAN", _("Allowed to unban users") }, + { CA_VOICE, 4, "level_voice", "VOICE", _("Allowed to (de)voice users") }, + { CA_VOICEME, 3, "level_voiceme", "VOICEME", _("Allowed to (de)voice him/herself") }, + { CA_SIZE, -1, "", "", "" } +}; + +static void reset_levels(ChannelInfo *ci) +{ + for (int i = 0; defaultLevels[i].priv != CA_SIZE; ++i) + ci->levels[defaultLevels[i].priv] = defaultLevels[i].default_level; +} + +class AccessChanAccess : public ChanAccess +{ + public: + int level; + + AccessChanAccess(AccessProvider *p) : ChanAccess(p) + { + } + + bool Matches(User *u, NickCore *nc) + { + if (u && (Anope::Match(u->nick, this->mask) || Anope::Match(u->GetMask(), this->mask))) + return true; + else if (nc && Anope::Match(nc->display, this->mask)) + return true; + return false; + } + + bool HasPriv(ChannelAccess priv) + { + for (int i = 0; defaultLevels[i].priv != CA_SIZE; ++i) + if (defaultLevels[i].priv == priv) + return DetermineLevel(this) >= this->ci->levels[priv]; + return false; + } + + Anope::string Serialize() + { + return stringify(this->level); + } + + void Unserialize(const Anope::string &data) + { + this->level = convertTo<int>(data); + } + + static int DetermineLevel(ChanAccess *access) + { + if (access->provider->name == "access/access") + { + AccessChanAccess *aaccess = debug_cast<AccessChanAccess *>(access); + return aaccess->level; + } + else + { + int highest = 1; + for (int i = 0; defaultLevels[i].priv != CA_SIZE; ++i) + if (access->ci->levels[defaultLevels[i].priv] > highest && access->HasPriv(defaultLevels[i].priv)) + highest = access->ci->levels[defaultLevels[i].priv]; + return highest; + } + } +}; + +class AccessAccessProvider : public AccessProvider +{ + public: + AccessAccessProvider(Module *o) : AccessProvider(o, "access/access") + { + } + + ChanAccess *Create() + { + return new AccessChanAccess(this); + } +}; + +class AccessListCallback : public NumberList +{ + protected: + CommandSource &source; + ChannelInfo *ci; + bool SentHeader; + public: + AccessListCallback(CommandSource &_source, ChannelInfo *_ci, const Anope::string &numlist) : NumberList(numlist, false), source(_source), ci(_ci), SentHeader(false) + { + } + + ~AccessListCallback() + { + if (SentHeader) + source.Reply(_("End of access list.")); + else + source.Reply(_("No matching entries on %s access list."), ci->name.c_str()); + } + + virtual void HandleNumber(unsigned Number) + { + if (!Number || Number > ci->GetAccessCount()) + return; + + if (!SentHeader) + { + SentHeader = true; + source.Reply(CHAN_ACCESS_LIST_HEADER, ci->name.c_str()); + } + + DoList(source, ci, Number - 1, ci->GetAccess(Number - 1)); + } + + static void DoList(CommandSource &source, ChannelInfo *ci, unsigned Number, ChanAccess *access) + { + source.Reply(_(" %3d %4d %s"), Number + 1, AccessChanAccess::DetermineLevel(access), access->mask.c_str()); + } +}; + +class AccessViewCallback : public AccessListCallback +{ + public: + AccessViewCallback(CommandSource &_source, ChannelInfo *_ci, const Anope::string &numlist) : AccessListCallback(_source, _ci, numlist) + { + } + + void HandleNumber(unsigned Number) + { + if (!Number || Number > ci->GetAccessCount()) + return; + + if (!SentHeader) + { + SentHeader = true; + source.Reply(CHAN_ACCESS_LIST_HEADER, ci->name.c_str()); + } + + DoList(source, ci, Number - 1, ci->GetAccess(Number - 1)); + } + + static void DoList(CommandSource &source, ChannelInfo *ci, unsigned Number, ChanAccess *access) + { + Anope::string timebuf; + if (ci->c) + for (CUserList::const_iterator cit = ci->c->users.begin(), cit_end = ci->c->users.end(); cit != cit_end; ++cit) + if (access->Matches((*cit)->user, (*cit)->user->Account())) + timebuf = "Now"; + if (timebuf.empty()) + { + if (access->last_seen == 0) + timebuf = "Never"; + else + timebuf = do_strftime(access->last_seen); + } + + source.Reply(CHAN_ACCESS_VIEW_AXS_FORMAT, Number + 1, AccessChanAccess::DetermineLevel(access), access->mask.c_str(), access->creator.c_str(), timebuf.c_str()); + } +}; + +class AccessDelCallback : public NumberList +{ + CommandSource &source; + ChannelInfo *ci; + Command *c; + unsigned Deleted; + Anope::string Nicks; + bool Denied; + bool override; + public: + AccessDelCallback(CommandSource &_source, ChannelInfo *_ci, Command *_c, const Anope::string &numlist) : NumberList(numlist, true), source(_source), ci(_ci), c(_c), Deleted(0), Denied(false) + { + if (!ci->HasPriv(source.u, CA_ACCESS_CHANGE) && source.u->HasPriv("chanserv/access/modify")) + this->override = true; + } + + ~AccessDelCallback() + { + if (Denied && !Deleted) + source.Reply(ACCESS_DENIED); + else if (!Deleted) + source.Reply(_("No matching entries on %s access list."), ci->name.c_str()); + else + { + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source.u, c, ci) << "for user" << (Deleted == 1 ? " " : "s ") << Nicks; + + if (Deleted == 1) + source.Reply(_("Deleted 1 entry from %s access list."), ci->name.c_str()); + else + source.Reply(_("Deleted %d entries from %s access list."), Deleted, ci->name.c_str()); + } + } + + void HandleNumber(unsigned Number) + { + if (!Number || Number > ci->GetAccessCount()) + return; + + User *u = source.u; + + ChanAccess *access = ci->GetAccess(Number - 1); + + AccessGroup u_access = ci->AccessFor(u); + ChanAccess *u_highest = u_access.Highest(); + + if (u_highest ? AccessChanAccess::DetermineLevel(u_highest) : 0 <= AccessChanAccess::DetermineLevel(access) && !u->HasPriv("chanserv/access/modify")) + { + Denied = true; + return; + } + + ++Deleted; + if (!Nicks.empty()) + Nicks += ", " + access->mask; + else + Nicks = access->mask; + + FOREACH_MOD(I_OnAccessDel, OnAccessDel(ci, u, access)); + + ci->EraseAccess(Number - 1); + } +}; + +class CommandCSAccess : public Command +{ + void DoAdd(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + + Anope::string mask = params[2]; + int level = ACCESS_INVALID; + + try + { + level = convertTo<int>(params[3]); + } + catch (const ConvertException &) { } + + if (!level) + { + source.Reply(_("Access level must be non-zero.")); + return; + } + + AccessGroup u_access = ci->AccessFor(u); + ChanAccess *highest = u_access.Highest(); + int u_level = (highest ? AccessChanAccess::DetermineLevel(highest) : 0); + if (!ci->HasPriv(u, CA_FOUNDER) && level >= u_level && !u->HasPriv("chanserv/access/modify")) + { + source.Reply(ACCESS_DENIED); + return; + } + else if (level <= ACCESS_INVALID || level >= ACCESS_FOUNDER) + { + source.Reply(CHAN_ACCESS_LEVEL_RANGE, ACCESS_INVALID + 1, ACCESS_FOUNDER - 1); + return; + } + + bool override = !ci->HasPriv(u, CA_ACCESS_CHANGE) || level >= u_level; + + for (unsigned i = ci->GetAccessCount(); i > 0; --i) + { + ChanAccess *access = ci->GetAccess(i - 1); + if (mask.equals_ci(access->mask)) + { + /* Don't allow lowering from a level >= u_level */ + if (AccessChanAccess::DetermineLevel(access) >= u_level && !u->HasPriv("chanserv/access/modify")) + { + source.Reply(ACCESS_DENIED); + return; + } + ci->EraseAccess(i - 1); + break; + } + } + + if (ci->GetAccessCount() >= Config->CSAccessMax) + { + source.Reply(_("Sorry, you can only have %d access entries on a channel."), Config->CSAccessMax); + return; + } + + if (mask.find_first_of("!*@") == Anope::string::npos && findnick(mask) == NULL) + mask += "!*@*"; + + service_reference<AccessProvider> provider("access/access"); + if (!provider) + return; + AccessChanAccess *access = debug_cast<AccessChanAccess *>(provider->Create()); + access->ci = ci; + access->mask = mask; + access->creator = u->nick; + access->level = level; + access->last_seen = 0; + access->created = Anope::CurTime; + ci->AddAccess(access); + + FOREACH_MOD(I_OnAccessAdd, OnAccessAdd(ci, u, access)); + + Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "ADD " << mask << " (level: " << level << ") as level " << u_level; + source.Reply(_("\002%s\002 added to %s access list at level \002%d\002."), access->mask.c_str(), ci->name.c_str(), level); + + return; + } + + void DoDel(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + + const Anope::string &mask = params[2]; + + if (!ci->GetAccessCount()) + source.Reply(_("%s access list is empty."), ci->name.c_str()); + else if (isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos) + { + AccessDelCallback list(source, ci, this, mask); + list.Process(); + } + else + { + AccessGroup u_access = ci->AccessFor(u); + ChanAccess *highest = u_access.Highest(); + int u_level = (highest ? AccessChanAccess::DetermineLevel(highest) : 0); + + for (unsigned i = ci->GetAccessCount(); i > 0; --i) + { + ChanAccess *access = ci->GetAccess(i - 1); + if (mask.equals_ci(access->mask)) + { + int access_level = AccessChanAccess::DetermineLevel(access); + if (!access->mask.equals_ci(u->Account()->display) && u_level <= access_level && !u->HasPriv("chanserv/access/modify")) + source.Reply(ACCESS_DENIED); + else + { + source.Reply(_("\002%s\002 deleted from %s access list."), access->mask.c_str(), ci->name.c_str()); + bool override = !ci->HasPriv(u, CA_ACCESS_CHANGE) && !access->mask.equals_ci(u->Account()->display); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "DEL " << access->mask; + + FOREACH_MOD(I_OnAccessDel, OnAccessDel(ci, u, access)); + ci->EraseAccess(access); + } + return; + } + } + + source.Reply(_("\002%s\002 not found on %s access list."), mask.c_str(), ci->name.c_str()); + } + + return; + } + + void DoList(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> ¶ms) + { + const Anope::string &nick = params.size() > 2 ? params[2] : ""; + + if (!ci->GetAccessCount()) + source.Reply(_("%s access list is empty."), ci->name.c_str()); + else if (!nick.empty() && nick.find_first_not_of("1234567890,-") == Anope::string::npos) + { + AccessListCallback list(source, ci, nick); + list.Process(); + } + else + { + bool SentHeader = false; + + for (unsigned i = 0, end = ci->GetAccessCount(); i < end; ++i) + { + ChanAccess *access = ci->GetAccess(i); + + if (!nick.empty() && !Anope::Match(access->mask, nick)) + continue; + + if (!SentHeader) + { + SentHeader = true; + source.Reply(CHAN_ACCESS_LIST_HEADER, ci->name.c_str()); + } + + AccessListCallback::DoList(source, ci, i, access); + } + + if (SentHeader) + source.Reply(_("End of access list.")); + else + source.Reply(_("No matching entries on %s access list."), ci->name.c_str()); + } + + return; + } + + void DoView(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> ¶ms) + { + const Anope::string &nick = params.size() > 2 ? params[2] : ""; + + if (!ci->GetAccessCount()) + source.Reply(_("%s access list is empty."), ci->name.c_str()); + else if (!nick.empty() && nick.find_first_not_of("1234567890,-") == Anope::string::npos) + { + AccessViewCallback list(source, ci, nick); + list.Process(); + } + else + { + bool SentHeader = false; + + for (unsigned i = 0, end = ci->GetAccessCount(); i < end; ++i) + { + ChanAccess *access = ci->GetAccess(i); + + if (!nick.empty() && !Anope::Match(access->mask, nick)) + continue; + + if (!SentHeader) + { + SentHeader = true; + source.Reply(CHAN_ACCESS_LIST_HEADER, ci->name.c_str()); + } + + AccessViewCallback::DoList(source, ci, i, access); + } + + if (SentHeader) + source.Reply(_("End of access list.")); + else + source.Reply(_("No matching entries on %s access list."), ci->name.c_str()); + } + + return; + } + + void DoClear(CommandSource &source, ChannelInfo *ci) + { + User *u = source.u; + + if (!IsFounder(u, ci) && !u->HasPriv("chanserv/access/modify")) + source.Reply(ACCESS_DENIED); + else + { + ci->ClearAccess(); + + FOREACH_MOD(I_OnAccessClear, OnAccessClear(ci, u)); + + source.Reply(_("Channel %s access list has been cleared."), ci->name.c_str()); + + bool override = !IsFounder(u, ci); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "CLEAR"; + } + + return; + } + + public: + CommandCSAccess(Module *creator) : Command(creator, "chanserv/access", 2, 4) + { + this->SetDesc(_("Modify the list of privileged users")); + this->SetSyntax(_("\037channel\037 ADD \037mask\037 \037level\037")); + this->SetSyntax(_("\037channel\037 DEL {\037mask\037 | \037entry-num\037 | \037list\037}")); + this->SetSyntax(_("\037channel\037 LIST [\037mask\037 | \037list\037]")); + this->SetSyntax(_("\037channel\037 VIEW [\037mask\037 | \037list\037]")); + this->SetSyntax(_("\037channel\037 CLEAR\002")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &cmd = params[1]; + const Anope::string &nick = params.size() > 2 ? params[2] : ""; + const Anope::string &s = params.size() > 3 ? params[3] : ""; + + User *u = source.u; + ChannelInfo *ci = cs_findchan(params[0]); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + bool is_list = cmd.equals_ci("LIST") || cmd.equals_ci("VIEW"); + bool is_clear = cmd.equals_ci("CLEAR"); + bool is_del = cmd.equals_ci("DEL"); + + bool has_access = false; + if (u->HasPriv("chanserv/access/modify")) + has_access = true; + else if (is_list && ci->HasPriv(u, CA_ACCESS_LIST)) + has_access = true; + else if (ci->HasPriv(u, CA_ACCESS_CHANGE)) + has_access = true; + else if (is_del) + { + NickAlias *na = findnick(nick); + if (na && na->nc == u->Account()) + has_access = true; + } + + /* If LIST, we don't *require* any parameters, but we can take any. + * If DEL, we require a nick and no level. + * Else (ADD), we require a level (which implies a nick). */ + if (is_list || is_clear ? 0 : (cmd.equals_ci("DEL") ? (nick.empty() || !s.empty()) : s.empty())) + this->OnSyntaxError(source, cmd); + else if (!has_access) + source.Reply(ACCESS_DENIED); + else if (readonly && !is_list) + source.Reply(_("Sorry, channel access list modification is temporarily disabled.")); + else if (cmd.equals_ci("ADD")) + this->DoAdd(source, ci, params); + else if (cmd.equals_ci("DEL")) + this->DoDel(source, ci, params); + else if (cmd.equals_ci("LIST")) + this->DoList(source, ci, params); + else if (cmd.equals_ci("VIEW")) + this->DoView(source, ci, params); + else if (cmd.equals_ci("CLEAR")) + this->DoClear(source, ci); + else + this->OnSyntaxError(source, ""); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Maintains the \002access list\002 for a channel. The access\n" + "list specifies which users are allowed chanop status or\n" + "access to %s commands on the channel. Different\n" + "user levels allow for access to different subsets of\n" + "privileges. Any nick not on the access list has\n" + "a user level of 0."), source.owner->nick.c_str()); + source.Reply(" "); + source.Reply(_("The \002ACCESS ADD\002 command adds the given mask to the\n" + "access list with the given user level; if the mask is\n" + "already present on the list, its access level is changed to\n" + "the level specified in the command. The \037level\037 specified\n" + "must be less than that of the user giving the command, and\n" + "if the \037mask\037 is already on the access list, the current\n" + "access level of that nick must be less than the access level\n" + "of the user giving the command. When a user joins the channel\n" + "the access they receive is from the highest level entry in the\n" + "access list.")); + source.Reply(" "); + source.Reply(_("The \002ACCESS DEL\002 command removes the given nick from the\n" + "access list. If a list of entry numbers is given, those\n" + "entries are deleted. (See the example for LIST below.)\n" + "You may remove yourself from an access list, even if you\n" + "do not have access to modify that list otherwise.")); + source.Reply(" "); + source.Reply(_("The \002ACCESS LIST\002 command displays the access list. If\n" + "a wildcard mask is given, only those entries matching the\n" + "mask are displayed. If a list of entry numbers is given,\n" + "only those entries are shown; for example:\n" + " \002ACCESS #channel LIST 2-5,7-9\002\n" + " Lists access entries numbered 2 through 5 and\n" + " 7 through 9.\n" + " \n" + "The \002ACCESS VIEW\002 command displays the access list similar\n" + "to \002ACCESS LIST\002 but shows the creator and last used time.\n" + " \n" + "The \002ACCESS CLEAR\002 command clears all entries of the\n" + "access list.")); + source.Reply(_("\002User access levels\002\n" + " \n" + "By default, the following access levels are defined:\n" + " \n" + " \002Founder\002 Full access to %s functions; automatic\n" + " opping upon entering channel. Note\n" + " that only one person may have founder\n" + " status (it cannot be given using the\n" + " \002ACCESS\002 command).\n" + " \002 10\002 Access to AKICK command; automatic opping.\n" + " \002 5\002 Automatic opping.\n" + " \002 3\002 Automatic voicing.\n" + " \002 0\002 No special privileges; can be opped by other\n" + " ops (unless \002secure-ops\002 is set).\n" + " \002 <0\002 May not be opped.\n" + " \n" + "These levels may be changed, or new ones added, using the\n" + "\002LEVELS\002 command; type \002%s%s HELP LEVELS\002 for\n" + "information."), source.owner->nick.c_str(), Config->UseStrictPrivMsgString.c_str(), source.owner->nick.c_str()); + return true; + } +}; + +class CommandCSLevels : public Command +{ + int levelinfo_maxwidth; + + void DoSet(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + + const Anope::string &what = params[2]; + const Anope::string &lev = params[3]; + + int level; + + if (lev.equals_ci("FOUNDER")) + level = ACCESS_FOUNDER; + else + { + try + { + level = convertTo<int>(lev); + } + catch (const ConvertException &) + { + this->OnSyntaxError(source, "SET"); + return; + } + } + + if (level <= ACCESS_INVALID || level > ACCESS_FOUNDER) + source.Reply(_("Level must be between %d and %d inclusive."), ACCESS_INVALID + 1, ACCESS_FOUNDER - 1); + else + { + for (int i = 0; defaultLevels[i].priv != CA_SIZE; ++i) + { + AccessLevels &l = defaultLevels[i]; + + if (what.equals_ci(l.name)) + { + ci->levels[l.priv] = level; + FOREACH_MOD(I_OnLevelChange, OnLevelChange(u, ci, i, level)); + + bool override = !ci->HasPriv(u, CA_FOUNDER); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "SET " << l.name << " to " << level; + + if (level == ACCESS_FOUNDER) + source.Reply(_("Level for %s on channel %s changed to founder only."), l.name.c_str(), ci->name.c_str()); + else + source.Reply(_("Level for \002%s\002 on channel %s changed to \002%d\002."), l.name.c_str(), ci->name.c_str(), level); + return; + } + } + + source.Reply(_("Setting \002%s\002 not known. Type \002%s%s HELP LEVELS \002 for a list of valid settings."), what.c_str(), Config->UseStrictPrivMsgString.c_str(), source.owner->nick.c_str()); + } + + return; + } + + void DoDisable(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + + const Anope::string &what = params[2]; + + /* Don't allow disabling of the founder level. It would be hard to change it back if you dont have access to use this command */ + if (!what.equals_ci("FOUNDER")) + for (int i = 0; defaultLevels[i].priv != CA_SIZE; ++i) + { + AccessLevels &l = defaultLevels[i]; + + if (what.equals_ci(l.name)) + { + ci->levels[l.priv] = ACCESS_INVALID; + FOREACH_MOD(I_OnLevelChange, OnLevelChange(u, ci, i, l.priv)); + + bool override = !ci->HasPriv(u, CA_FOUNDER); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "DISABLE " << l.name; + + source.Reply(_("\002%s\002 disabled on channel %s."), l.name.c_str(), ci->name.c_str()); + return; + } + } + + source.Reply(_("Setting \002%s\002 not known. Type \002%s%s HELP LEVELS \002 for a list of valid settings."), what.c_str(), Config->UseStrictPrivMsgString.c_str(), source.owner->nick.c_str()); + + return; + } + + void DoList(CommandSource &source, ChannelInfo *ci) + { + source.Reply(_("Access level settings for channel %s:"), ci->name.c_str()); + + if (!levelinfo_maxwidth) + for (int i = 0; defaultLevels[i].priv != CA_SIZE; ++i) + { + AccessLevels &l = defaultLevels[i]; + + int len = l.name.length(); + if (len > levelinfo_maxwidth) + levelinfo_maxwidth = len; + } + + for (int i = 0; defaultLevels[i].priv != CA_SIZE; ++i) + { + AccessLevels &l = defaultLevels[i]; + int j = ci->levels[l.priv]; + + if (j == ACCESS_INVALID) + source.Reply(_(" %-*s (disabled)"), levelinfo_maxwidth, l.name.c_str()); + else if (j == ACCESS_FOUNDER) + source.Reply(_(" %-*s (founder only)"), levelinfo_maxwidth, l.name.c_str()); + else + source.Reply(_(" %-*s %d"), levelinfo_maxwidth, l.name.c_str(), j); + } + + return; + } + + void DoReset(CommandSource &source, ChannelInfo *ci) + { + User *u = source.u; + + reset_levels(ci); + FOREACH_MOD(I_OnLevelChange, OnLevelChange(u, ci, -1, 0)); + + bool override = !ci->HasPriv(u, CA_FOUNDER); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "RESET"; + + source.Reply(_("Access levels for \002%s\002 reset to defaults."), ci->name.c_str()); + return; + } + + public: + CommandCSLevels(Module *creator) : Command(creator, "chanserv/levels", 2, 4), levelinfo_maxwidth(0) + { + this->SetDesc(_("Redefine the meanings of access levels")); + this->SetSyntax(_("\037channel\037 SET \037type\037 \037level\037")); + this->SetSyntax(_("\037channel\037 {DIS | DISABLE} \037type\037")); + this->SetSyntax(_("\037channel\037 LIST")); + this->SetSyntax(_("\037channel\037 RESET")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &cmd = params[1]; + const Anope::string &what = params.size() > 2 ? params[2] : ""; + const Anope::string &s = params.size() > 3 ? params[3] : ""; + + User *u = source.u; + + ChannelInfo *ci = cs_findchan(params[0]); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + /* If SET, we want two extra parameters; if DIS[ABLE] or FOUNDER, we want only + * one; else, we want none. + */ + if (cmd.equals_ci("SET") ? s.empty() : (cmd.substr(0, 3).equals_ci("DIS") ? (what.empty() || !s.empty()) : !what.empty())) + this->OnSyntaxError(source, cmd); + else if (!ci->HasPriv(u, CA_FOUNDER) && !u->HasPriv("chanserv/access/modify")) + source.Reply(ACCESS_DENIED); + else if (cmd.equals_ci("SET")) + this->DoSet(source, ci, params); + else if (cmd.equals_ci("DIS") || cmd.equals_ci("DISABLE")) + this->DoDisable(source, ci, params); + else if (cmd.equals_ci("LIST")) + this->DoList(source, ci); + else if (cmd.equals_ci("RESET")) + this->DoReset(source, ci); + else + this->OnSyntaxError(source, ""); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + if (subcommand.equals_ci("DESC")) + { + source.Reply(_("The following feature/function names are understood.")); + if (!levelinfo_maxwidth) + for (int i = 0; defaultLevels[i].priv != CA_SIZE; ++i) + { + AccessLevels &l = defaultLevels[i]; + + int len = l.name.length(); + if (len > levelinfo_maxwidth) + levelinfo_maxwidth = len; + } + for (int i = 0; defaultLevels[i].priv != CA_SIZE; ++i) + { + AccessLevels &l = defaultLevels[i]; + source.Reply(_(" %-*s %s"), levelinfo_maxwidth, l.name.c_str(), translate(source.u, l.desc.c_str())); + } + } + else + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("The \002LEVELS\002 command allows fine control over the meaning of\n" + "the numeric access levels used for channels. With this\n" + "command, you can define the access level required for most\n" + "of %s's functions. (The \002SET FOUNDER\002 and this command\n" + "are always restricted to the channel founder.)\n" + " \n" + "\002LEVELS SET\002 allows the access level for a function or group of\n" + "functions to be changed. \002LEVELS DISABLE\002 (or \002DIS\002 for short)\n" + "disables an automatic feature or disallows access to a\n" + "function by anyone, INCLUDING the founder (although, the founder\n" + "can always reenable it).\n" + " \n" + "\002LEVELS LIST\002 shows the current levels for each function or\n" + "group of functions. \002LEVELS RESET\002 resets the levels to the\n" + "default levels of a newly-created channel (see\n" + "\002HELP ACCESS LEVELS\002).\n" + " \n" + "For a list of the features and functions whose levels can be\n" + "set, see \002HELP LEVELS DESC\002."), source.owner->nick.c_str()); + } + return true; + } +}; + +class CSAccess : public Module +{ + AccessAccessProvider accessprovider; + CommandCSAccess commandcsaccess; + CommandCSLevels commandcslevels; + + public: + CSAccess(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + accessprovider(this), commandcsaccess(this), commandcslevels(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&accessprovider); + ModuleManager::RegisterService(&commandcsaccess); + ModuleManager::RegisterService(&commandcslevels); + + Implementation i[] = { I_OnReload, I_OnChanRegistered }; + ModuleManager::Attach(i, this, 2); + + this->OnReload(); + } + + void OnReload() + { + ConfigReader config; + + for (int i = 0; defaultLevels[i].priv != CA_SIZE; ++i) + { + AccessLevels &l = defaultLevels[i]; + + const Anope::string &value = config.ReadValue("chanserv", l.config_name, "", 0); + if (value.equals_ci("founder")) + l.default_level = ACCESS_FOUNDER; + else + l.default_level = config.ReadInteger("chanserv", l.config_name, 0, false); + } + } + + void OnChanRegistered(ChannelInfo *ci) + { + reset_levels(ci); + } +}; + +MODULE_INIT(CSAccess) diff --git a/modules/commands/cs_akick.cpp b/modules/commands/cs_akick.cpp new file mode 100644 index 000000000..437bf5572 --- /dev/null +++ b/modules/commands/cs_akick.cpp @@ -0,0 +1,564 @@ +/* ChanServ core 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 "module.h" + +static void split_usermask(const Anope::string &mask, Anope::string &nick, Anope::string &user, Anope::string &host) +{ + size_t ex = mask.find('!'), at = mask.find('@', ex == Anope::string::npos ? 0 : ex + 1); + if (ex == Anope::string::npos) + { + if (at == Anope::string::npos) + { + nick = mask; + user = host = "*"; + } + else + { + nick = "*"; + user = mask.substr(0, at); + host = mask.substr(at + 1); + } + } + else + { + nick = mask.substr(0, ex); + if (at == Anope::string::npos) + { + user = mask.substr(ex + 1); + host = "*"; + } + else + { + user = mask.substr(ex + 1, at - ex - 1); + host = mask.substr(at + 1); + } + } +} + +class AkickListCallback : public NumberList +{ + protected: + CommandSource &source; + ChannelInfo *ci; + bool SentHeader; + public: + AkickListCallback(CommandSource &_source, ChannelInfo *_ci, const Anope::string &numlist) : NumberList(numlist, false), source(_source), ci(_ci), SentHeader(false) + { + } + + ~AkickListCallback() + { + if (!SentHeader) + source.Reply(_("No matching entries on %s autokick list."), ci->name.c_str()); + } + + virtual void HandleNumber(unsigned Number) + { + if (!Number || Number > ci->GetAkickCount()) + return; + + if (!SentHeader) + { + SentHeader = true; + source.Reply(_("Autokick list for %s:"), ci->name.c_str()); + } + + DoList(source, ci, Number - 1, ci->GetAkick(Number - 1)); + } + + static void DoList(CommandSource &source, ChannelInfo *ci, unsigned index, AutoKick *akick) + { + source.Reply(_(" %3d %s (%s)"), index + 1, akick->HasFlag(AK_ISNICK) ? akick->nc->display.c_str() : akick->mask.c_str(), !akick->reason.empty() ? akick->reason.c_str() : NO_REASON); + } +}; + +class AkickViewCallback : public AkickListCallback +{ + public: + AkickViewCallback(CommandSource &_source, ChannelInfo *_ci, const Anope::string &numlist) : AkickListCallback(_source, _ci, numlist) + { + } + + void HandleNumber(unsigned Number) + { + if (!Number || Number > ci->GetAkickCount()) + return; + + if (!SentHeader) + { + SentHeader = true; + source.Reply(_("Autokick list for %s:"), ci->name.c_str()); + } + + DoList(source, ci, Number - 1, ci->GetAkick(Number - 1)); + } + + static void DoList(CommandSource &source, ChannelInfo *ci, unsigned index, AutoKick *akick) + { + Anope::string timebuf; + + if (akick->addtime) + timebuf = do_strftime(akick->addtime); + else + timebuf = UNKNOWN; + + source.Reply(CHAN_AKICK_VIEW_FORMAT, index + 1, akick->HasFlag(AK_ISNICK) ? akick->nc->display.c_str() : akick->mask.c_str(), !akick->creator.empty() ? akick->creator.c_str() : UNKNOWN, timebuf.c_str(), !akick->reason.empty() ? akick->reason.c_str() : _(NO_REASON)); + + if (akick->last_used) + source.Reply(_(" Last used %s"), do_strftime(akick->last_used).c_str()); + } +}; + +class AkickDelCallback : public NumberList +{ + CommandSource &source; + ChannelInfo *ci; + Command *c; + unsigned Deleted; + public: + AkickDelCallback(CommandSource &_source, ChannelInfo *_ci, Command *_c, const Anope::string &list) : NumberList(list, true), source(_source), ci(_ci), c(_c), Deleted(0) + { + } + + ~AkickDelCallback() + { + User *u = source.u; + bool override = !ci->HasPriv(u, CA_AKICK); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, c, ci) << "DEL on " << Deleted << " users"; + + if (!Deleted) + source.Reply(_("No matching entries on %s autokick list."), ci->name.c_str()); + else if (Deleted == 1) + source.Reply(_("Deleted 1 entry from %s autokick list."), ci->name.c_str()); + else + source.Reply(_("Deleted %d entries from %s autokick list."), Deleted, ci->name.c_str()); + } + + void HandleNumber(unsigned Number) + { + if (!Number || Number > ci->GetAkickCount()) + return; + + ++Deleted; + ci->EraseAkick(Number - 1); + } +}; + +class CommandCSAKick : public Command +{ + void DoAdd(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + + Anope::string mask = params[2]; + Anope::string reason = params.size() > 3 ? params[3] : ""; + NickAlias *na = findnick(mask); + NickCore *nc = NULL; + AutoKick *akick; + + if (!na) + { + Anope::string nick, user, host; + + split_usermask(mask, nick, user, host); + mask = nick + "!" + user + "@" + host; + } + else + nc = na->nc; + + /* Check excepts BEFORE we get this far */ + if (ci->c) + { + std::pair<Channel::ModeList::iterator, Channel::ModeList::iterator> modes = ci->c->GetModeList(CMODE_EXCEPT); + for (; modes.first != modes.second; ++modes.first) + { + if (Anope::Match(modes.first->second, mask)) + { + source.Reply(CHAN_EXCEPTED, mask.c_str(), ci->name.c_str()); + return; + } + } + } + + /* Check whether target nick has equal/higher access + * or whether the mask matches a user with higher/equal access - Viper */ + if (ci->HasFlag(CI_PEACE) && nc) + { + AccessGroup nc_access = ci->AccessFor(nc), u_access = ci->AccessFor(u); + if (nc == ci->GetFounder() || nc_access >= u_access) + { + source.Reply(ACCESS_DENIED); + return; + } + } + else if (ci->HasFlag(CI_PEACE)) + { + /* Match against all currently online users with equal or + * higher access. - Viper */ + for (Anope::insensitive_map<User *>::iterator it = UserListByNick.begin(), it_end = UserListByNick.end(); it != it_end; ++it) + { + User *u2 = it->second; + + AccessGroup nc_access = ci->AccessFor(nc), u_access = ci->AccessFor(u); + Entry entry_mask(CMODE_BEGIN, mask); + + if ((ci->HasPriv(u2, CA_FOUNDER) || nc_access >= u_access) && entry_mask.Matches(u2)) + { + source.Reply(ACCESS_DENIED); + return; + } + } + + /* Match against the lastusermask of all nickalias's with equal + * or higher access. - Viper */ + for (nickalias_map::const_iterator it = NickAliasList.begin(), it_end = NickAliasList.end(); it != it_end; ++it) + { + na = it->second; + + AccessGroup nc_access = ci->AccessFor(na->nc), u_access = ci->AccessFor(u); + if (na->nc && (na->nc == ci->GetFounder() || nc_access >= u_access)) + { + Anope::string buf = na->nick + "!" + na->last_usermask; + if (Anope::Match(buf, mask)) + { + source.Reply(ACCESS_DENIED); + return; + } + } + } + } + + for (unsigned j = 0, end = ci->GetAkickCount(); j < end; ++j) + { + akick = ci->GetAkick(j); + if (akick->HasFlag(AK_ISNICK) ? akick->nc == nc : mask.equals_ci(akick->mask)) + { + source.Reply(_("\002%s\002 already exists on %s autokick list."), akick->HasFlag(AK_ISNICK) ? akick->nc->display.c_str() : akick->mask.c_str(), ci->name.c_str()); + return; + } + } + + if (ci->GetAkickCount() >= Config->CSAutokickMax) + { + source.Reply(_("Sorry, you can only have %d autokick masks on a channel."), Config->CSAutokickMax); + return; + } + + if (nc) + akick = ci->AddAkick(u->nick, nc, reason); + else + akick = ci->AddAkick(u->nick, mask, reason); + + bool override = !ci->HasPriv(u, CA_AKICK); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "ADD " << mask << ": " << reason; + + FOREACH_MOD(I_OnAkickAdd, OnAkickAdd(u, ci, akick)); + + source.Reply(_("\002%s\002 added to %s autokick list."), mask.c_str(), ci->name.c_str()); + + this->DoEnforce(source, ci); + } + + void DoDel(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + + const Anope::string &mask = params[2]; + AutoKick *akick; + unsigned i, end; + + if (!ci->GetAkickCount()) + { + source.Reply(_("%s autokick list is empty."), ci->name.c_str()); + return; + } + + /* Special case: is it a number/list? Only do search if it isn't. */ + if (isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos) + { + AkickDelCallback list(source, ci, this, mask); + list.Process(); + } + else + { + NickAlias *na = findnick(mask); + NickCore *nc = na ? na->nc : NULL; + + for (i = 0, end = ci->GetAkickCount(); i < end; ++i) + { + akick = ci->GetAkick(i); + + if ((akick->HasFlag(AK_ISNICK) && akick->nc == nc) || (!akick->HasFlag(AK_ISNICK) && mask.equals_ci(akick->mask))) + break; + } + + if (i == ci->GetAkickCount()) + { + source.Reply(_("\002%s\002 not found on %s autokick list."), mask.c_str(), ci->name.c_str()); + return; + } + + bool override = !ci->HasPriv(u, CA_AKICK); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "DEL " << mask; + + ci->EraseAkick(i); + + source.Reply(_("\002%s\002 deleted from %s autokick list."), mask.c_str(), ci->name.c_str()); + } + } + + void DoList(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + + const Anope::string &mask = params.size() > 2 ? params[2] : ""; + + bool override = !ci->HasPriv(u, CA_AKICK); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "LIST"; + + if (!ci->GetAkickCount()) + { + source.Reply(_("%s autokick list is empty."), ci->name.c_str()); + return; + } + + if (!mask.empty() && isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos) + { + AkickListCallback list(source, ci, mask); + list.Process(); + } + else + { + bool SentHeader = false; + + for (unsigned i = 0, end = ci->GetAkickCount(); i < end; ++i) + { + AutoKick *akick = ci->GetAkick(i); + + if (!mask.empty()) + { + if (!akick->HasFlag(AK_ISNICK) && !Anope::Match(akick->mask, mask)) + continue; + if (akick->HasFlag(AK_ISNICK) && !Anope::Match(akick->nc->display, mask)) + continue; + } + + if (!SentHeader) + { + SentHeader = true; + source.Reply(_("Autokick list for %s:"), ci->name.c_str()); + } + + AkickListCallback::DoList(source, ci, i, akick); + } + + if (!SentHeader) + source.Reply(_("No matching entries on %s autokick list."), ci->name.c_str()); + } + } + + void DoView(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + + const Anope::string &mask = params.size() > 2 ? params[2] : ""; + + bool override = !ci->HasPriv(u, CA_AKICK); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "VIEW"; + + if (!ci->GetAkickCount()) + { + source.Reply(_("%s autokick list is empty."), ci->name.c_str()); + return; + } + + if (!mask.empty() && isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos) + { + AkickViewCallback list(source, ci, mask); + list.Process(); + } + else + { + bool SentHeader = false; + + for (unsigned i = 0, end = ci->GetAkickCount(); i < end; ++i) + { + AutoKick *akick = ci->GetAkick(i); + + if (!mask.empty()) + { + if (!akick->HasFlag(AK_ISNICK) && !Anope::Match(akick->mask, mask)) + continue; + if (akick->HasFlag(AK_ISNICK) && !Anope::Match(akick->nc->display, mask)) + continue; + } + + if (!SentHeader) + { + SentHeader = true; + source.Reply(_("Autokick list for %s:"), ci->name.c_str()); + } + + AkickViewCallback::DoList(source, ci, i, akick); + } + + if (!SentHeader) + source.Reply(_("No matching entries on %s autokick list."), ci->name.c_str()); + } + } + + void DoEnforce(CommandSource &source, ChannelInfo *ci) + { + User *u = source.u; + Channel *c = ci->c; + int count = 0; + + if (!c) + { + source.Reply(CHAN_X_NOT_IN_USE, ci->name.c_str()); + return; + } + + for (CUserList::iterator it = c->users.begin(), it_end = c->users.end(); it != it_end; ) + { + UserContainer *uc = *it++; + + if (ci->CheckKick(uc->user)) + ++count; + } + + bool override = !ci->HasPriv(u, CA_AKICK); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "ENFORCE, affects " << count << " users"; + + source.Reply(_("AKICK ENFORCE for \002%s\002 complete; \002%d\002 users were affected."), ci->name.c_str(), count); + } + + void DoClear(CommandSource &source, ChannelInfo *ci) + { + User *u = source.u; + bool override = !ci->HasPriv(u, CA_AKICK); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "CLEAR"; + + ci->ClearAkick(); + source.Reply(_("Channel %s akick list has been cleared."), ci->name.c_str()); + } + + public: + CommandCSAKick(Module *creator) : Command(creator, "chanserv/akick", 2, 4) + { + this->SetDesc(_("Maintain the AutoKick list")); + this->SetSyntax(_("\037channel\037 ADD {\037nick\037 | \037mask\037} [\037reason\037]")); + this->SetSyntax(_("\037channel\037 DEL {\037nick\037 | \037mask\037 | \037entry-num\037 | \037list\037}")); + this->SetSyntax(_("\037channel\037 LIST [\037mask\037 | \037entry-num\037 | \037list\037]")); + this->SetSyntax(_("\002AKICK \037channel\037 VIEW [\037mask\037 | \037entry-num\037 | \037list\037]")); + this->SetSyntax(_("\037channel\037 ENFORCE")); + this->SetSyntax(_("\037channel\037 CLEAR")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + Anope::string chan = params[0]; + Anope::string cmd = params[1]; + Anope::string mask = params.size() > 2 ? params[2] : ""; + + User *u = source.u; + + ChannelInfo *ci = cs_findchan(params[0]); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + if (mask.empty() && (cmd.equals_ci("ADD") || cmd.equals_ci("DEL"))) + this->OnSyntaxError(source, cmd); + else if (!ci->HasPriv(u, CA_AKICK) && !u->HasPriv("chanserv/access/modify")) + source.Reply(ACCESS_DENIED); + else if (!cmd.equals_ci("LIST") && !cmd.equals_ci("VIEW") && !cmd.equals_ci("ENFORCE") && readonly) + source.Reply(_("Sorry, channel autokick list modification is temporarily disabled.")); + else if (cmd.equals_ci("ADD")) + this->DoAdd(source, ci, params); + else if (cmd.equals_ci("DEL")) + this->DoDel(source, ci, params); + else if (cmd.equals_ci("LIST")) + this->DoList(source, ci, params); + else if (cmd.equals_ci("VIEW")) + this->DoView(source, ci, params); + else if (cmd.equals_ci("ENFORCE")) + this->DoEnforce(source, ci); + else if (cmd.equals_ci("CLEAR")) + this->DoClear(source, ci); + else + this->OnSyntaxError(source, ""); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Maintains the \002AutoKick list\002 for a channel. If a user\n" + "on the AutoKick list attempts to join the channel,\n" + "%s will ban that user from the channel, then kick\n" + "the user.\n" + " \n" + "The \002AKICK ADD\002 command adds the given nick or usermask\n" + "to the AutoKick list. If a \037reason\037 is given with\n" + "the command, that reason will be used when the user is\n" + "kicked; if not, the default reason is \"You have been\n" + "banned from the channel\".\n" + "When akicking a \037registered nick\037 the nickserv account\n" + "will be added to the akick list instead of the mask.\n" + "All users within that nickgroup will then be akicked.\n"), + source.owner->nick.c_str()); + source.Reply(_( + " \n" + "The \002AKICK DEL\002 command removes the given nick or mask\n" + "from the AutoKick list. It does not, however, remove any\n" + "bans placed by an AutoKick; those must be removed\n" + "manually.\n" + " \n" + "The \002AKICK LIST\002 command displays the AutoKick list, or\n" + "optionally only those AutoKick entries which match the\n" + "given mask.\n" + " \n" + "The \002AKICK VIEW\002 command is a more verbose version of\n" + "\002AKICK LIST\002 command.\n" + " \n" + "The \002AKICK ENFORCE\002 command causes %s to enforce the\n" + "current AKICK list by removing those users who match an\n" + "AKICK mask.\n" + " \n" + "The \002AKICK CLEAR\002 command clears all entries of the\n" + "akick list."), source.owner->nick.c_str()); + return true; + } +}; + +class CSAKick : public Module +{ + CommandCSAKick commandcsakick; + + public: + CSAKick(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandcsakick(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandcsakick); + } +}; + +MODULE_INIT(CSAKick) diff --git a/modules/commands/cs_appendtopic.cpp b/modules/commands/cs_appendtopic.cpp new file mode 100644 index 000000000..c9af50613 --- /dev/null +++ b/modules/commands/cs_appendtopic.cpp @@ -0,0 +1,117 @@ +/* cs_appendtopic.c - Add text to a channels topic + * + * (C) 2003-2011 Anope Team + * Contact us at team@anope.org + * + * Based on the original module by SGR <Alex_SGR@ntlworld.com> + * Included in the Anope module pack since Anope 1.7.9 + * Anope Coder: GeniusDex <geniusdex@anope.org> + * + * Please read COPYING and README for further details. + * + * Send bug reports to the Anope Coder instead of the module + * author, because any changes since the inclusion into anope + * are not supported by the original author. + */ + +/*************************************************************************/ + +#include "module.h" + +/* ------------------------------------------------------------ + * Name: cs_appendtopic + * Author: SGR <Alex_SGR@ntlworld.com> + * Date: 31/08/2003 + * ------------------------------------------------------------ + * + * This module has no configurable options. For information on + * this module, load it and refer to /ChanServ APPENDTOPIC HELP + * + * Thanks to dengel, Rob and Certus for all there support. + * Especially Rob, who always manages to show me where I have + * not allocated any memory. Even if it takes a few weeks of + * pestering to get him to look at it. + * + * ------------------------------------------------------------ + */ + +/* ---------------------------------------------------------------------- */ +/* DO NOT EDIT BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING */ +/* ---------------------------------------------------------------------- */ + +class CommandCSAppendTopic : public Command +{ + public: + CommandCSAppendTopic(Module *creator) : Command(creator, "chanserv/appendtopic", 2, 2) + { + this->SetDesc(_("Add text to a channels topic")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &newtopic = params[1]; + + User *u = source.u; + Channel *c = findchan(params[0]);; + + if (!c) + source.Reply(CHAN_X_NOT_IN_USE, params[0].c_str()); + else if (!c->ci) + source.Reply(CHAN_X_NOT_REGISTERED, c->name.c_str()); + else if (!c->ci->HasPriv(u, CA_TOPIC)) + source.Reply(ACCESS_DENIED); + else + { + Anope::string topic; + if (!c->ci->last_topic.empty()) + { + topic = c->ci->last_topic + " " + newtopic; + c->ci->last_topic.clear(); + } + else + topic = newtopic; + + bool has_topiclock = c->ci->HasFlag(CI_TOPICLOCK); + c->ci->UnsetFlag(CI_TOPICLOCK); + c->ChangeTopic(u->nick, topic, Anope::CurTime); + if (has_topiclock) + c->ci->SetFlag(CI_TOPICLOCK); + + bool override = c->ci->HasPriv(u, CA_TOPIC); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, c->ci) << "changed topic to " << topic; + } + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + source.Reply(_("Syntax: APPENDTOPIC channel text")); + source.Reply(" "); + source.Reply(("This command allows users to append text to a currently set\n" + "channel topic. When TOPICLOCK is on, the topic is updated and\n" + "the new, updated topic is locked.")); + + return true; + } + + void OnSyntaxError(CommandSource &source, const Anope::string &subcommand) + { + source.Reply(_("Syntax: APPENDTOPIC channel text")); + } +}; + +class CSAppendTopic : public Module +{ + CommandCSAppendTopic commandcsappendtopic; + + public: + CSAppendTopic(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, SUPPORTED), + commandcsappendtopic(this) + { + this->SetAuthor("SGR"); + + ModuleManager::RegisterService(&commandcsappendtopic); + } +}; + +MODULE_INIT(CSAppendTopic) diff --git a/modules/commands/cs_ban.cpp b/modules/commands/cs_ban.cpp new file mode 100644 index 000000000..759649662 --- /dev/null +++ b/modules/commands/cs_ban.cpp @@ -0,0 +1,109 @@ +/* ChanServ core 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 "module.h" + +class CommandCSBan : public Command +{ + public: + CommandCSBan(Module *creator) : Command(creator, "chanserv/ban", 2, 3) + { + this->SetDesc(_("Bans a selected nick on a channel")); + this->SetSyntax(_("\037#channel\037 \037nick\037 [\037reason\037]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &chan = params[0]; + const Anope::string &target = params[1]; + const Anope::string &reason = params.size() > 2 ? params[2] : "Requested"; + + ChannelInfo *ci = cs_findchan(params[0]); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + User *u = source.u; + Channel *c = ci->c; + bool is_same = target.equals_ci(u->nick); + User *u2 = is_same ? u : finduser(target); + + AccessGroup u_access = ci->AccessFor(u), u2_access = ci->AccessFor(u2); + + if (!c) + source.Reply(CHAN_X_NOT_IN_USE, chan.c_str()); + else if (!u2) + source.Reply(NICK_X_NOT_IN_USE, target.c_str()); + else if (!ci->HasPriv(u, CA_BAN)) + source.Reply(ACCESS_DENIED); + else if (!is_same && ci->HasFlag(CI_PEACE) && u2_access >= u_access) + source.Reply(ACCESS_DENIED); + /* + * Dont ban/kick the user on channels where he is excepted + * to prevent services <-> server wars. + */ + else if (matches_list(ci->c, u2, CMODE_EXCEPT)) + source.Reply(CHAN_EXCEPTED, u2->nick.c_str(), ci->name.c_str()); + else if (u2->IsProtected()) + source.Reply(ACCESS_DENIED); + else + { + Anope::string mask; + get_idealban(ci, u2, mask); + + // XXX need a way to detect if someone is overriding + Log(LOG_COMMAND, u, this, ci) << "for " << mask; + + c->SetMode(NULL, CMODE_BAN, mask); + + /* We still allow host banning while not allowing to kick */ + if (!c->FindUser(u2)) + return; + + if (ci->HasFlag(CI_SIGNKICK) || (ci->HasFlag(CI_SIGNKICK_LEVEL) && !ci->HasPriv(u, CA_SIGNKICK))) + c->Kick(ci->WhoSends(), u2, "%s (%s)", reason.c_str(), u->nick.c_str()); + else + c->Kick(ci->WhoSends(), u2, "%s", reason.c_str()); + } + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Bans a selected nick on a channel.\n" + " \n" + "By default, limited to AOPs or those with level 5 access \n" + "and above on the channel.")); + return true; + } +}; + +class CSBan : public Module +{ + CommandCSBan commandcsban; + + public: + CSBan(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), commandcsban(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandcsban); + } +}; + +MODULE_INIT(CSBan) diff --git a/modules/commands/cs_clearusers.cpp b/modules/commands/cs_clearusers.cpp new file mode 100644 index 000000000..18dc75479 --- /dev/null +++ b/modules/commands/cs_clearusers.cpp @@ -0,0 +1,83 @@ +/* ChanServ core 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 "module.h" + +class CommandCSClearUsers : public Command +{ + public: + CommandCSClearUsers(Module *creator) : Command(creator, "chanserv/clearusers", 1, 1) + { + this->SetDesc(_("Kicks all users on a channel")); + this->SetSyntax(_("\037channel\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &chan = params[0]; + + User *u = source.u; + Channel *c = findchan(chan); + Anope::string modebuf; + + if (!c) + source.Reply(CHAN_X_NOT_IN_USE, chan.c_str()); + else if (!c->ci) + source.Reply(CHAN_X_NOT_REGISTERED, c->name.c_str()); + else if (!c->ci->HasPriv(u, CA_FOUNDER)) + { + source.Reply(ACCESS_DENIED); + return; + } + + Anope::string buf = "CLEARUSERS command from " + u->nick + " (" + u->Account()->display + ")"; + + for (CUserList::iterator it = c->users.begin(), it_end = c->users.end(); it != it_end; ) + { + UserContainer *uc = *it++; + + c->Kick(NULL, uc->user, "%s", buf.c_str()); + } + + source.Reply(_("All users have been kicked from \2%s\2."), chan.c_str()); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Tells %s to clear (kick) all users certain settings on a channel." + " \n" + "By default, limited to those with founder access on the\n" + "channel."), source.owner->nick.c_str()); + return true; + } +}; + +class CSClearUsers : public Module +{ + CommandCSClearUsers commandcsclearusers; + + public: + CSClearUsers(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandcsclearusers(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandcsclearusers); + } +}; + +MODULE_INIT(CSClearUsers) diff --git a/modules/commands/cs_clone.cpp b/modules/commands/cs_clone.cpp new file mode 100644 index 000000000..5ede6a9e4 --- /dev/null +++ b/modules/commands/cs_clone.cpp @@ -0,0 +1,186 @@ +/* ChanServ core 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 "module.h" + +class CommandCSClone : public Command +{ +public: + CommandCSClone(Module *creator) : Command(creator, "chanserv/clone", 2, 3) + { + this->SetDesc(_("Copy all settings from one channel to another")); + this->SetSyntax(_("\037channel\037 \037target\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &channel = params[0]; + const Anope::string &target = params[1]; + Anope::string what = params.size() > 2 ? params[2] : ""; + + User *u = source.u; + ChannelInfo *ci = cs_findchan(params[0]); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + if (!ci->HasPriv(u, CA_SET)) + { + source.Reply(ACCESS_DENIED); + return; + } + ChannelInfo *target_ci = cs_findchan(target); + if (!target_ci) + { + source.Reply(CHAN_X_NOT_REGISTERED, target.c_str()); + return; + } + if (!IsFounder(u, ci) || !IsFounder(u, target_ci)) + { + source.Reply(ACCESS_DENIED); + return; + } + + if (Config->CSMaxReg && u->Account()->channelcount >= Config->CSMaxReg && !u->HasPriv("chanserv/no-register-limit")) + { + source.Reply(u->Account()->channelcount > Config->CSMaxReg ? CHAN_EXCEEDED_CHANNEL_LIMIT : _(CHAN_REACHED_CHANNEL_LIMIT), Config->CSMaxReg); + return; + } + + if (what.equals_ci("ALL")) + what.clear(); + + if (what.empty()) + { + delete target_ci; + target_ci = new ChannelInfo(*ci); + target_ci->name = target; + RegisteredChannelList[target_ci->name] = target_ci; + target_ci->c = findchan(target_ci->name); + if (target_ci->c) + { + target_ci->c->ci = target_ci; + + check_modes(target_ci->c); + + ChannelMode *cm; + if (u->FindChannel(target_ci->c) != NULL) + { + /* On most ircds you do not receive the admin/owner mode till its registered */ + if ((cm = ModeManager::FindChannelModeByName(CMODE_OWNER))) + target_ci->c->SetMode(NULL, cm, u->nick); + else if ((cm = ModeManager::FindChannelModeByName(CMODE_PROTECT))) + target_ci->c->RemoveMode(NULL, cm, u->nick); + } + + /* Mark the channel as persistant */ + if (target_ci->c->HasMode(CMODE_PERM)) + target_ci->SetFlag(CI_PERSIST); + /* Persist may be in def cflags, set it here */ + else if (target_ci->HasFlag(CI_PERSIST) && (cm = ModeManager::FindChannelModeByName(CMODE_PERM))) + target_ci->c->SetMode(NULL, CMODE_PERM); + + if (target_ci->bi && target_ci->c->FindUser(target_ci->bi) == NULL) + target_ci->bi->Join(target_ci->c); + } + + if (target_ci->c && !target_ci->c->topic.empty()) + { + target_ci->last_topic = target_ci->c->topic; + target_ci->last_topic_setter = target_ci->c->topic_setter; + target_ci->last_topic_time = target_ci->c->topic_time; + } + else + target_ci->last_topic_setter = source.owner->nick; + + FOREACH_MOD(I_OnChanRegistered, OnChanRegistered(target_ci)); + + source.Reply(_("All settings from \002%s\002 have been transferred to \002%s\002"), channel.c_str(), target.c_str()); + } + else if (what.equals_ci("ACCESS")) + { + /*target_ci->ClearAccess(); + for (unsigned i = 0; i < ci->GetAccessCount(); ++i) + { + ChanAccess *access = ci->GetAccess(i); + target_ci->AddAccess(access->GetMask(), access->level, access->creator, access->last_seen); + } + + source.Reply(_("All access entries from \002%s\002 have been transferred to \002%s\002"), channel.c_str(), target.c_str()); + XXX + */ + } + else if (what.equals_ci("AKICK")) + { + target_ci->ClearAkick(); + for (unsigned i = 0; i < ci->GetAkickCount(); ++i) + { + AutoKick *akick = ci->GetAkick(i); + if (akick->HasFlag(AK_ISNICK)) + target_ci->AddAkick(akick->creator, akick->nc, akick->reason, akick->addtime, akick->last_used); + else + target_ci->AddAkick(akick->creator, akick->mask, akick->reason, akick->addtime, akick->last_used); + } + + source.Reply(_("All akick entries from \002%s\002 have been transferred to \002%s\002"), channel.c_str(), target.c_str()); + } + else if (what.equals_ci("BADWORDS")) + { + target_ci->ClearBadWords(); + for (unsigned i = 0; i < ci->GetBadWordCount(); ++i) + { + BadWord *bw = ci->GetBadWord(i); + target_ci->AddBadWord(bw->word, bw->type); + } + + source.Reply(_("All badword entries from \002%s\002 have been transferred to \002%s\002"), channel.c_str(), target.c_str()); + } + else + { + this->OnSyntaxError(source, ""); + return; + } + + Log(LOG_COMMAND, u, this, ci) << "to clone it to " << target_ci->name; + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Copies all settings, access, akicks, etc from channel to the\n" + "target channel. If access, akick, or badwords is specified then only\n" + "the respective settings are transferred. You must have founder level\n" + "access to \037channel\037 and \037target\037.")); + return true; + } +}; + +class CSClone : public Module +{ + CommandCSClone commandcsclone; + + public: + CSClone(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), commandcsclone(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandcsclone); + } +}; + +MODULE_INIT(CSClone) diff --git a/modules/commands/cs_drop.cpp b/modules/commands/cs_drop.cpp new file mode 100644 index 000000000..eb01875db --- /dev/null +++ b/modules/commands/cs_drop.cpp @@ -0,0 +1,102 @@ +/* ChanServ core 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 "module.h" + +class CommandCSDrop : public Command +{ + public: + CommandCSDrop(Module *creator) : Command(creator, "chanserv/drop", 1, 1) + { + this->SetDesc(_("Cancel the registration of a channel")); + this->SetSyntax(_("\037channel\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &chan = params[0]; + + User *u = source.u; + + if (readonly) + { + source.Reply(_("Sorry, channel de-registration is temporarily disabled.")); // XXX: READ_ONLY_MODE? + return; + } + + ChannelInfo *ci = cs_findchan(params[0]); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + ci = cs_findchan(chan); + + if (ci->HasFlag(CI_SUSPENDED) && !u->HasCommand("chanserv/chanserv/drop")) + { + source.Reply(CHAN_X_SUSPENDED, chan.c_str()); + return; + } + + if ((ci->HasFlag(CI_SECUREFOUNDER) ? !IsFounder(u, ci) : !ci->HasPriv(u, CA_FOUNDER)) && !u->HasCommand("chanserv/chanserv/drop")) + { + source.Reply(ACCESS_DENIED); + return; + } + + if (ci->c && ModeManager::FindChannelModeByName(CMODE_REGISTERED)) + ci->c->RemoveMode(NULL, CMODE_REGISTERED, "", false); + + bool override = (ci->HasFlag(CI_SECUREFOUNDER) ? !IsFounder(u, ci) : !ci->HasPriv(u, CA_FOUNDER)); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "founder: " << (ci->GetFounder() ? ci->GetFounder()->display : "none"); + + delete ci; + + source.Reply(_("Channel \002%s\002 has been dropped."), chan.c_str()); + + FOREACH_MOD(I_OnChanDrop, OnChanDrop(chan)); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + User *u = source.u; + this->SendSyntax(source); + source.Reply(" "); + if (u->IsServicesOper()) + source.Reply(_("Unregisters the named channel. Only \002Services Operators\002\n" + "can drop a channel for which they have not identified.")); + else + source.Reply(_("Unregisters the named channel. Can only be used by\n" + "\002channel founder\002.")); + + return true; + } +}; + +class CSDrop : public Module +{ + CommandCSDrop commandcsdrop; + + public: + CSDrop(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), commandcsdrop(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandcsdrop); + } +}; + +MODULE_INIT(CSDrop) diff --git a/modules/commands/cs_enforce.cpp b/modules/commands/cs_enforce.cpp new file mode 100644 index 000000000..26885c9b0 --- /dev/null +++ b/modules/commands/cs_enforce.cpp @@ -0,0 +1,215 @@ +/* cs_enforce - Add a /cs ENFORCE command to enforce various set + * options and channelmodes on a channel. + * + * (C) 2003-2011 Anope Team + * Contact us at team@anope.org + * + * Included in the Anope module pack since Anope 1.7.9 + * Anope Coder: GeniusDex <geniusdex@anope.org> + * + * Please read COPYING and README for further details. + * + * Send any bug reports to the Anope Coder, as he will be able + * to deal with it best. + */ + +#include "module.h" + +class CommandCSEnforce : public Command +{ + private: + void DoSet(Channel *c) + { + ChannelInfo *ci; + + if (!(ci = c->ci)) + return; + + if (ci->HasFlag(CI_SECUREOPS)) + this->DoSecureOps(c); + if (ci->HasFlag(CI_RESTRICTED)) + this->DoRestricted(c); + } + + void DoModes(Channel *c) + { + if (c->HasMode(CMODE_REGISTEREDONLY)) + this->DoCModeR(c); + } + + void DoSecureOps(Channel *c) + { + ChannelInfo *ci; + bool hadsecureops = false; + + if (!(ci = c->ci)) + return; + + /* Dirty hack to allow chan_set_correct_modes to work ok. + * We pretend like SECUREOPS is on so it doesn't ignore that + * part of the code. This way we can enforce SECUREOPS even + * if it's off. + */ + if (!ci->HasFlag(CI_SECUREOPS)) + { + ci->SetFlag(CI_SECUREOPS); + hadsecureops = true; + } + + for (CUserList::iterator it = c->users.begin(), it_end = c->users.end(); it != it_end; ++it) + { + UserContainer *uc = *it; + + chan_set_correct_modes(uc->user, c, 0); + } + + if (hadsecureops) + ci->UnsetFlag(CI_SECUREOPS); + } + + void DoRestricted(Channel *c) + { + ChannelInfo *ci = c->ci; + if (ci == NULL) + return; + + for (CUserList::iterator it = c->users.begin(), it_end = c->users.end(); it != it_end; ) + { + UserContainer *uc = *it++; + User *user = uc->user; + + if (ci->AccessFor(user).empty()) + { + Anope::string mask; + get_idealban(ci, user, mask); + Anope::string reason = translate(user, CHAN_NOT_ALLOWED_TO_JOIN); + c->SetMode(NULL, CMODE_BAN, mask); + c->Kick(NULL, user, "%s", reason.c_str()); + } + } + } + + void DoCModeR(Channel *c) + { + ChannelInfo *ci; + Anope::string mask; + + if (!(ci = c->ci)) + return; + + for (CUserList::iterator it = c->users.begin(), it_end = c->users.end(); it != it_end; ) + { + UserContainer *uc = *it++; + + if (!uc->user->IsIdentified()) + { + get_idealban(ci, uc->user, mask); + Anope::string reason = translate(uc->user, CHAN_NOT_ALLOWED_TO_JOIN); + if (!c->HasMode(CMODE_REGISTEREDONLY)) + c->SetMode(NULL, CMODE_BAN, mask); + c->Kick(NULL, uc->user, "%s", reason.c_str()); + } + } + } + public: + CommandCSEnforce(Module *creator) : Command(creator, "chanserv/enforce", 1, 2) + { + this->SetDesc(_("Enforce various channel modes and set options")); + this->SetSyntax(_("\037channel\037 [\037what\037]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &what = params.size() > 1 ? params[1] : ""; + + User *u = source.u; + Channel *c = findchan(params[0]); + + if (!c) + source.Reply(CHAN_X_NOT_IN_USE, params[0].c_str()); + else if (!c->ci) + source.Reply(CHAN_X_NOT_REGISTERED, c->name.c_str()); + else if (!c->ci->HasPriv(u, CA_AKICK)) + source.Reply(ACCESS_DENIED); + else + { + if (what.empty() || what.equals_ci("SET")) + { + this->DoSet(c); + source.Reply(_("Enforced %s"), !what.empty() ? what.c_str() : "SET"); + } + else if (what.equals_ci("MODES")) + { + this->DoModes(c); + source.Reply(_("Enforced %s"), what.c_str()); + } + else if (what.equals_ci("SECUREOPS")) + { + this->DoSecureOps(c); + source.Reply(_("Enforced %s"), what.c_str()); + } + else if (what.equals_ci("RESTRICTED")) + { + this->DoRestricted(c); + source.Reply(_("Enforced %s"), what.c_str()); + } + else if (what.equals_ci("+R")) + { + this->DoCModeR(c); + source.Reply(_("Enforced %s"), what.c_str()); + } + else + this->OnSyntaxError(source, ""); + } + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Enforce various channel modes and set options. The \037channel\037\n" + "option indicates what channel to enforce the modes and options\n" + "on. The \037what\037 option indicates what modes and options to\n" + "enforce, and can be any of SET, SECUREOPS, RESTRICTED, MODES,\n" + "or +R. When left out, it defaults to SET.\n" + " \n" + "If \037what\037 is SET, it will enforce SECUREOPS and RESTRICTED\n" + "on the users currently in the channel, if they are set. Give\n" + "SECUREOPS to enforce the SECUREOPS option, even if it is not\n" + "enabled. Use RESTRICTED to enfore the RESTRICTED option, also\n" + "if it's not enabled.")); + source.Reply(" "); + if (ModeManager::FindChannelModeByName(CMODE_REGISTERED)) + source.Reply(_("If \037what\037 is MODES, it will enforce channelmode +R if it is\n" + "set. If +R is specified for \037what\037, the +R channelmode will\n" + "also be enforced, but even if it is not set. If it is not set,\n" + "users will be banned to ensure they don't just rejoin.")); + else + source.Reply(_("If \037what\037 is MODES, nothing will be enforced, since it would\n" + "enforce modes that the current ircd does not support. If +R is\n" + "specified for \037what\037, an equalivant of channelmode +R on\n" + "other ircds will be enforced. All users that are in the channel\n" + "but have not identified for their nickname will be kicked and\n" + "banned from the channel.")); + + return true; + } +}; + +class CSEnforce : public Module +{ + CommandCSEnforce commandcsenforce; + + public: + CSEnforce(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, SUPPORTED), + commandcsenforce(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandcsenforce); + } +}; + +MODULE_INIT(CSEnforce) diff --git a/modules/commands/cs_entrymsg.cpp b/modules/commands/cs_entrymsg.cpp new file mode 100644 index 000000000..3aa8ed862 --- /dev/null +++ b/modules/commands/cs_entrymsg.cpp @@ -0,0 +1,224 @@ +/* ChanServ core 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 "module.h" + +struct EntryMsg +{ + static unsigned MaxEntries; + + EntryMsg(const Anope::string &cname, const Anope::string &cmessage, time_t ct = Anope::CurTime) + { + this->creator = cname; + this->message = cmessage; + this->when = ct; + } + + Anope::string creator; + Anope::string message; + time_t when; +}; +unsigned EntryMsg::MaxEntries = 0; + +class CommandEntryMessage : public Command +{ + private: + void DoList(CommandSource &source, ChannelInfo *ci) + { + std::vector<EntryMsg> messages; + if (ci->GetExtRegular("cs_entrymsg", messages)) + { + source.Reply(_("Entry message list for \2%s\2:"), ci->name.c_str()); + for (unsigned i = 0; i < messages.size(); ++i) + source.Reply(CHAN_LIST_ENTRY, i + 1, messages[i].message.c_str(), messages[i].creator.c_str(), do_strftime(messages[i].when).c_str()); + source.Reply(_("End of entry message list.")); + } + else + source.Reply(_("Entry message list for \2%s\2 is empty."), ci->name.c_str()); + } + + void DoAdd(CommandSource &source, ChannelInfo *ci, const Anope::string &message) + { + std::vector<EntryMsg> messages; + ci->GetExtRegular("cs_entrymsg", messages); + + if (EntryMsg::MaxEntries && messages.size() >= EntryMsg::MaxEntries) + source.Reply(_("The entry message list for \2%s\2 is full."), ci->name.c_str()); + else + { + messages.push_back(EntryMsg(source.u->nick, message)); + ci->Extend("cs_entrymsg", new ExtensibleItemRegular<std::vector<EntryMsg> >(messages)); + source.Reply(_("Entry message added to \2%s\2"), ci->name.c_str()); + } + } + + void DoDel(CommandSource &source, ChannelInfo *ci, const Anope::string &message) + { + std::vector<EntryMsg> messages; + if (!message.is_pos_number_only()) + source.Reply(("Entry message \002%s\002 not found on channel \002%s\002."), message.c_str(), ci->name.c_str()); + else if (ci->GetExtRegular("cs_entrymsg", messages)) + { + try + { + unsigned i = convertTo<unsigned>(message); + if (i > 0 && i <= messages.size()) + { + messages.erase(messages.begin() + i - 1); + if (!messages.empty()) + ci->Extend("cs_entrymsg", new ExtensibleItemRegular<std::vector<EntryMsg> >(messages)); + else + ci->Shrink("cs_entrymsg"); + source.Reply(_("Entry message \2%i\2 for \2%s\2 deleted."), i, ci->name.c_str()); + } + else + throw ConvertException(); + } + catch (const ConvertException &) + { + source.Reply(_("Entry message \2%s\2 not found on channel \2%s\2."), message.c_str(), ci->name.c_str()); + } + } + else + source.Reply(_("Entry message list for \2%s\2 is empty."), ci->name.c_str()); + } + + void DoClear(CommandSource &source, ChannelInfo *ci) + { + ci->Shrink("cs_entrymsg"); + source.Reply(_("Entry messages for \2%s\2 have been cleared."), ci->name.c_str()); + } + + public: + CommandEntryMessage(Module *creator) : Command(creator, "chanserv/entrymsg", 2, 3) + { + this->SetDesc(_("Manage the channel's entry messages")); + this->SetSyntax(_("\037channel\037 {ADD|DEL|LIST|CLEAR} [\037message\037|\037num\037]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + + ChannelInfo *ci = cs_findchan(params[0]); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + if (IsFounder(u, ci) || u->HasCommand("chanserv/entrymsg")) + { + bool success = true; + if (params[1].equals_ci("LIST")) + this->DoList(source, ci); + else if (params[1].equals_ci("CLEAR")) + this->DoClear(source, ci); + else if (params.size() < 3) + { + success = false; + this->OnSyntaxError(source, ""); + } + else if (params[1].equals_ci("ADD")) + this->DoAdd(source, ci, params[2]); + else if (params[1].equals_ci("DEL")) + this->DoDel(source, ci, params[2]); + else + { + success = false; + this->OnSyntaxError(source, ""); + } + if (success) + Log(IsFounder(u, ci) ? LOG_COMMAND : LOG_OVERRIDE, u, this, ci) << params[1]; + } + else + source.Reply(ACCESS_DENIED); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Controls what messages will be sent to users when they join the channel.")); + return true; + } +}; + +class CSEntryMessage : public Module +{ + CommandEntryMessage commandentrymsg; + + public: + CSEntryMessage(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, SUPPORTED), commandentrymsg(this) + { + this->SetAuthor("Anope"); + + Implementation i[] = { I_OnJoinChannel, I_OnReload, I_OnDatabaseReadMetadata, I_OnDatabaseWriteMetadata }; + ModuleManager::Attach(i, this, sizeof(i) / sizeof(Implementation)); + + ModuleManager::RegisterService(&commandentrymsg); + + this->OnReload(); + } + + void OnJoinChannel(User *u, Channel *c) + { + if (u && c && c->ci && u->server->IsSynced()) + { + std::vector<EntryMsg> messages; + + if (c->ci->GetExtRegular("cs_entrymsg", messages)) + for (unsigned i = 0; i < messages.size(); ++i) + u->SendMessage(c->ci->WhoSends(), "[%s] %s", c->ci->name.c_str(), messages[i].message.c_str()); + } + } + + void OnReload() + { + ConfigReader config; + EntryMsg::MaxEntries = config.ReadInteger("cs_entrymsg", "maxentries", "5", 0, true); + } + + EventReturn OnDatabaseReadMetadata(ChannelInfo *ci, const Anope::string &key, const std::vector<Anope::string> ¶ms) + { + if (key.find("CS_ENTRYMSG_") == 0 && params.size() > 2) + { + Anope::string creator = params[0]; + time_t t = params[1].is_pos_number_only() ? convertTo<time_t>(params[1]) : Anope::CurTime; + Anope::string message = params[2]; + for (unsigned j = 3; j < params.size(); ++j) + message += " " + params[j]; + + std::vector<EntryMsg> messages; + ci->GetExtRegular("cs_entrymsg", messages); + messages.push_back(EntryMsg(creator, message, t)); + ci->Extend("cs_entrymsg", new ExtensibleItemRegular<std::vector<EntryMsg> >(messages)); + + return EVENT_STOP; + } + + return EVENT_CONTINUE; + } + + void OnDatabaseWriteMetadata(void (*WriteMetadata)(const Anope::string &, const Anope::string &), ChannelInfo *ci) + { + std::vector<EntryMsg> messages; + if (ci->GetExtRegular("cs_entrymsg", messages)) + for (unsigned i = 0; i < messages.size(); ++i) + WriteMetadata("CS_ENTRYMSG_" + stringify(i), messages[i].creator + " " + stringify(messages[i].when) + " " + messages[i].message); + } +}; + +MODULE_INIT(CSEntryMessage) diff --git a/modules/commands/cs_flags.cpp b/modules/commands/cs_flags.cpp new file mode 100644 index 000000000..48f1a22f1 --- /dev/null +++ b/modules/commands/cs_flags.cpp @@ -0,0 +1,455 @@ +/* ChanServ core 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 "module.h" + +static struct FlagLevels +{ + ChannelAccess priv; + char default_char; + Anope::string config_name; + Anope::string desc; +} flagLevels[] = { + { CA_ACCESS_CHANGE, 'f', "flag_change", _("Allowed to modify the access list") }, + { CA_ACCESS_LIST, 'l', "flag_list", _("Allowed to view the access list") }, + { CA_AKICK, 'K', "flag_akick", _("Allowed to use AKICK command") }, + { CA_ASSIGN, 's', "flag_assign", _("Allowed to assign/unassign a bot") }, + { CA_AUTOHALFOP, 'H', "flag_autohalfop", _("Automatic mode +h") }, + { CA_AUTOOP, 'O', "flag_autoop", _("Automatic channel operator status") }, + { CA_AUTOOWNER, 'Q', "flag_autoowner", _("Automatic mode +q") }, + { CA_AUTOPROTECT, 'A', "flag_autoprotect", _("Automatic mode +a") }, + { CA_AUTOVOICE, 'V', "flag_autovoice", _("Automatic mode +v") }, + { CA_BADWORDS, 'k', "flag_badwords", _("Allowed to modify channel badwords list") }, + { CA_BAN, 'b', "flag_ban", _("Allowed to use ban users") }, + { CA_FANTASIA, 'c', "flag_fantasia", _("Allowed to use fantasy commands") }, + { CA_FOUNDER, 'F', "flag_founder", _("Allowed to issue commands restricted to channel founders") }, + { CA_GETKEY, 'G', "flag_getkey", _("Allowed to use GETKEY command") }, + { CA_GREET, 'g', "flag_greet", _("Greet message displayed") }, + { CA_HALFOP, 'h', "flag_halfop", _("Allowed to (de)halfop users") }, + { CA_HALFOPME, 'h', "flag_halfopme", _("Allowed to (de)halfop him/herself") }, + { CA_INFO, 'I', "flag_info", _("Allowed to use INFO command with ALL option") }, + { CA_INVITE, 'i', "flag_invite", _("Allowed to use the INVITE command") }, + { CA_KICK, 'k', "flag_kick", _("Allowed to use the KICK command") }, + { CA_MEMO, 'm', "flag_memo", _("Allowed to read channel memos") }, + { CA_MODE, 's', "flag_mode", _("Allowed to change channel modes") }, + { CA_NOKICK, 'N', "flag_nokick", _("Prevents users being kicked by Services") }, + { CA_OPDEOP, 'o', "flag_opdeop", _("Allowed to (de)op users") }, + { CA_OPDEOPME, 'o', "flag_opdeopme", _("Allowed to (de)op him/herself") }, + { CA_OWNER, 'q', "flag_owner", _("Allowed to use (de)owner users") }, + { CA_OWNERME, 'q', "flag_ownerme", _("Allowed to (de)owner him/herself") }, + { CA_PROTECT, 'a', "flag_protect", _("Allowed to (de)protect users") }, + { CA_PROTECTME, 'a', "flag_protectme", _("Allowed to (de)protect him/herself"), }, + { CA_SAY, 'B', "flag_say", _("Allowed to use SAY and ACT commands") }, + { CA_SET, 's', "flag_set", _("Allowed to set channel settings") }, + { CA_SIGNKICK, 'K', "flag_signkick", _("Prevents kicks from being signed") }, + { CA_TOPIC, 't', "flag_topic", _("Allowed to change channel topics") }, + { CA_UNBAN, 'u', "flag_unban", _("Allowed to unban users") }, + { CA_VOICE, 'v', "flag_voice", _("Allowed to (de)voice users") }, + { CA_VOICEME, 'v', "flag_voiceme", _("Allowed to (de)voice him/herself") }, + { CA_SIZE, -1, "", "" } +}; + +class FlagsChanAccess : public ChanAccess +{ + public: + std::set<char> flags; + + FlagsChanAccess(AccessProvider *p) : ChanAccess(p) + { + } + + bool Matches(User *u, NickCore *nc) + { + if (u && (Anope::Match(u->nick, this->mask) || Anope::Match(u->GetMask(), this->mask))) + return true; + else if (nc && Anope::Match(nc->display, this->mask)) + return true; + return false; + } + + bool HasPriv(ChannelAccess priv) + { + for (int i = 0; flagLevels[i].priv != CA_SIZE; ++i) + if (flagLevels[i].priv == priv) + return this->flags.count(flagLevels[i].default_char); + + return false; + } + + Anope::string Serialize() + { + return Anope::string(this->flags.begin(), this->flags.end()); + } + + void Unserialize(const Anope::string &data) + { + for (unsigned i = data.length(); i > 0; --i) + this->flags.insert(data[i - 1]); + } + + static Anope::string DetermineFlags(ChanAccess *access) + { + if (access->provider->name == "access/flags") + return access->Serialize(); + + std::set<char> buffer; + + for (int i = 0; flagLevels[i].priv != CA_SIZE; ++i) + { + FlagLevels &l = flagLevels[i]; + + if (access->HasPriv(l.priv)) + buffer.insert(l.default_char); + } + + return Anope::string(buffer.begin(), buffer.end()); + } +}; + +class FlagsAccessProvider : public AccessProvider +{ + public: + FlagsAccessProvider(Module *o) : AccessProvider(o, "access/flags") + { + } + + ChanAccess *Create() + { + return new FlagsChanAccess(this); + } +}; + +class CommandCSFlags : public Command +{ + void DoModify(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + + Anope::string mask = params.size() > 2 ? params[2] : ""; + Anope::string flags = params.size() > 3 ? params[3] : ""; + + if (flags.empty()) + { + this->OnSyntaxError(source, ""); + return; + } + + AccessGroup u_access = ci->AccessFor(u); + + if (mask.find_first_of("!*@") == Anope::string::npos && findnick(mask) == NULL) + mask += "!*@*"; + + ChanAccess *current = NULL; + std::set<char> current_flags; + for (unsigned i = ci->GetAccessCount(); i > 0; --i) + { + ChanAccess *access = ci->GetAccess(i - 1); + if (mask.equals_ci(access->mask)) + { + current = access; + Anope::string cur_flags = FlagsChanAccess::DetermineFlags(access); + for (unsigned j = cur_flags.length(); j > 0; --j) + current_flags.insert(cur_flags[j - 1]); + break; + } + } + + if (ci->GetAccessCount() >= Config->CSAccessMax) + { + source.Reply(_("Sorry, you can only have %d access entries on a channel."), Config->CSAccessMax); + return; + } + + bool override = false; + int add = -1; + for (size_t i = 0; i < flags.length(); ++i) + { + char f = flags[i]; + switch (f) + { + case '+': + add = 1; + break; + case '-': + add = 0; + break; + case '*': + if (add == -1) + break; + for (int j = 0; flagLevels[j].priv != CA_SIZE; ++j) + { + FlagLevels &l = flagLevels[j]; + if (!u_access.HasPriv(l.priv)) + { + if (u->HasPriv("chanserv/access/modify")) + override = true; + else + continue; + } + if (add == 1) + current_flags.insert(f); + else if (add == 0) + current_flags.erase(f); + } + break; + default: + if (add == -1) + break; + for (int j = 0; flagLevels[j].priv != CA_SIZE; ++j) + { + FlagLevels &l = flagLevels[j]; + if (f != l.default_char) + continue; + else if (!u_access.HasPriv(l.priv)) + { + if (u->HasPriv("chanserv/access/modify")) + override = true; + else + { + source.Reply(_("You can not set the \002%c\002 flag."), f); + continue; + } + } + if (add == 1) + current_flags.insert(f); + else if (add == 0) + current_flags.erase(f); + break; + } + } + } + if (current_flags.empty()) + { + if (current != NULL) + { + FOREACH_MOD(I_OnAccessDel, OnAccessDel(ci, u, current)); + ci->EraseAccess(current); + source.Reply(_("\002%s\002 removed from the %s access list."), mask.c_str(), ci->name.c_str()); + } + else + { + source.Reply(_("Insufficient flags given")); + } + return; + } + + service_reference<AccessProvider> provider("access/flags"); + if (!provider) + return; + FlagsChanAccess *access = debug_cast<FlagsChanAccess *>(provider->Create()); + access->ci = ci; + access->mask = mask; + access->creator = u->nick; + access->last_seen = current ? current->last_seen : 0; + access->created = Anope::CurTime; + access->flags = current_flags; + + if (current != NULL) + ci->EraseAccess(current); + + ci->AddAccess(access); + + FOREACH_MOD(I_OnAccessAdd, OnAccessAdd(ci, u, access)); + + Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "MODIFY " << mask << " with flags " << access->Serialize(); + source.Reply(_("Access for \002%s\002 on %s set to +\002%s\002"), access->mask.c_str(), ci->name.c_str(), access->Serialize().c_str()); + + return; + } + + void DoList(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> ¶ms) + { + const Anope::string &arg = params.size() > 2 ? params[2] : ""; + + if (!ci->GetAccessCount()) + source.Reply(_("%s access list is empty."), ci->name.c_str()); + else + { + unsigned total = 0; + + for (unsigned i = 0, end = ci->GetAccessCount(); i < end; ++i) + { + ChanAccess *access = ci->GetAccess(i); + const Anope::string &flags = FlagsChanAccess::DetermineFlags(access); + + if (!arg.empty()) + { + if (arg[0] == '+') + { + bool pass = true; + for (size_t j = 1; j < arg.length(); ++j) + if (flags.find(arg[j]) == Anope::string::npos) + pass = false; + if (pass == false) + continue; + } + else if (!Anope::Match(access->mask, arg)) + continue; + } + + if (++total == 1) + { + source.Reply(_("Flags list for %s"), ci->name.c_str()); + } + + source.Reply(_(" %3d %-10s +%-10s [last modified on %s by %s]"), i + 1, access->mask.c_str(), FlagsChanAccess::DetermineFlags(access).c_str(), do_strftime(access->created, source.u->Account(), true).c_str(), access->creator.c_str()); + } + + if (total == 0) + source.Reply(_("No matching entries on %s access list."), ci->name.c_str()); + else if (total == ci->GetAccessCount()) + source.Reply(_("End of access list.")); + else + source.Reply(_("End of access list - %d/%d entries shown."), total, ci->GetAccessCount()); + } + } + + void DoClear(CommandSource &source, ChannelInfo *ci) + { + User *u = source.u; + + if (!IsFounder(u, ci) && !u->HasPriv("chanserv/access/modify")) + source.Reply(ACCESS_DENIED); + else + { + ci->ClearAccess(); + + FOREACH_MOD(I_OnAccessClear, OnAccessClear(ci, u)); + + source.Reply(_("Channel %s access list has been cleared."), ci->name.c_str()); + + bool override = !IsFounder(u, ci); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "CLEAR"; + } + + return; + } + + public: + CommandCSFlags(Module *creator) : Command(creator, "chanserv/flags", 2, 4) + { + this->SetDesc(_("Modify the list of privileged users")); + this->SetSyntax(_("\037channel\037 MODIFY \037mask\037 \037changes\037")); + this->SetSyntax(_("\037channel\037 LIST [\037mask\037 | +\037flags\037]")); + this->SetSyntax(_("\037channel\037 CLEAR\002")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &chan = params[0]; + const Anope::string &cmd = params[1]; + + User *u = source.u; + ChannelInfo *ci = cs_findchan(chan); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, chan.c_str()); + return; + } + + bool is_list = cmd.equals_ci("LIST"); + bool has_access = false; + if (u->HasPriv("chanserv/access/modify")) + has_access = true; + else if (is_list && ci->HasPriv(u, CA_ACCESS_LIST)) + has_access = true; + else if (ci->HasPriv(u, CA_ACCESS_CHANGE)) + has_access = true; + + if (!has_access) + source.Reply(ACCESS_DENIED); + else if (readonly && !is_list) + source.Reply(_("Sorry, channel access list modification is temporarily disabled.")); + else if (cmd.equals_ci("MODIFY")) + this->DoModify(source, ci, params); + else if (cmd.equals_ci("LIST")) + this->DoList(source, ci, params); + else if (cmd.equals_ci("CLEAR")) + this->DoClear(source, ci); + else + this->OnSyntaxError(source, cmd); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("%s is another way to modify the channel access list, similar to\n" + "the XOP and ACCESS methods."), source.command.c_str()); + source.Reply(" "); + source.Reply(_("The MODIFY command allows you to modify the access list. If mask is\n" + "not already on the access list is it added, then the changes are applied.\n" + "If the mask has no more flags, then the mask is removed from the access list.\n" + "Additionally, you may use +* or -* to add or remove all flags, respectively. You are\n" + "only able to modify the access list if you have the proper permission on the channel,\n" + "and even then you can only give other people access to up what you already have.")); + source.Reply(" "); + source.Reply(_("The LIST command allows you to list existing entries on the channel access list.\n" + "If a mask is given, the mask is wildcard matched against all existing entries on the\n" + "access list, and only those entries are returned. If a set of flags is given, only those\n" + "on the access list with the specified flags are returned.")); + source.Reply(" "); + source.Reply(_("The CLEAR command clears the channel access list, which requires channel founder.")); + source.Reply(" "); + source.Reply(_("The available flags are:")); + + std::multimap<char, FlagLevels *, std::less<ci::string> > levels; + for (int i = 0; flagLevels[i].priv != CA_SIZE; ++i) + levels.insert(std::make_pair(flagLevels[i].default_char, &flagLevels[i])); + for (std::multimap<char, FlagLevels *, std::less<ci::string> >::iterator it = levels.begin(), it_end = levels.end(); it != it_end; ++it) + { + FlagLevels *l = it->second; + source.Reply(" %c - %s", l->default_char, translate(source.u->Account(), l->desc.c_str())); + } + + return true; + } +}; + +class CSFlags : public Module +{ + FlagsAccessProvider accessprovider; + CommandCSFlags commandcsflags; + + public: + CSFlags(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + accessprovider(this), commandcsflags(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&accessprovider); + ModuleManager::RegisterService(&commandcsflags); + + Implementation i[] = { I_OnReload }; + ModuleManager::Attach(i, this, 1); + + this->OnReload(); + } + + void OnReload() + { + ConfigReader config; + + for (int i = 0; flagLevels[i].priv != CA_SIZE; ++i) + { + FlagLevels &l = flagLevels[i]; + + const Anope::string &value = config.ReadValue("chanserv", l.config_name, "", 0); + if (value.empty()) + continue; + l.default_char = value[0]; + } + } +}; + +MODULE_INIT(CSFlags) diff --git a/modules/commands/cs_getkey.cpp b/modules/commands/cs_getkey.cpp new file mode 100644 index 000000000..4a6d62442 --- /dev/null +++ b/modules/commands/cs_getkey.cpp @@ -0,0 +1,80 @@ +/* ChanServ core 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 "module.h" + +class CommandCSGetKey : public Command +{ + public: + CommandCSGetKey(Module *creator) : Command(creator, "chanserv/getkey", 1, 1) + { + this->SetDesc(_("Returns the key of the given channel")); + this->SetSyntax(_("\037channel\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &chan = params[0]; + + User *u = source.u; + ChannelInfo *ci = cs_findchan(params[0]); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + + if (!ci->HasPriv(u, CA_GETKEY) && !u->HasCommand("chanserv/chanserv/getkey")) + { + source.Reply(ACCESS_DENIED); + return; + } + + Anope::string key; + if (!ci->c || !ci->c->GetParam(CMODE_KEY, key)) + { + source.Reply(_("The channel \002%s\002 has no key."), chan.c_str()); + return; + } + + bool override = !ci->HasPriv(u, CA_GETKEY); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci); + + source.Reply(_("Key for channel \002%s\002 is \002%s\002."), chan.c_str(), key.c_str()); + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Returns the key of the given channel.")); + return true; + } +}; + +class CSGetKey : public Module +{ + CommandCSGetKey commandcsgetkey; + + public: + CSGetKey(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), commandcsgetkey(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandcsgetkey); + } +}; + +MODULE_INIT(CSGetKey) diff --git a/modules/commands/cs_info.cpp b/modules/commands/cs_info.cpp new file mode 100644 index 000000000..ef026563e --- /dev/null +++ b/modules/commands/cs_info.cpp @@ -0,0 +1,142 @@ +/* ChanServ core 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 "module.h" + +class CommandCSInfo : public Command +{ + void CheckOptStr(Anope::string &buf, ChannelInfoFlag opt, const Anope::string &str, ChannelInfo *ci, const NickCore *nc) + { + if (ci->HasFlag(opt)) + { + if (!buf.empty()) + buf += ", "; + + buf += str; + } + } + + public: + CommandCSInfo(Module *creator) : Command(creator, "chanserv/info", 1, 2) + { + this->SetFlag(CFLAG_ALLOW_UNREGISTERED); + this->SetDesc(_("Lists information about the named registered channel")); + this->SetSyntax(_("\037channel\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &chan = params[0]; + + User *u = source.u; + ChannelInfo *ci = cs_findchan(params[0]); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + bool has_auspex = u->IsIdentified() && u->HasPriv("chanserv/auspex"); + bool show_all = false; + + /* Should we show all fields? Only for sadmins and identified users */ + if (has_auspex || ci->HasPriv(u, CA_INFO)) + show_all = true; + + source.Reply(CHAN_INFO_HEADER, chan.c_str()); + if (ci->GetFounder()) + source.Reply(_(" Founder: %s"), ci->GetFounder()->display.c_str()); + + if (show_all && ci->successor) + source.Reply(_(" Successor: %s"), ci->successor->display.c_str()); + + if (!ci->desc.empty()) + source.Reply(_(" Description: %s"), ci->desc.c_str()); + source.Reply(_(" Registered: %s"), do_strftime(ci->time_registered).c_str()); + source.Reply(_(" Last used: %s"), do_strftime(ci->last_used).c_str()); + + ModeLock *secret = ci->GetMLock(CMODE_SECRET); + if (!ci->last_topic.empty() && (show_all || ((!secret || secret->set == false) && (!ci->c || !ci->c->HasMode(CMODE_SECRET))))) + { + source.Reply(_(" Last topic: %s"), ci->last_topic.c_str()); + source.Reply(_(" Topic set by: %s"), ci->last_topic_setter.c_str()); + } + + if (show_all) + { + source.Reply(_(" Ban type: %d"), ci->bantype); + Anope::string optbuf; + + CheckOptStr(optbuf, CI_KEEPTOPIC, _("Topic Retention"), ci, u->Account()); + CheckOptStr(optbuf, CI_OPNOTICE, _("OP Notice"), ci, u->Account()); + CheckOptStr(optbuf, CI_PEACE, _("Peace"), ci, u->Account()); + CheckOptStr(optbuf, CI_PRIVATE, _("Private"), ci, u->Account()); + CheckOptStr(optbuf, CI_RESTRICTED, _("Restricted Access"), ci, u->Account()); + CheckOptStr(optbuf, CI_SECURE, _("Secure"), ci, u->Account()); + CheckOptStr(optbuf, CI_SECUREFOUNDER, _("Secure Founder"), ci, u->Account()); + CheckOptStr(optbuf, CI_SECUREOPS, _("Secure Ops"), ci, u->Account()); + if (ci->HasFlag(CI_SIGNKICK)) + CheckOptStr(optbuf, CI_SIGNKICK, _("Signed kicks"), ci, u->Account()); + else + CheckOptStr(optbuf, CI_SIGNKICK_LEVEL, _("Signed kicks"), ci, u->Account()); + CheckOptStr(optbuf, CI_TOPICLOCK, _("Topic Lock"), ci, u->Account()); + CheckOptStr(optbuf, CI_PERSIST, _("Persistant"), ci, u->Account()); + CheckOptStr(optbuf, CI_NO_EXPIRE, _("No expire"), ci, u->Account()); + + source.Reply(NICK_INFO_OPTIONS, optbuf.empty() ? _("None") : optbuf.c_str()); + source.Reply(_(" Mode lock: %s"), ci->GetMLockAsString(true).c_str()); + + if (!ci->HasFlag(CI_NO_EXPIRE)) + source.Reply(_(" Expires on: %s"), do_strftime(ci->last_used + Config->CSExpire).c_str()); + } + if (ci->HasFlag(CI_SUSPENDED)) + { + Anope::string by, reason; + ci->GetExtRegular("suspend_by", by); + ci->GetExtRegular("suspend_reason", reason); + source.Reply(_(" Suspended: [%s] %s"), by.c_str(), !reason.empty() ? reason.c_str() : NO_REASON); + } + + FOREACH_MOD(I_OnChanInfo, OnChanInfo(source, ci, show_all)); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Lists information about the named registered channel,\n" + "including its founder, time of registration, last time\n" + "used, description, and mode lock, if any. If \002ALL\002 is \n" + "specified, the entry message and successor will also \n" + "be displayed.")); + return true; + } +}; + +class CSInfo : public Module +{ + CommandCSInfo commandcsinfo; + + public: + CSInfo(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandcsinfo(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandcsinfo); + } +}; + +MODULE_INIT(CSInfo) diff --git a/modules/commands/cs_invite.cpp b/modules/commands/cs_invite.cpp new file mode 100644 index 000000000..27b4b03a3 --- /dev/null +++ b/modules/commands/cs_invite.cpp @@ -0,0 +1,102 @@ +/* ChanServ core 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 "module.h" + +class CommandCSInvite : public Command +{ + public: + CommandCSInvite(Module *creator) : Command(creator, "chanserv/invite", 1, 3) + { + this->SetDesc(_("Invites you into a channel")); + this->SetSyntax(_("\037channel\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &chan = params[0]; + + User *u = source.u; + Channel *c = findchan(chan); + + if (!c) + { + source.Reply(CHAN_X_NOT_IN_USE, chan.c_str()); + return; + } + + ChannelInfo *ci = c->ci; + if (!ci) + { + source.Reply(CHAN_X_NOT_REGISTERED, chan.c_str()); + return; + } + + if (!ci->HasPriv(u, CA_INVITE)) + { + source.Reply(ACCESS_DENIED); + return; + } + + User *u2; + if (params.size() == 1) + u2 = u; + else + { + if (!(u2 = finduser(params[1]))) + { + source.Reply(NICK_X_NOT_IN_USE, params[1].c_str()); + return; + } + } + + // XXX need a check for override... + Log(LOG_COMMAND, u, this, ci) << "for " << u2->nick; + + if (c->FindUser(u2)) + source.Reply(_("You are already in \002%s\002! "), c->name.c_str()); + else + { + ircdproto->SendInvite(ci->WhoSends(), chan, u2->nick); + source.Reply(_("\002%s\002 has been invited to \002%s\002."), u2->nick.c_str(), c->name.c_str()); + u2->SendMessage(ci->WhoSends(), _("You have been invited to \002%s\002."), c->name.c_str()); + } + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Tells %s to invite you into the given channel.\n" + " \n" + "By default, limited to AOPs or those with level 5 and above\n" + "on the channel."), source.owner->nick.c_str()); + return true; + } +}; + +class CSInvite : public Module +{ + CommandCSInvite commandcsinvite; + + public: + CSInvite(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), commandcsinvite(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandcsinvite); + } +}; + +MODULE_INIT(CSInvite) diff --git a/modules/commands/cs_kick.cpp b/modules/commands/cs_kick.cpp new file mode 100644 index 000000000..b98d0953d --- /dev/null +++ b/modules/commands/cs_kick.cpp @@ -0,0 +1,91 @@ +/* ChanServ core 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 "module.h" + +class CommandCSKick : public Command +{ + public: + CommandCSKick(Module *creator) : Command(creator, "chanserv/kick", 2, 3) + { + this->SetDesc(_("Kicks a selected nick from a channel")); + this->SetSyntax(_("\037channel\037 \037nick\037 [\037reason\037]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &chan = params[0]; + const Anope::string &target = params[1]; + const Anope::string &reason = params.size() > 2 ? params[2] : "Requested"; + + User *u = source.u; + ChannelInfo *ci = cs_findchan(params[0]); + Channel *c = findchan(params[0]); + bool is_same = target.equals_ci(u->nick); + User *u2 = is_same ? u : finduser(target); + + AccessGroup u_access = ci->AccessFor(u), u2_access = ci->AccessFor(u2); + + if (!c) + source.Reply(CHAN_X_NOT_IN_USE, chan.c_str()); + else if (!ci) + source.Reply(CHAN_X_NOT_REGISTERED, chan.c_str()); + else if (!u2) + source.Reply(NICK_X_NOT_IN_USE, target.c_str()); + else if (!ci->HasPriv(u, CA_KICK)) + source.Reply(ACCESS_DENIED); + else if (!is_same && (ci->HasFlag(CI_PEACE)) && u2_access >= u_access) + source.Reply(ACCESS_DENIED); + else if (u2->IsProtected()) + source.Reply(ACCESS_DENIED); + else if (!c->FindUser(u2)) + source.Reply(NICK_X_NOT_ON_CHAN, u2->nick.c_str(), c->name.c_str()); + else + { + // XXX + Log(LOG_COMMAND, u, this, ci) << "for " << u2->nick; + + if (ci->HasFlag(CI_SIGNKICK) || (ci->HasFlag(CI_SIGNKICK_LEVEL) && !ci->HasPriv(u, CA_SIGNKICK))) + ci->c->Kick(ci->WhoSends(), u2, "%s (%s)", reason.c_str(), u->nick.c_str()); + else + ci->c->Kick(ci->WhoSends(), u2, "%s", reason.c_str()); + } + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Kicks a selected nick on a channel.\n" + " \n" + "By default, limited to AOPs or those with level 5 access \n" + "and above on the channel.")); + return true; + } +}; + +class CSKick : public Module +{ + CommandCSKick commandcskick; + + public: + CSKick(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), commandcskick(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandcskick); + } +}; + +MODULE_INIT(CSKick) diff --git a/modules/commands/cs_list.cpp b/modules/commands/cs_list.cpp new file mode 100644 index 000000000..a79c047c4 --- /dev/null +++ b/modules/commands/cs_list.cpp @@ -0,0 +1,136 @@ +/* ChanServ core 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 "module.h" + +class CommandCSList : public Command +{ + public: + CommandCSList(Module *creator) : Command(creator, "chanserv/list", 1, 2) + { + this->SetFlag(CFLAG_STRIP_CHANNEL); + this->SetDesc(_("Lists all registered channels matching the given pattern")); + this->SetSyntax(_("\037pattern\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + + Anope::string pattern = params[0]; + unsigned nchans; + bool is_servadmin = u->HasCommand("chanserv/chanserv/list"); + int count = 0, from = 0, to = 0; + bool suspended = false, channoexpire = false; + + if (pattern[0] == '#') + { + Anope::string n1 = myStrGetToken(pattern.substr(1), '-', 0), /* Read FROM out */ + n2 = myStrGetTokenRemainder(pattern, '-', 1); + + try + { + from = convertTo<int>(n1); + to = convertTo<int>(n2); + } + catch (const ConvertException &) + { + source.Reply(LIST_INCORRECT_RANGE); + source.Reply(_("To search for channels starting with #, search for the channel\n" + "name without the #-sign prepended (\002anope\002 instead of \002#anope\002).")); + return; + } + + pattern = "*"; + } + + nchans = 0; + + if (is_servadmin && params.size() > 1) + { + Anope::string keyword; + spacesepstream keywords(params[1]); + while (keywords.GetToken(keyword)) + { + if (keyword.equals_ci("SUSPENDED")) + suspended = true; + if (keyword.equals_ci("NOEXPIRE")) + channoexpire = true; + } + } + + Anope::string spattern = "#" + pattern; + + source.Reply(LIST_HEADER, pattern.c_str()); + + for (registered_channel_map::const_iterator it = RegisteredChannelList.begin(), it_end = RegisteredChannelList.end(); it != it_end; ++it) + { + ChannelInfo *ci = it->second; + + if (!is_servadmin && (ci->HasFlag(CI_PRIVATE) || ci->HasFlag(CI_SUSPENDED))) + continue; + else if (suspended && !ci->HasFlag(CI_SUSPENDED)) + continue; + else if (channoexpire && !ci->HasFlag(CI_NO_EXPIRE)) + continue; + + if (pattern.equals_ci(ci->name) || ci->name.equals_ci(spattern) || Anope::Match(ci->name, pattern) || Anope::Match(ci->name, spattern)) + { + if (((count + 1 >= from && count + 1 <= to) || (!from && !to)) && ++nchans <= Config->CSListMax) + { + char noexpire_char = ' '; + if (is_servadmin && (ci->HasFlag(CI_NO_EXPIRE))) + noexpire_char = '!'; + + Anope::string buf; + if (ci->HasFlag(CI_SUSPENDED)) + buf = Anope::printf("%-20s [Suspended]", ci->name.c_str()); + else + buf = Anope::printf("%-20s %s", ci->name.c_str(), !ci->desc.empty() ? ci->desc.c_str() : ""); + + source.Reply(" %c%s", noexpire_char, buf.c_str()); + } + ++count; + } + } + + source.Reply(_("End of list - %d/%d matches shown."), nchans > Config->CSListMax ? Config->CSListMax : nchans, nchans); + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Lists all registered channels matching the given pattern.\n" + "(Channels with the \002PRIVATE\002 option set are not listed.)\n" + "Note that a preceding '#' specifies a range, channel names\n" + "are to be written without '#'.")); + return true; + } +}; + +class CSList : public Module +{ + CommandCSList commandcslist; + + public: + CSList(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), commandcslist(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandcslist); + } +}; + +MODULE_INIT(CSList) diff --git a/modules/commands/cs_mode.cpp b/modules/commands/cs_mode.cpp new file mode 100644 index 000000000..e67abd85a --- /dev/null +++ b/modules/commands/cs_mode.cpp @@ -0,0 +1,364 @@ +/* ChanServ core 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 "module.h" + +class CommandCSMode : public Command +{ + bool CanSet(User *u, ChannelInfo *ci, ChannelModeName mode) + { + switch (mode) + { + case CMODE_OWNER: + return ci->HasPriv(u, CA_OWNER); + case CMODE_PROTECT: + return ci->HasPriv(u, CA_PROTECT); + case CMODE_OP: + return ci->HasPriv(u, CA_OPDEOP); + case CMODE_HALFOP: + return ci->HasPriv(u, CA_HALFOP); + case CMODE_VOICE: + return ci->HasPriv(u, CA_VOICE); + default: + break; + } + + return false; + } + + void DoLock(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + const Anope::string &subcommand = params[2]; + const Anope::string ¶m = params.size() > 3 ? params[3] : ""; + + if (subcommand.equals_ci("ADD") && !param.empty()) + { + spacesepstream sep(param); + Anope::string modes; + + sep.GetToken(modes); + + int adding = -1; + for (size_t i = 0; i < modes.length(); ++i) + { + switch (modes[i]) + { + case '+': + adding = 1; + break; + case '-': + adding = 0; + break; + default: + if (adding == -1) + break; + ChannelMode *cm = ModeManager::FindChannelModeByChar(modes[i]); + if (!cm || !cm->CanSet(u)) + { + source.Reply(_("Unknown mode character %c ignored."), modes[i]); + break; + } + Anope::string mode_param; + if (((cm->Type == MODE_STATUS || cm->Type == MODE_LIST) && !sep.GetToken(mode_param)) || (cm->Type == MODE_PARAM && adding && !sep.GetToken(mode_param))) + source.Reply(_("Missing parameter for mode %c."), cm->ModeChar); + else + { + ci->SetMLock(cm, adding, mode_param, u->nick); + if (!mode_param.empty()) + mode_param = " " + mode_param; + source.Reply(_("%c%c%s locked on %s"), adding ? '+' : '-', cm->ModeChar, mode_param.c_str(), ci->name.c_str()); + } + } + } + + if (ci->c) + check_modes(ci->c); + } + else if (subcommand.equals_ci("DEL") && !param.empty()) + { + spacesepstream sep(param); + Anope::string modes; + + sep.GetToken(modes); + + int adding = -1; + for (size_t i = 0; i < modes.length(); ++i) + { + switch (modes[i]) + { + case '+': + adding = 1; + break; + case '-': + adding = 0; + break; + default: + if (adding == -1) + break; + ChannelMode *cm = ModeManager::FindChannelModeByChar(modes[i]); + if (!cm || !cm->CanSet(u)) + { + source.Reply(_("Unknown mode character %c ignored."), modes[i]); + break; + } + Anope::string mode_param; + if (!cm->Type == MODE_REGULAR && !sep.GetToken(mode_param)) + source.Reply(_("Missing parameter for mode %c."), cm->ModeChar); + else + { + if (ci->RemoveMLock(cm, mode_param)) + { + if (!mode_param.empty()) + mode_param = " " + mode_param; + source.Reply(_("%c%c%s has been unlocked from %s."), adding == 1 ? '+' : '-', cm->ModeChar, mode_param.c_str(), ci->name.c_str()); + } + else + source.Reply(_("%c is not locked on %s."), cm->ModeChar, ci->name.c_str()); + } + } + } + } + else if (subcommand.equals_ci("LIST")) + { + const std::multimap<ChannelModeName, ModeLock> &mlocks = ci->GetMLock(); + if (mlocks.empty()) + { + source.Reply(_("Channel %s has no mode locks."), ci->name.c_str()); + } + else + { + source.Reply(_("Mode locks for %s:"), ci->name.c_str()); + for (std::multimap<ChannelModeName, ModeLock>::const_iterator it = mlocks.begin(), it_end = mlocks.end(); it != it_end; ++it) + { + const ModeLock &ml = it->second; + ChannelMode *cm = ModeManager::FindChannelModeByName(ml.name); + if (!cm) + continue; + + Anope::string modeparam = ml.param; + if (!modeparam.empty()) + modeparam = " " + modeparam; + source.Reply(_("%c%c%s, by %s on %s"), ml.set ? '+' : '-', cm->ModeChar, modeparam.c_str(), ml.setter.c_str(), do_strftime(ml.created).c_str()); + } + } + } + else + this->OnSyntaxError(source, subcommand); + } + + void DoSet(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + + spacesepstream sep(params.size() > 3 ? params[3] : ""); + Anope::string modes = params[2], param; + + Log(LOG_COMMAND, u, this, ci) << "to set " << params[2]; + + int adding = -1; + for (size_t i = 0; i < modes.length(); ++i) + { + switch (modes[i]) + { + case '+': + adding = 1; + break; + case '-': + adding = 0; + break; + case '*': + if (adding == -1) + break; + for (unsigned j = 0; j < ModeManager::ChannelModes.size(); ++j) + { + ChannelMode *cm = ModeManager::ChannelModes[j]; + if (cm->CanSet(u)) + { + if (cm->Type == MODE_REGULAR || (!adding && cm->Type == MODE_PARAM)) + { + if (adding) + ci->c->SetMode(NULL, cm); + else + ci->c->RemoveMode(NULL, cm); + } + } + } + break; + default: + if (adding == -1) + break; + ChannelMode *cm = ModeManager::FindChannelModeByChar(modes[i]); + if (!cm || !cm->CanSet(u)) + continue; + switch (cm->Type) + { + case MODE_REGULAR: + if (adding) + ci->c->SetMode(NULL, cm); + else + ci->c->RemoveMode(NULL, cm); + break; + case MODE_PARAM: + if (adding && !sep.GetToken(param)) + break; + if (adding) + ci->c->SetMode(NULL, cm, param); + else + ci->c->RemoveMode(NULL, cm); + break; + case MODE_STATUS: + { + if (!sep.GetToken(param)) + break; + + if (!this->CanSet(u, ci, cm->Name)) + { + source.Reply(_("You do not have access to set mode %c."), cm->ModeChar); + break; + } + + AccessGroup u_access = ci->AccessFor(u); + + if (str_is_wildcard(param)) + { + for (CUserList::const_iterator it = ci->c->users.begin(), it_end = ci->c->users.end(); it != it_end; ++it) + { + UserContainer *uc = *it; + + AccessGroup targ_access = ci->AccessFor(uc->user); + + if (targ_access > u_access) + { + source.Reply(_("You do not have the access to change %s's modes."), uc->user->nick.c_str()); + continue; + } + + if (Anope::Match(uc->user->GetMask(), param)) + { + if (adding) + ci->c->SetMode(NULL, cm, uc->user->nick); + else + ci->c->RemoveMode(NULL, cm, uc->user->nick); + } + } + } + else + { + User *target = finduser(param); + if (target != NULL) + { + AccessGroup targ_access = ci->AccessFor(target); + if (targ_access > u_access) + { + source.Reply(_("You do not have the access to change %s's modes."), target->nick.c_str()); + break; + } + } + if (adding) + ci->c->SetMode(NULL, cm, param); + else + ci->c->RemoveMode(NULL, cm, param); + } + break; + } + case MODE_LIST: + if (!sep.GetToken(param)) + break; + if (adding) + ci->c->SetMode(NULL, cm, param); + else + { + std::pair<Channel::ModeList::iterator, Channel::ModeList::iterator> its = ci->c->GetModeList(cm->Name); + for (; its.first != its.second;) + { + const Anope::string &mask = its.first->second; + ++its.first; + + if (Anope::Match(mask, param)) + ci->c->RemoveMode(NULL, cm, mask); + } + } + } + } + } + } + + public: + CommandCSMode(Module *creator) : Command(creator, "chanserv/mode", 3, 4) + { + this->SetDesc(_("Control modes and mode locks on a channel")); + this->SetSyntax(_("\037channel\037 LOCK {ADD|DEL|LIST} [\037what\037]")); + this->SetSyntax(_("\037channel\037 SET \037modes\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &subcommand = params[1]; + + User *u = source.u; + ChannelInfo *ci = cs_findchan(params[0]); + + if (!ci || !ci->c) + source.Reply(CHAN_X_NOT_IN_USE, ci->name.c_str()); + else if (!ci->HasPriv(u, CA_MODE) && !u->HasCommand("chanserv/chanserv/mode")) + source.Reply(ACCESS_DENIED); + else if (subcommand.equals_ci("LOCK")) + this->DoLock(source, ci, params); + else if (subcommand.equals_ci("SET")) + this->DoSet(source, ci, params); + else + this->OnSyntaxError(source, ""); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Mainly controls mode locks and mode access (which is different from channel access)\n" + "on a channel.\n" + " \n" + "The \002MODE LOCK\002 command allows you to add, delete, and view mode locks on a channel.\n" + "If a mode is locked on or off, services will not allow that mode to be changed.\n" + "Example:\n" + " \002MODE #channel LOCK ADD +bmnt *!*@*aol*\002\n" + " \n" + "The \002MODE SET\002 command allows you to set modes through services. Wildcards * and ? may\n" + "be given as parameters for list and status modes.\n" + "Example:\n" + " \002MODE #channel SET +v *\002\n" + " Sets voice status to all users in the channel.\n" + " \n" + " \002MODE #channel SET -b ~c:*\n" + " Clears all extended bans that start with ~c:")); + return true; + } +}; + +class CSMode : public Module +{ + CommandCSMode commandcsmode; + + public: + CSMode(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandcsmode(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandcsmode); + } +}; + +MODULE_INIT(CSMode) diff --git a/modules/commands/cs_modes.cpp b/modules/commands/cs_modes.cpp new file mode 100644 index 000000000..cee2f9460 --- /dev/null +++ b/modules/commands/cs_modes.cpp @@ -0,0 +1,431 @@ +/* ChanServ core 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 "module.h" + +class CommandModeBase : public Command +{ + void do_mode(CommandSource &source, Command *com, ChannelMode *cm, const Anope::string &chan, const Anope::string &nick, bool set, ChannelAccess level, ChannelAccess levelself, ChannelInfoFlag notice) + { + User *u = source.u; + User *u2 = finduser(nick); + Channel *c = findchan(chan); + ChannelInfo *ci = c ? c->ci : NULL; + + bool is_same = u == u2; + + AccessGroup u_access = ci ? ci->AccessFor(u) : AccessGroup(), u2_access = ci && u2 ? ci->AccessFor(u2) : AccessGroup(); + + if (!c) + source.Reply(CHAN_X_NOT_IN_USE, chan.c_str()); + else if (!ci) + source.Reply(CHAN_X_NOT_REGISTERED, chan.c_str()); + else if (!u2) + source.Reply(NICK_X_NOT_IN_USE, nick.c_str()); + else if (is_same ? !ci->HasPriv(u, levelself) : !ci->HasPriv(u, level)) + source.Reply(ACCESS_DENIED); + else if (!set && !is_same && ci->HasFlag(CI_PEACE) && u2_access >= u_access) + source.Reply(ACCESS_DENIED); + else if (!set && u2->IsProtected() && !is_same) + source.Reply(ACCESS_DENIED); + else if (!c->FindUser(u2)) + source.Reply(NICK_X_NOT_ON_CHAN, u2->nick.c_str(), c->name.c_str()); + else + { + if (set) + c->SetMode(NULL, cm, u2->nick); + else + c->RemoveMode(NULL, cm, u2->nick); + + Log(LOG_COMMAND, u, com, ci) << "for " << u2->nick; + if (notice && ci->HasFlag(notice)) + ircdproto->SendMessage(ci->WhoSends(), c->name, "%s command used for %s by %s", com->name.c_str(), u2->nick.c_str(), u->nick.c_str()); + } + } + + protected: + /** do_util: not a command, but does the job of others + * @param source The source of the command + * @param com The command calling this function + * @param cm A channel mode class + * @param chan The channel its being set on + * @param nick The nick the modes being set on + * @param set Is the mode being set or removed + * @param level The acecss level required to set this mode on someone else + * @param levelself The access level required to set this mode on yourself + * @param notice Flag required on a channel to send a notice + */ + void do_util(CommandSource &source, Command *com, ChannelMode *cm, const Anope::string &chan, const Anope::string &nick, bool set, ChannelAccess level, ChannelAccess levelself, ChannelInfoFlag notice) + { + User *u = source.u; + + if (chan.empty()) + for (UChannelList::iterator it = u->chans.begin(); it != u->chans.end(); ++it) + do_mode(source, com, cm, (*it)->chan->name, u->nick, set, level, levelself, notice); + else + do_mode(source, com, cm, chan, !nick.empty() ? nick : u->nick, set, level, levelself, notice); + + return; + } + + public: + CommandModeBase(Module *creator, const Anope::string &cname) : Command(creator, cname, 0, 2) + { + this->SetSyntax(_("[\037#channel\037] [\037nick\037]")); + } +}; + +class CommandCSOp : public CommandModeBase +{ + public: + CommandCSOp(Module *creator) : CommandModeBase(creator, "chanserv/op") + { + this->SetDesc(_("Gives Op status to a selected nick on a channel")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + ChannelMode *cm = ModeManager::FindChannelModeByName(CMODE_OP); + + return do_util(source, this, cm, !params.empty() ? params[0] : "", params.size() > 1 ? params[1] : "", true, CA_OPDEOP, CA_OPDEOPME, CI_OPNOTICE); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Ops a selected nick on a channel. If nick is not given,\n" + "it will op you. If channel is not given, it will op you\n" + "on every channel.\n" + " \n" + "By default, limited to AOPs or those with level 5 access \n" + "and above on the channel.")); + return true; + } +}; + +class CommandCSDeOp : public CommandModeBase +{ + public: + CommandCSDeOp(Module *creator) : CommandModeBase(creator, "chanserv/deop") + { + this->SetDesc(_("Deops a selected nick on a channel")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + ChannelMode *cm = ModeManager::FindChannelModeByName(CMODE_OP); + + return do_util(source, this, cm, !params.empty() ? params[0] : "", params.size() > 1 ? params[1] : "", false, CA_OPDEOP, CA_OPDEOPME, CI_OPNOTICE); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply("Deops a selected nick on a channel. If nick is not given,\n" + "it will deop you. If channel is not given, it will deop\n" + "you on every channel.\n" + " \n" + "By default, limited to AOPs or those with level 5 access \n" + "and above on the channel."); + return true; + } +}; + +class CommandCSVoice : public CommandModeBase +{ + public: + CommandCSVoice(Module *creator) : CommandModeBase(creator, "chanserv/voice") + { + this->SetDesc(_("Voices a selected nick on a channel")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + ChannelMode *cm = ModeManager::FindChannelModeByName(CMODE_VOICE); + + return do_util(source, this, cm, !params.empty() ? params[0] : "", params.size() > 1 ? params[1] : "", true, CA_VOICE, CA_VOICEME, CI_BEGIN); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Voices a selected nick on a channel. If nick is not given,\n" + "it will voice you. If channel is not given, it will voice you\n" + "on every channel.\n" + " \n" + "By default, limited to AOPs or those with level 5 access \n" + "and above on the channel, or to VOPs or those with level 3 \n" + "and above for self voicing.")); + return true; + } +}; + +class CommandCSDeVoice : public CommandModeBase +{ + public: + CommandCSDeVoice(Module *creator) : CommandModeBase(creator, "chanserv/devoice") + { + this->SetDesc(_("Devoices a selected nick on a channel")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + ChannelMode *cm = ModeManager::FindChannelModeByName(CMODE_VOICE); + + return do_util(source, this, cm, !params.empty() ? params[0] : "", params.size() > 1 ? params[1] : "", false, CA_VOICE, CA_VOICEME, CI_BEGIN); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Devoices a selected nick on a channel. If nick is not given,\n" + "it will devoice you. If channel is not given, it will devoice\n" + "you on every channel.\n" + " \n" + "By default, limited to AOPs or those with level 5 access \n" + "and above on the channel, or to VOPs or those with level 3 \n" + "and above for self devoicing.")); + return true; + } +}; + +class CommandCSHalfOp : public CommandModeBase +{ + public: + CommandCSHalfOp(Module *creator) : CommandModeBase(creator, "chanserv/halfop") + { + this->SetDesc(_("Halfops a selected nick on a channel")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + ChannelMode *cm = ModeManager::FindChannelModeByName(CMODE_HALFOP); + + if (!cm) + return; + + return do_util(source, this, cm, !params.empty() ? params[0] : "", params.size() > 1 ? params[1] : "", true, CA_HALFOP, CA_HALFOPME, CI_BEGIN); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Halfops a selected nick on a channel. If nick is not given,\n" + "it will halfop you. If channel is not given, it will halfop\n" + "you on every channel.\n" + " \n" + "By default, limited to AOPs and those with level 5 access \n" + "and above on the channel, or to HOPs or those with level 4 \n")); + return true; + } +}; + +class CommandCSDeHalfOp : public CommandModeBase +{ + public: + CommandCSDeHalfOp(Module *creator) : CommandModeBase(creator, "chanserv/dehalfop") + { + this->SetDesc(_("Dehalfops a selected nick on a channel")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + ChannelMode *cm = ModeManager::FindChannelModeByName(CMODE_HALFOP); + + if (!cm) + return; + + return do_util(source, this, cm, !params.empty() ? params[0] : "", params.size() > 1 ? params[1] : "", false, CA_HALFOP, CA_HALFOPME, CI_BEGIN); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Dehalfops a selected nick on a channel. If nick is not given,\n" + "it will dehalfop you. If channel is not given, it will dehalfop\n" + "you on every channel.\n" + " \n" + "By default, limited to AOPs and those with level 5 access \n" + "and above on the channel, or to HOPs or those with level 4 \n" + "and above for self dehalfopping.")); + return true; + } +}; + +class CommandCSProtect : public CommandModeBase +{ + public: + CommandCSProtect(Module *creator) : CommandModeBase(creator, "chanserv/protect") + { + this->SetDesc(_("Protects a selected nick on a channel")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + ChannelMode *cm = ModeManager::FindChannelModeByName(CMODE_PROTECT); + + if (!cm) + return; + + return do_util(source, this, cm, !params.empty() ? params[0] : "", params.size() > 1 ? params[1] : "", true, CA_PROTECT, CA_PROTECTME, CI_BEGIN); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Protects a selected nick on a channel. If nick is not given,\n" + "it will protect you. If channel is not given, it will protect\n" + "you on every channel.\n" + " \n" + "By default, limited to the founder, or to SOPs or those with \n" + "level 10 and above on the channel for self protecting.")); + return true; + } +}; + +class CommandCSDeProtect : public CommandModeBase +{ + public: + CommandCSDeProtect(Module *creator) : CommandModeBase(creator, "chanserv/deprotect") + { + this->SetDesc(_("Deprotects a selected nick on a channel")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + ChannelMode *cm = ModeManager::FindChannelModeByName(CMODE_PROTECT); + + if (!cm) + return; + + return do_util(source, this, cm, !params.empty() ? params[0] : "", params.size() > 1 ? params[1] : "", false, CA_PROTECT, CA_PROTECTME, CI_BEGIN); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Deprotects a selected nick on a channel. If nick is not given,\n" + "it will deprotect you. If channel is not given, it will deprotect\n" + "you on every channel.\n" + " \n" + "By default, limited to the founder, or to SOPs or those with \n")); + return true; + } +}; + +class CommandCSOwner : public CommandModeBase +{ + public: + CommandCSOwner(Module *creator) : CommandModeBase(module, "chanserv/owner") + { + this->SetDesc(_("Gives you owner status on channel")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + ChannelMode *cm = ModeManager::FindChannelModeByName(CMODE_OWNER); + + if (!cm) + return; + + return do_util(source, this, cm, !params.empty() ? params[0] : "", params.size() > 1 ? params[1] : "", true, CA_OWNER, CA_OWNERME, CI_BEGIN); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Gives the selected nick owner status on \002channel\002. If nick is not\n" + "given, it will give you owner. If channel is not given, it will\n" + "give you owner on every channel.\n" + " \n" + "Limited to those with founder access on the channel.")); + return true; + } +}; + +class CommandCSDeOwner : public CommandModeBase +{ + public: + CommandCSDeOwner(Module *creator) : CommandModeBase(creator, "chanserv/deowner") + { + this->SetDesc(_("Removes your owner status on a channel")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + ChannelMode *cm = ModeManager::FindChannelModeByName(CMODE_OWNER); + + if (!cm) + return; + + return do_util(source, this, cm, !params.empty() ? params[0] : "", params.size() > 1 ? params[1] : "", false, CA_OWNER, CA_OWNERME, CI_BEGIN); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Removes owner status from the selected nick on \002channel\002. If nick\n" + "is not given, it will deowner you. If channel is not given, it will\n" + "deowner you on every channel.\n" + " \n" + "Limited to those with founder access on the channel.")); + return true; + } +}; + +class CSModes : public Module +{ + CommandCSOwner commandcsowner; + CommandCSDeOwner commandcsdeowner; + CommandCSProtect commandcsprotect; + CommandCSDeProtect commandcsdeprotect; + CommandCSOp commandcsop; + CommandCSDeOp commandcsdeop; + CommandCSHalfOp commandcshalfop; + CommandCSDeHalfOp commandcsdehalfop; + CommandCSVoice commandcsvoice; + CommandCSDeVoice commandcsdevoice; + + public: + CSModes(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandcsowner(this), commandcsdeowner(this), commandcsprotect(this), commandcsdeprotect(this), + commandcsop(this), commandcsdeop(this), commandcshalfop(this), commandcsdehalfop(this), + commandcsvoice(this), commandcsdevoice(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandcsop); + ModuleManager::RegisterService(&commandcsdeop); + ModuleManager::RegisterService(&commandcsvoice); + ModuleManager::RegisterService(&commandcsdevoice); + + ModuleManager::RegisterService(&commandcsowner); + ModuleManager::RegisterService(&commandcsdeowner); + ModuleManager::RegisterService(&commandcsprotect); + ModuleManager::RegisterService(&commandcsdeprotect); + ModuleManager::RegisterService(&commandcshalfop); + ModuleManager::RegisterService(&commandcsdehalfop); + } +}; + +MODULE_INIT(CSModes) diff --git a/modules/commands/cs_register.cpp b/modules/commands/cs_register.cpp new file mode 100644 index 000000000..117212519 --- /dev/null +++ b/modules/commands/cs_register.cpp @@ -0,0 +1,193 @@ +/* ChanServ core 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 "module.h" + +class CommandCSRegister : public Command +{ + public: + CommandCSRegister(Module *creator) : Command(creator, "chanserv/register", 1, 2) + { + this->SetDesc(_("Register a channel")); + this->SetSyntax(_("\037channel\037 [\037description\037]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &chan = params[0]; + const Anope::string &chdesc = params.size() > 1 ? params[1] : ""; + + User *u = source.u; + Channel *c = findchan(params[0]); + ChannelInfo *ci = cs_findchan(params[0]); + + if (readonly) + source.Reply(_("Sorry, channel registration is temporarily disabled.")); + else if (u->Account()->HasFlag(NI_UNCONFIRMED)) + source.Reply(_("You must confirm your account before you can register a channel.")); + else if (chan[0] == '&') + source.Reply(_("Local channels cannot be registered.")); + else if (chan[0] != '#') + source.Reply(CHAN_SYMBOL_REQUIRED); + else if (!ircdproto->IsChannelValid(chan)) + source.Reply(CHAN_X_INVALID, chan.c_str()); + else if (ci) + source.Reply(_("Channel \002%s\002 is already registered!"), chan.c_str()); + else if (c && !c->HasUserStatus(u, CMODE_OP)) + source.Reply(_("You must be a channel operator to register the channel.")); + else if (Config->CSMaxReg && u->Account()->channelcount >= Config->CSMaxReg && !u->HasPriv("chanserv/no-register-limit")) + source.Reply(u->Account()->channelcount > Config->CSMaxReg ? CHAN_EXCEEDED_CHANNEL_LIMIT : _(CHAN_REACHED_CHANNEL_LIMIT), Config->CSMaxReg); + else + { + ci = new ChannelInfo(chan); + ci->SetFounder(u->Account()); + if (!chdesc.empty()) + ci->desc = chdesc; + + if (c && !c->topic.empty()) + { + ci->last_topic = c->topic; + ci->last_topic_setter = c->topic_setter; + ci->last_topic_time = c->topic_time; + } + else + ci->last_topic_setter = source.owner->nick; + + ci->bi = NULL; + Log(LOG_COMMAND, u, this, ci); + source.Reply(_("Channel \002%s\002 registered under your nickname: %s"), chan.c_str(), u->nick.c_str()); + + /* Implement new mode lock */ + if (c) + { + check_modes(c); + + ChannelMode *cm; + if (u->FindChannel(c) != NULL) + { + /* On most ircds you do not receive the admin/owner mode till its registered */ + if ((cm = ModeManager::FindChannelModeByName(CMODE_OWNER))) + c->SetMode(NULL, cm, u->nick); + else if ((cm = ModeManager::FindChannelModeByName(CMODE_PROTECT))) + c->RemoveMode(NULL, cm, u->nick); + } + + /* Mark the channel as persistant */ + if (c->HasMode(CMODE_PERM)) + ci->SetFlag(CI_PERSIST); + /* Persist may be in def cflags, set it here */ + else if (ci->HasFlag(CI_PERSIST) && (cm = ModeManager::FindChannelModeByName(CMODE_PERM))) + c->SetMode(NULL, CMODE_PERM); + } + + FOREACH_MOD(I_OnChanRegistered, OnChanRegistered(ci)); + } + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Registers a channel in the %s database. In order\n" + "to use this command, you must first be a channel operator\n" + "on the channel you're trying to register.\n" + "The description, which is optional, is a\n" + "general description of the channel's purpose.\n" + " \n" + "When you register a channel, you are recorded as the\n" + "\"founder\" of the channel. The channel founder is allowed\n" + "to change all of the channel settings for the channel;\n" + "%s will also automatically give the founder\n" + "channel-operator privileges when s/he enters the channel.\n" + "See the \002ACCESS\002 command (\002%s%s HELP ACCESS\002) for\n" + "information on giving a subset of these privileges to\n" + "other channel users.\n" + " \n" + "NOTICE: In order to register a channel, you must have\n" + "first registered your nickname. If you haven't,\n" + "\002%s%s HELP\002 for information on how to do so."), + source.owner->nick.c_str(), source.owner->nick.c_str(), Config->UseStrictPrivMsgString.c_str(), source.owner->nick.c_str(), Config->UseStrictPrivMsgString.c_str(), source.owner->nick.c_str()); + return true; + } +}; + +class ExpireCallback : public CallBack +{ + public: + ExpireCallback(Module *owner) : CallBack(owner, Config->ExpireTimeout, Anope::CurTime, true) { } + + void Tick(time_t) + { + if (!Config->CSExpire || noexpire || readonly) + 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->c && Config->CSExpire && 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_SUSPENDED)) + extra = "suspended "; + + Log(LOG_NORMAL, "chanserv/expire") << "Expiring " << extra << "channel " << ci->name << " (founder: " << (ci->GetFounder() ? ci->GetFounder()->display : "(none)") << ")"; + FOREACH_MOD(I_OnChanExpire, OnChanExpire(ci)); + delete ci; + } + } + } +}; + +class CSRegister : public Module +{ + CommandCSRegister commandcsregister; + ExpireCallback ecb; + + public: + CSRegister(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandcsregister(this), ecb(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandcsregister); + ModuleManager::Attach(I_OnDelChan, this); + } + + void OnDelChan(ChannelInfo *ci) + { + if (ci->c && ci->c->HasMode(CMODE_REGISTERED)) + ci->c->RemoveMode(NULL, CMODE_REGISTERED, "", false); + } +}; + +MODULE_INIT(CSRegister) diff --git a/modules/commands/cs_saset.cpp b/modules/commands/cs_saset.cpp new file mode 100644 index 000000000..699c5bc2c --- /dev/null +++ b/modules/commands/cs_saset.cpp @@ -0,0 +1,74 @@ +/* ChanServ core 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 "module.h" + +class CommandCSSASet : public Command +{ + public: + CommandCSSASet(Module *creator) : Command(creator, "chanserv/saset", 2, 3) + { + this->SetDesc(_("Forcefully set channel options and information")); + this->SetSyntax(_("\037option\037 \037channel\037 \037parameters\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + this->OnSyntaxError(source, ""); + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Allows Services Operators to forcefully change settings\n" + "on channels.\n" + " \n" + "Available options:")); + Anope::string this_name = source.command; + for (BotInfo::command_map::iterator it = source.owner->commands.begin(), it_end = source.owner->commands.end(); it != it_end; ++it) + { + const Anope::string &c_name = it->first; + CommandInfo &info = it->second; + if (c_name.find_ci(this_name + " ") == 0) + { + service_reference<Command> command(info.name); + if (command) + { + source.command = it->first; + command->OnServHelp(source); + } + } + } + source.Reply(_("Type \002%s%s HELP SASET \037option\037\002 for more information on a\n" + "particular option."), Config->UseStrictPrivMsgString.c_str(), source.owner->nick.c_str()); + return true; + } +}; + +class CSSASet : public Module +{ + CommandCSSASet commandcssaset; + + public: + CSSASet(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandcssaset(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandcssaset); + } +}; + +MODULE_INIT(CSSASet) diff --git a/modules/commands/cs_saset_noexpire.cpp b/modules/commands/cs_saset_noexpire.cpp new file mode 100644 index 000000000..1e085496f --- /dev/null +++ b/modules/commands/cs_saset_noexpire.cpp @@ -0,0 +1,81 @@ +/* ChanServ core 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 "module.h" + +class CommandCSSASetNoexpire : public Command +{ + public: + CommandCSSASetNoexpire(Module *creator) : Command(creator, "chanserv/saset/noexpire", 2, 2) + { + this->SetDesc(_("Prevent the channel from expiring")); + this->SetDesc(_("\037channel\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + ChannelInfo *ci = cs_findchan(params[0]); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + if (source.permission.empty() && !ci->HasPriv(u, CA_SET)) + { + source.Reply(ACCESS_DENIED); + return; + } + + if (params[1].equals_ci("ON")) + { + ci->SetFlag(CI_NO_EXPIRE); + source.Reply(_("Channel %s \002will not\002 expire."), ci->name.c_str()); + } + else if (params[1].equals_ci("OFF")) + { + ci->UnsetFlag(CI_NO_EXPIRE); + source.Reply(_("Channel %s \002will\002 expire."), ci->name.c_str()); + } + else + this->OnSyntaxError(source, "NOEXPIRE"); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Sets whether the given channel will expire. Setting this\n" + "to ON prevents the channel from expiring.")); + return true; + } +}; + +class CSSetNoexpire : public Module +{ + CommandCSSASetNoexpire commandcssasetnoexpire; + + public: + CSSetNoexpire(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandcssasetnoexpire(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandcssasetnoexpire); + } +}; + +MODULE_INIT(CSSetNoexpire) diff --git a/modules/commands/cs_set.cpp b/modules/commands/cs_set.cpp new file mode 100644 index 000000000..65719ce91 --- /dev/null +++ b/modules/commands/cs_set.cpp @@ -0,0 +1,74 @@ +/* ChanServ core 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 "module.h" + +class CommandCSSet : public Command +{ + public: + CommandCSSet(Module *creator) : Command(creator, "chanserv/set", 2, 3) + { + this->SetDesc(_("Set channel options and information")); + this->SetSyntax(_("\037option\037 \037channel\037 \037parameters\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + this->OnSyntaxError(source, ""); + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Allows the channel founder to set various channel options\n" + "and other information.\n" + " \n" + "Available options:")); + Anope::string this_name = source.command; + for (BotInfo::command_map::iterator it = source.owner->commands.begin(), it_end = source.owner->commands.end(); it != it_end; ++it) + { + const Anope::string &c_name = it->first; + CommandInfo &info = it->second; + if (c_name.find_ci(this_name + " ") == 0) + { + service_reference<Command> command(info.name); + if (command) + { + source.command = it->first; + command->OnServHelp(source); + } + } + } + source.Reply(_("Type \002%s%s HELP SET \037option\037\002 for more information on a\n" + "particular option."), Config->UseStrictPrivMsgString.c_str(), source.owner->nick.c_str()); + return true; + } +}; + +class CSSet : public Module +{ + CommandCSSet commandcsset; + + public: + CSSet(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandcsset(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandcsset); + } +}; + +MODULE_INIT(CSSet) diff --git a/modules/commands/cs_set_bantype.cpp b/modules/commands/cs_set_bantype.cpp new file mode 100644 index 000000000..94d2aa151 --- /dev/null +++ b/modules/commands/cs_set_bantype.cpp @@ -0,0 +1,98 @@ +/* ChanServ core 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 "module.h" + +class CommandCSSetBanType : public Command +{ + public: + CommandCSSetBanType(Module *creator, const Anope::string &cname = "chanserv/set/bantype") : Command(creator, cname, 2, 2) + { + this->SetDesc(_("Set how Services make bans on the channel")); + this->SetSyntax(_("\037channel\037 \037bantype\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + ChannelInfo *ci = cs_findchan(params[0]); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + if (source.permission.empty() && !ci->HasPriv(u, CA_SET)) + { + source.Reply(ACCESS_DENIED); + return; + } + + try + { + int16 new_type = convertTo<int16>(params[1]); + if (new_type < 0 || new_type > 3) + throw ConvertException("Invalid range"); + ci->bantype = new_type; + source.Reply(_("Ban type for channel %s is now #%d."), ci->name.c_str(), ci->bantype); + } + catch (const ConvertException &) + { + source.Reply(_("\002%s\002 is not a valid ban type."), params[1].c_str()); + } + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Sets the ban type that will be used by services whenever\n" + "they need to ban someone from your channel.\n" + " \n" + "bantype is a number between 0 and 3 that means:\n" + " \n" + "0: ban in the form *!user@host\n" + "1: ban in the form *!*user@host\n" + "2: ban in the form *!*@host\n" + "3: ban in the form *!*user@*.domain"), this->name.c_str()); + return true; + } +}; + +class CommandCSSASetBanType : public CommandCSSetBanType +{ + public: + CommandCSSASetBanType(Module *creator) : CommandCSSetBanType(creator, "chanserv/saset/bantype") + { + } +}; + +class CSSetBanType : public Module +{ + CommandCSSetBanType commandcssetbantype; + CommandCSSASetBanType commandcssasetbantype; + + public: + CSSetBanType(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandcssetbantype(this), commandcssasetbantype(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandcssetbantype); + ModuleManager::RegisterService(&commandcssasetbantype); + } +}; + +MODULE_INIT(CSSetBanType) diff --git a/modules/commands/cs_set_description.cpp b/modules/commands/cs_set_description.cpp new file mode 100644 index 000000000..5c2cd447d --- /dev/null +++ b/modules/commands/cs_set_description.cpp @@ -0,0 +1,89 @@ +/* ChanServ core 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 "module.h" + +class CommandCSSetDescription : public Command +{ + public: + CommandCSSetDescription(Module *creator, const Anope::string &cname = "chanserv/set/description") : Command(creator, cname, 1, 2) + { + this->SetDesc(_("Set the channel description")); + this->SetSyntax(_("\037channel\037 [\037description\037]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + ChannelInfo *ci = cs_findchan(params[0]); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + if (source.permission.empty() && !ci->HasPriv(u, CA_SET)) + { + source.Reply(ACCESS_DENIED); + return; + } + + if (params.size() > 1) + { + ci->desc = params[1]; + source.Reply(_("Description of %s changed to \002%s\002."), ci->name.c_str(), ci->desc.c_str()); + } + else + { + ci->desc.clear(); + source.Reply(_("Description of %s unset."), ci->name.c_str()); + } + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Sets the description for the channel, which shows up with\n" + "the \002LIST\002 and \002INFO\002 commands."), this->name.c_str()); + return true; + } +}; + +class CommandCSSASetDescription : public CommandCSSetDescription +{ + public: + CommandCSSASetDescription(Module *creator) : CommandCSSetDescription(creator, "chanserv/saset/description") + { + } +}; + +class CSSetDescription : public Module +{ + CommandCSSetDescription commandcssetdescription; + CommandCSSASetDescription commandcssasetdescription; + + public: + CSSetDescription(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandcssetdescription(this), commandcssasetdescription(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandcssetdescription); + ModuleManager::RegisterService(&commandcssasetdescription); + } +}; + +MODULE_INIT(CSSetDescription) diff --git a/modules/commands/cs_set_founder.cpp b/modules/commands/cs_set_founder.cpp new file mode 100644 index 000000000..2169af972 --- /dev/null +++ b/modules/commands/cs_set_founder.cpp @@ -0,0 +1,105 @@ +/* ChanServ core 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 "module.h" + +class CommandCSSetFounder : public Command +{ + public: + CommandCSSetFounder(Module *creator, const Anope::string &cname = "chanserv/set/founder") : Command(creator, cname, 2, 2) + { + this->SetDesc(_("Set the founder of a channel")); + this->SetSyntax(_("\037channel\037 \037nick\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + ChannelInfo *ci = cs_findchan(params[0]); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + if (source.permission.empty() && !ci->HasPriv(u, CA_SET)) + { + source.Reply(ACCESS_DENIED); + return; + } + + if (source.permission.empty() && (ci->HasFlag(CI_SECUREFOUNDER) ? !IsFounder(u, ci) : !ci->HasPriv(u, CA_FOUNDER))) + { + source.Reply(ACCESS_DENIED); + return; + } + + NickAlias *na = findnick(params[1]); + + if (!na) + { + source.Reply(NICK_X_NOT_REGISTERED, params[1].c_str()); + return; + } + + NickCore *nc = na->nc; + if (Config->CSMaxReg && nc->channelcount >= Config->CSMaxReg && !u->HasPriv("chanserv/no-register-limit")) + { + source.Reply(_("\002%s\002 has too many channels registered."), na->nick.c_str()); + return; + } + + Log(!source.permission.empty() ? LOG_ADMIN : LOG_COMMAND, u, this, ci) << "to change the founder to " << nc->display; + + ci->SetFounder(nc); + + source.Reply(_("Founder of %s changed to \002%s\002."), ci->name.c_str(), na->nick.c_str()); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Changes the founder of a channel. The new nickname must\n" + "be a registered one."), this->name.c_str()); + return true; + } +}; + +class CommandCSSASetFounder : public CommandCSSetFounder +{ + public: + CommandCSSASetFounder(Module *creator) : CommandCSSetFounder(creator, "chanserv/saset/founder") + { + } +}; + +class CSSetFounder : public Module +{ + CommandCSSetFounder commandcssetfounder; + CommandCSSASetFounder commandcssasetfounder; + + public: + CSSetFounder(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandcssetfounder(this), commandcssasetfounder(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandcssetfounder); + ModuleManager::RegisterService(&commandcssasetfounder); + } +}; + +MODULE_INIT(CSSetFounder) diff --git a/modules/commands/cs_set_keeptopic.cpp b/modules/commands/cs_set_keeptopic.cpp new file mode 100644 index 000000000..05578a2e5 --- /dev/null +++ b/modules/commands/cs_set_keeptopic.cpp @@ -0,0 +1,94 @@ +/* ChanServ core 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 "module.h" + +class CommandCSSetKeepTopic : public Command +{ + public: + CommandCSSetKeepTopic(Module *creator, const Anope::string &cname = "chanserv/set/keeptopic") : Command(creator, cname, 2, 2) + { + this->SetDesc(_("Retain topic when channel is not in use")); + this->SetSyntax(_("\037channel\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + ChannelInfo *ci = cs_findchan(params[0]); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + if (source.permission.empty() && !ci->HasPriv(u, CA_SET)) + { + source.Reply(ACCESS_DENIED); + return; + } + + if (params[1].equals_ci("ON")) + { + ci->SetFlag(CI_KEEPTOPIC); + source.Reply(_("Topic retention option for %s is now \002on\002."), ci->name.c_str()); + } + else if (params[1].equals_ci("OFF")) + { + ci->UnsetFlag(CI_KEEPTOPIC); + source.Reply(_("Topic retention option for %s is now \002off\002."), ci->name.c_str()); + } + else + this->OnSyntaxError(source, "KEEPTOPIC"); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Enables or disables the \002topic retention\002 option for a \n" + "channel. When \002topic retention\002 is set, the topic for the\n" + "channel will be remembered by %s even after the\n" + "last user leaves the channel, and will be restored the\n" + "next time the channel is created."), this->name.c_str(), source.owner->nick.c_str()); + return true; + } +}; + +class CommandCSSASetKeepTopic : public CommandCSSetKeepTopic +{ + public: + CommandCSSASetKeepTopic(Module *creator) : CommandCSSetKeepTopic(creator, "chanserv/saset/keeptopic") + { + } +}; + +class CSSetKeepTopic : public Module +{ + CommandCSSetKeepTopic commandcssetkeeptopic; + CommandCSSASetKeepTopic commandcssasetkeeptopic; + + public: + CSSetKeepTopic(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandcssetkeeptopic(this), commandcssasetkeeptopic(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandcssetkeeptopic); + ModuleManager::RegisterService(&commandcssasetkeeptopic); + } +}; + +MODULE_INIT(CSSetKeepTopic) diff --git a/modules/commands/cs_set_misc.cpp b/modules/commands/cs_set_misc.cpp new file mode 100644 index 000000000..8f145ab88 --- /dev/null +++ b/modules/commands/cs_set_misc.cpp @@ -0,0 +1,110 @@ +/* + * (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 "module.h" + +class CommandCSSetMisc : public Command +{ + public: + CommandCSSetMisc(Module *creator, const Anope::string &cname = "chanserv/set/misc") : Command(creator, cname, 1, 2) + { + this->SetSyntax(_("\037channel\037 \037parameters\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + ChannelInfo *ci = cs_findchan(params[0]); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + ci->Shrink("cs_set_misc:" + source.command.replace_all_cs(" ", "_")); + if (params.size() > 1) + { + ci->Extend("cs_set_misc:" + source.command.replace_all_cs(" ", "_"), new ExtensibleItemRegular<Anope::string>(params[1])); + source.Reply(CHAN_SETTING_CHANGED, source.command.c_str(), ci->name.c_str(), params[1].c_str()); + } + else + source.Reply(CHAN_SETTING_UNSET, source.command.c_str(), ci->name.c_str()); + } +}; + +class CommandCSSASetMisc : public CommandCSSetMisc +{ + public: + CommandCSSASetMisc(Module *creator) : CommandCSSetMisc(creator, "chanserv/saset/misc") + { + } +}; + +class CSSetMisc : public Module +{ + CommandCSSetMisc commandcssetmisc; + CommandCSSASetMisc commandcssasetmisc; + + public: + CSSetMisc(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, SUPPORTED), + commandcssetmisc(this), commandcssasetmisc(this) + { + this->SetAuthor("Anope"); + + Implementation i[] = { I_OnChanInfo, I_OnDatabaseWriteMetadata, I_OnDatabaseReadMetadata }; + ModuleManager::Attach(i, this, sizeof(i) / sizeof(Implementation)); + + ModuleManager::RegisterService(&this->commandcssetmisc); + ModuleManager::RegisterService(&this->commandcssasetmisc); + } + + void OnChanInfo(CommandSource &source, ChannelInfo *ci, bool ShowHidden) + { + std::deque<Anope::string> list; + ci->GetExtList(list); + + for (unsigned i = 0; i < list.size(); ++i) + { + if (list[i].find("cs_set_misc:") != 0) + continue; + + Anope::string value; + if (ci->GetExtRegular(list[i], value)) + source.Reply(" %s: %s", list[i].substr(12).replace_all_cs("_", " ").c_str(), value.c_str()); + } + } + + void OnDatabaseWriteMetadata(void (*WriteMetadata)(const Anope::string &, const Anope::string &), ChannelInfo *ci) + { + std::deque<Anope::string> list; + ci->GetExtList(list); + + for (unsigned i = 0; i < list.size(); ++i) + { + if (list[i].find("cs_set_misc:") != 0) + continue; + + Anope::string value; + if (ci->GetExtRegular(list[i], value)) + WriteMetadata(list[i], ":" + value); + } + } + + EventReturn OnDatabaseReadMetadata(ChannelInfo *ci, const Anope::string &key, const std::vector<Anope::string> ¶ms) + { + if (key.find("cs_set_misc:") == 0) + ci->Extend(key, new ExtensibleItemRegular<Anope::string>(params[0])); + + return EVENT_CONTINUE; + } +}; + +MODULE_INIT(CSSetMisc) diff --git a/modules/commands/cs_set_opnotice.cpp b/modules/commands/cs_set_opnotice.cpp new file mode 100644 index 000000000..018b89437 --- /dev/null +++ b/modules/commands/cs_set_opnotice.cpp @@ -0,0 +1,93 @@ +/* ChanServ core 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 "module.h" + +class CommandCSSetOpNotice : public Command +{ + public: + CommandCSSetOpNotice(Module *creator, const Anope::string &cname = "chanserv/set/notice") : Command(creator, cname, 2, 2) + { + this->SetDesc(_("Send a notice when OP/DEOP commands are used")); + this->SetSyntax(_("\037channel\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + ChannelInfo *ci = cs_findchan(params[0]); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + if (source.permission.empty() && !ci->HasPriv(u, CA_SET)) + { + source.Reply(ACCESS_DENIED); + return; + } + + if (params[1].equals_ci("ON")) + { + ci->SetFlag(CI_OPNOTICE); + source.Reply(_("Op-notice option for %s is now \002on\002."), ci->name.c_str()); + } + else if (params[1].equals_ci("OFF")) + { + ci->UnsetFlag(CI_OPNOTICE); + source.Reply(_("Op-notice option for %s is now \002off\002."), ci->name.c_str()); + } + else + this->OnSyntaxError(source, "OPNOTICE"); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Enables or disables the \002op-notice\002 option for a channel.\n" + "When \002op-notice\002 is set, %s will send a notice to the\n" + "channel whenever the \002OP\002 or \002DEOP\002 commands are used for a user\n" + "in the channel."), this->name.c_str(), source.owner->nick.c_str()); + return true; + } +}; + +class CommandCSSASetOpNotice : public CommandCSSetOpNotice +{ + public: + CommandCSSASetOpNotice(Module *creator) : CommandCSSetOpNotice(creator, "chanserv/saset/opnotice") + { + } +}; + +class CSSetOpNotice : public Module +{ + CommandCSSetOpNotice commandcssetopnotice; + CommandCSSASetOpNotice commandcssasetopnotice; + + public: + CSSetOpNotice(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandcssetopnotice(this), commandcssasetopnotice(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandcssetopnotice); + ModuleManager::RegisterService(&commandcssasetopnotice); + } +}; + +MODULE_INIT(CSSetOpNotice) diff --git a/modules/commands/cs_set_peace.cpp b/modules/commands/cs_set_peace.cpp new file mode 100644 index 000000000..3a045fc6d --- /dev/null +++ b/modules/commands/cs_set_peace.cpp @@ -0,0 +1,93 @@ +/* ChanServ core 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 "module.h" + +class CommandCSSetPeace : public Command +{ + public: + CommandCSSetPeace(Module *creator, const Anope::string &cname = "chanserv/set/peace") : Command(creator, cname, 2, 2) + { + this->SetDesc(_("Regulate the use of critical commands")); + this->SetSyntax(_("\037channel\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + ChannelInfo *ci = cs_findchan(params[0]); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + if (source.permission.empty() && !ci->HasPriv(u, CA_SET)) + { + source.Reply(ACCESS_DENIED); + return; + } + + if (params[1].equals_ci("ON")) + { + ci->SetFlag(CI_PEACE); + source.Reply(_("Peace option for %s is now \002on\002."), ci->name.c_str()); + } + else if (params[1].equals_ci("OFF")) + { + ci->UnsetFlag(CI_PEACE); + source.Reply(_("Peace option for %s is now \002off\002."), ci->name.c_str()); + } + else + this->OnSyntaxError(source, "PEACE"); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Enables or disables the \002peace\002 option for a channel.\n" + "When \002peace\002 is set, a user won't be able to kick,\n" + "ban or remove a channel status of a user that has\n" + "a level superior or equal to his via %s commands."), source.owner->nick.c_str()); + return true; + } +}; + +class CommandCSSASetPeace : public CommandCSSetPeace +{ + public: + CommandCSSASetPeace(Module *creator) : CommandCSSetPeace(creator, "chanserv/saset/peace") + { + } +}; + +class CSSetPeace : public Module +{ + CommandCSSetPeace commandcssetpeace; + CommandCSSASetPeace commandcssasetpeace; + + public: + CSSetPeace(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandcssetpeace(this), commandcssasetpeace(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandcssetpeace); + ModuleManager::RegisterService(&commandcssasetpeace); + } +}; + +MODULE_INIT(CSSetPeace) diff --git a/modules/commands/cs_set_persist.cpp b/modules/commands/cs_set_persist.cpp new file mode 100644 index 000000000..3cc9564c4 --- /dev/null +++ b/modules/commands/cs_set_persist.cpp @@ -0,0 +1,180 @@ +/* ChanServ core 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 "module.h" + +class CommandCSSetPersist : public Command +{ + public: + CommandCSSetPersist(Module *creator, const Anope::string &cname = "chanserv/set/persist") : Command(creator, cname, 2, 2) + { + this->SetDesc(_("Set the channel as permanent")); + this->SetSyntax(_("\037channel\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + ChannelInfo *ci = cs_findchan(params[0]); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + if (source.permission.empty() && !ci->HasPriv(u, CA_SET)) + { + source.Reply(ACCESS_DENIED); + return; + } + + ChannelMode *cm = ModeManager::FindChannelModeByName(CMODE_PERM); + + if (params[1].equals_ci("ON")) + { + if (!ci->HasFlag(CI_PERSIST)) + { + ci->SetFlag(CI_PERSIST); + if (ci->c) + ci->c->SetFlag(CH_PERSIST); + + /* Channel doesn't exist, create it */ + if (!ci->c) + { + Channel *c = new Channel(ci->name); + if (ci->bi) + ci->bi->Join(c); + } + + /* No botserv bot, no channel mode */ + /* Give them ChanServ + * Yes, this works fine with no Config->s_BotServ + */ + if (!ci->bi && !cm) + { + BotInfo *bi = findbot(Config->ChanServ); + if (!bi) + { + source.Reply(_("ChanServ is required to enable persist on this network.")); + return; + } + bi->Assign(NULL, ci); + if (!ci->c->FindUser(bi)) + bi->Join(ci->c); + } + + /* Set the perm mode */ + if (cm) + { + if (ci->c && !ci->c->HasMode(CMODE_PERM)) + ci->c->SetMode(NULL, cm); + /* Add it to the channels mlock */ + ci->SetMLock(cm, true); + } + } + + source.Reply(_("Channel \002%s\002 is now persistant."), ci->name.c_str()); + } + else if (params[1].equals_ci("OFF")) + { + if (ci->HasFlag(CI_PERSIST)) + { + ci->UnsetFlag(CI_PERSIST); + if (ci->c) + ci->c->UnsetFlag(CH_PERSIST); + + /* Unset perm mode */ + if (cm) + { + if (ci->c && ci->c->HasMode(CMODE_PERM)) + ci->c->RemoveMode(NULL, cm); + /* Remove from mlock */ + ci->RemoveMLock(cm); + } + + /* No channel mode, no BotServ, but using ChanServ as the botserv bot + * which was assigned when persist was set on + */ + if (!cm && Config->BotServ.empty() && ci->bi) + { + BotInfo *bi = findbot(Config->ChanServ); + if (!bi) + { + source.Reply(_("ChanServ is required to enable persist on this network.")); + return; + } + /* Unassign bot */ + bi->UnAssign(NULL, ci); + } + } + + source.Reply(_("Channel \002%s\002 is no longer persistant."), ci->name.c_str()); + } + else + this->OnSyntaxError(source, "PERSIST"); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Enables or disables the persistant channel setting.\n" + "When persistant is set, the service bot will remain\n" + "in the channel when it has emptied of users.\n" + " \n" + "If your IRCd does not a permanent (persistant) channel\n" + "mode you must have a service bot in your channel to\n" + "set persist on, and it can not be unassigned while persist\n" + "is on.\n" + " \n" + "If this network does not have BotServ enabled and does\n" + "not have a permanent channel mode, ChanServ will\n" + "join your channel when you set persist on (and leave when\n" + "it has been set off).\n" + " \n" + "If your IRCd has a permanent (persistant) channel mode\n" + "and is is set or unset (for any reason, including MLOCK),\n" + "persist is automatically set and unset for the channel aswell.\n" + "Additionally, services will set or unset this mode when you\n" + "set persist on or off.")); + return true; + } +}; + +class CommandCSSASetPersist : public CommandCSSetPersist +{ + public: + CommandCSSASetPersist(Module *creator) : CommandCSSetPersist(creator, "chanserv/saset/persist") + { + } +}; + +class CSSetPersist : public Module +{ + CommandCSSetPersist commandcssetpeace; + CommandCSSASetPersist commandcssasetpeace; + + public: + CSSetPersist(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandcssetpeace(this), commandcssasetpeace(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandcssetpeace); + ModuleManager::RegisterService(&commandcssasetpeace); + } +}; + +MODULE_INIT(CSSetPersist) diff --git a/modules/commands/cs_set_private.cpp b/modules/commands/cs_set_private.cpp new file mode 100644 index 000000000..108b11569 --- /dev/null +++ b/modules/commands/cs_set_private.cpp @@ -0,0 +1,93 @@ +/* ChanServ core 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 "module.h" + +class CommandCSSetPrivate : public Command +{ + public: + CommandCSSetPrivate(Module *creator, const Anope::string &cname = "chanserv/set/private") : Command(creator, cname, 2, 2) + { + this->SetDesc(_("Hide channel from LIST command")); + this->SetSyntax(_("\037channel\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + ChannelInfo *ci = cs_findchan(params[0]); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + if (source.permission.empty() && !ci->HasPriv(u, CA_SET)) + { + source.Reply(ACCESS_DENIED); + return; + } + + if (params[1].equals_ci("ON")) + { + ci->SetFlag(CI_PRIVATE); + source.Reply(_("Private option for %s is now \002on\002."), ci->name.c_str()); + } + else if (params[1].equals_ci("OFF")) + { + ci->UnsetFlag(CI_PRIVATE); + source.Reply(_("Private option for %s is now \002off\002."), ci->name.c_str()); + } + else + this->OnSyntaxError(source, "PRIVATE"); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Enables or disables the \002private\002 option for a channel.\n" + "When \002private\002 is set, a \002%s%s LIST\002 will not\n" + "include the channel in any lists."), + Config->UseStrictPrivMsgString.c_str(), source.owner->nick.c_str()); + return true; + } +}; + +class CommandCSSASetPrivate : public CommandCSSetPrivate +{ + public: + CommandCSSASetPrivate(Module *creator) : CommandCSSetPrivate(creator, "chanserv/saset/private") + { + } +}; + +class CSSetPrivate : public Module +{ + CommandCSSetPrivate commandcssetprivate; + CommandCSSASetPrivate commandcssasetprivate; + + public: + CSSetPrivate(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandcssetprivate(this), commandcssasetprivate(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandcssetprivate); + ModuleManager::RegisterService(&commandcssasetprivate); + } +}; + +MODULE_INIT(CSSetPrivate) diff --git a/modules/commands/cs_set_restricted.cpp b/modules/commands/cs_set_restricted.cpp new file mode 100644 index 000000000..5d675d2e7 --- /dev/null +++ b/modules/commands/cs_set_restricted.cpp @@ -0,0 +1,91 @@ +/* ChanServ core 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 "module.h" + +class CommandCSSetRestricted : public Command +{ + public: + CommandCSSetRestricted(Module *creator, const Anope::string &cname = "chanserv/set/restricted") : Command(creator, cname, 2, 2) + { + this->SetDesc(_("Restrict access to the channel")); + this->SetSyntax(_("\037channel\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + ChannelInfo *ci = cs_findchan(params[0]); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + if (source.permission.empty() && !ci->HasPriv(u, CA_SET)) + { + source.Reply(ACCESS_DENIED); + return; + } + + if (params[1].equals_ci("ON")) + { + ci->SetFlag(CI_RESTRICTED); + source.Reply(_("Restricted access option for %s is now \002on\002."), ci->name.c_str()); + } + else if (params[1].equals_ci("OFF")) + { + ci->UnsetFlag(CI_RESTRICTED); + source.Reply(_("Restricted access option for %s is now \002off\002."), ci->name.c_str()); + } + else + this->OnSyntaxError(source, "RESTRICTED"); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Enables or disables the \002restricted access\002 option for a\n" + "channel. When \002restricted access\002 is set, users not on the access list will\n" + "instead be kicked and banned from the channel.")); + return true; + } +}; + +class CommandCSSASetRestricted : public CommandCSSetRestricted +{ + public: + CommandCSSASetRestricted(Module *creator) : CommandCSSetRestricted(creator, "chanserv/saset/restricted") + { + } +}; + +class CSSetRestricted : public Module +{ + CommandCSSetRestricted commandcssetrestricted; + CommandCSSASetRestricted commandcssasetrestricted; + + public: + CSSetRestricted(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandcssetrestricted(this), commandcssasetrestricted(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandcssetrestricted); + ModuleManager::RegisterService(&commandcssasetrestricted); + } +}; + +MODULE_INIT(CSSetRestricted) diff --git a/modules/commands/cs_set_secure.cpp b/modules/commands/cs_set_secure.cpp new file mode 100644 index 000000000..26e8c5768 --- /dev/null +++ b/modules/commands/cs_set_secure.cpp @@ -0,0 +1,94 @@ +/* ChanServ core 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 "module.h" + +class CommandCSSetSecure : public Command +{ + public: + CommandCSSetSecure(Module *creator, const Anope::string &cname = "chanserv/set/secure") : Command(creator, cname, 2, 2) + { + this->SetDesc(_("Activate security features")); + this->SetSyntax(_("\037channel\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + ChannelInfo *ci = cs_findchan(params[0]); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + if (source.permission.empty() && !ci->HasPriv(u, CA_SET)) + { + source.Reply(ACCESS_DENIED); + return; + } + + if (params[1].equals_ci("ON")) + { + ci->SetFlag(CI_SECURE); + source.Reply(_("Secure option for %s is now \002on\002."), ci->name.c_str()); + } + else if (params[1].equals_ci("OFF")) + { + ci->UnsetFlag(CI_SECURE); + source.Reply(_("Secure option for %s is now \002off\002."), ci->name.c_str()); + } + else + this->OnSyntaxError(source, "SECURE"); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Enables or disables security features for a\n" + "channel. When \002%s\002 is set, only users who have\n" + "registered their nicknames and IDENTIFY'd\n" + "with their password will be given access to the channel\n" + "as controlled by the access list."), this->name.c_str()); + return true; + } +}; + +class CommandCSSASetSecure : public CommandCSSetSecure +{ + public: + CommandCSSASetSecure(Module *creator) : CommandCSSetSecure(creator, "chanserv/saset/secure") + { + } +}; + +class CSSetSecure : public Module +{ + CommandCSSetSecure commandcssetsecure; + CommandCSSASetSecure commandcssasetsecure; + + public: + CSSetSecure(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandcssetsecure(this), commandcssasetsecure(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandcssetsecure); + ModuleManager::RegisterService(&commandcssasetsecure); + } +}; + +MODULE_INIT(CSSetSecure) diff --git a/modules/commands/cs_set_securefounder.cpp b/modules/commands/cs_set_securefounder.cpp new file mode 100644 index 000000000..7afc2e32d --- /dev/null +++ b/modules/commands/cs_set_securefounder.cpp @@ -0,0 +1,95 @@ +/* ChanServ core 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 "module.h" + +class CommandCSSetSecureFounder : public Command +{ + public: + CommandCSSetSecureFounder(Module *creator, const Anope::string &cname = "chanserv/set/securefounder") : Command(creator, cname, 2, 2) + { + this->SetDesc(_("Stricter control of channel founder status")); + this->SetSyntax(_("\037channel\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + ChannelInfo *ci = cs_findchan(params[0]); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + + if (source.permission.empty() && ci->HasFlag(CI_SECUREFOUNDER) ? !IsFounder(u, ci) : !ci->HasPriv(u, CA_FOUNDER)) + { + source.Reply(ACCESS_DENIED); + return; + } + + if (params[1].equals_ci("ON")) + { + ci->SetFlag(CI_SECUREFOUNDER); + source.Reply(_("Secure founder option for %s is now \002on\002."), ci->name.c_str()); + } + else if (params[1].equals_ci("OFF")) + { + ci->UnsetFlag(CI_SECUREFOUNDER); + source.Reply(_("Secure founder option for %s is now \002off\002."), ci->name.c_str()); + } + else + this->OnSyntaxError(source, "SECUREFOUNDER"); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Enables or disables the \002secure founder\002 option for a channel.\n" + "When \002secure founder\002 is set, only the real founder will be\n" + "able to drop the channel, change its password, its founder and its\n" + "successor, and not those who have founder level access through\n" + "the access/qop command.")); + return true; + } +}; + +class CommandCSSASetSecureFounder : public CommandCSSetSecureFounder +{ + public: + CommandCSSASetSecureFounder(Module *creator) : CommandCSSetSecureFounder(creator, "chanserv/saset/securefounder") + { + } +}; + +class CSSetSecureFounder : public Module +{ + CommandCSSetSecureFounder commandcssetsecurefounder; + CommandCSSASetSecureFounder commandcssasetsecurefounder; + + public: + CSSetSecureFounder(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandcssetsecurefounder(this), commandcssasetsecurefounder(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandcssetsecurefounder); + ModuleManager::RegisterService(&commandcssasetsecurefounder); + } +}; + +MODULE_INIT(CSSetSecureFounder) diff --git a/modules/commands/cs_set_secureops.cpp b/modules/commands/cs_set_secureops.cpp new file mode 100644 index 000000000..4c3c54b05 --- /dev/null +++ b/modules/commands/cs_set_secureops.cpp @@ -0,0 +1,92 @@ +/* ChanServ core 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 "module.h" + +class CommandCSSetSecureOps : public Command +{ + public: + CommandCSSetSecureOps(Module *creator, const Anope::string &cname = "chanserv/set/secureops") : Command(creator, cname, 2, 2) + { + this->SetDesc(_("Stricter control of chanop status")); + this->SetSyntax(_("\037channel\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + ChannelInfo *ci = cs_findchan(params[0]); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + if (source.permission.empty() && !ci->HasPriv(u, CA_SET)) + { + source.Reply(ACCESS_DENIED); + return; + } + + if (params[1].equals_ci("ON")) + { + ci->SetFlag(CI_SECUREOPS); + source.Reply(_("Secure ops option for %s is now \002on\002."), ci->name.c_str()); + } + else if (params[1].equals_ci("OFF")) + { + ci->UnsetFlag(CI_SECUREOPS); + source.Reply(_("Secure ops option for %s is now \002off\002."), ci->name.c_str()); + } + else + this->OnSyntaxError(source, "SECUREOPS"); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Enables or disables the \002secure ops\002 option for a channel.\n" + "When \002secure ops\002 is set, users who are not on the userlist\n" + "will not be allowed chanop status.")); + return true; + } +}; + +class CommandCSSASetSecureOps : public CommandCSSetSecureOps +{ + public: + CommandCSSASetSecureOps(Module *creator) : CommandCSSetSecureOps(creator, "chanserv/saset/secureops") + { + } +}; + +class CSSetSecureOps : public Module +{ + CommandCSSetSecureOps commandcssetsecureops; + CommandCSSASetSecureOps commandcssasetsecureops; + + public: + CSSetSecureOps(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandcssetsecureops(this), commandcssasetsecureops(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandcssetsecureops); + ModuleManager::RegisterService(&commandcssasetsecureops); + } +}; + +MODULE_INIT(CSSetSecureOps) diff --git a/modules/commands/cs_set_signkick.cpp b/modules/commands/cs_set_signkick.cpp new file mode 100644 index 000000000..0d421576d --- /dev/null +++ b/modules/commands/cs_set_signkick.cpp @@ -0,0 +1,104 @@ +/* ChanServ core 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 "module.h" + +class CommandCSSetSignKick : public Command +{ + public: + CommandCSSetSignKick(Module *creator, const Anope::string &cname = "chanserv/set/signkick") : Command(creator, cname, 2, 2) + { + this->SetDesc(_("Sign kicks that are done with KICK command")); + this->SetSyntax(_("\037channel\037 SIGNKICK {ON | LEVEL | OFF}")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + ChannelInfo *ci = cs_findchan(params[0]); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + if (source.permission.empty() && !ci->HasPriv(u, CA_SET)) + { + source.Reply(ACCESS_DENIED); + return; + } + + if (params[1].equals_ci("ON")) + { + ci->SetFlag(CI_SIGNKICK); + ci->UnsetFlag(CI_SIGNKICK_LEVEL); + source.Reply(_("Signed kick option for %s is now \002on\002."), ci->name.c_str()); + } + else if (params[1].equals_ci("LEVEL")) + { + ci->SetFlag(CI_SIGNKICK_LEVEL); + ci->UnsetFlag(CI_SIGNKICK); + source.Reply(_("Signed kick option for %s is now \002ON\002, but depends of the\n" + "level of the user that is using the command."), ci->name.c_str()); + } + else if (params[1].equals_ci("OFF")) + { + ci->UnsetFlag(CI_SIGNKICK); + ci->UnsetFlag(CI_SIGNKICK_LEVEL); + source.Reply(_("Signed kick option for %s is now \002off\002."), ci->name.c_str()); + } + else + this->OnSyntaxError(source, "SIGNKICK"); + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Enables or disables signed kicks for a\n" + "channel. When \002SIGNKICK\002 is set, kicks issued with\n" + "KICK command will have the nick that used the\n" + "command in their reason.\n" + " \n" + "If you use \002LEVEL\002, those who have a level that is superior \n" + "or equal to the SIGNKICK level on the channel won't have their \n" + "kicks signed.")); + return true; + } +}; + +class CommandCSSASetSignKick : public CommandCSSetSignKick +{ + public: + CommandCSSASetSignKick(Module *creator) : CommandCSSetSignKick(creator, "chanserv/saset/signkick") + { + } +}; + +class CSSetSignKick : public Module +{ + CommandCSSetSignKick commandcssetsignkick; + CommandCSSASetSignKick commandcssasetsignkick; + + public: + CSSetSignKick(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandcssetsignkick(this), commandcssasetsignkick(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandcssetsignkick); + ModuleManager::RegisterService(&commandcssasetsignkick); + } +}; + +MODULE_INIT(CSSetSignKick) diff --git a/modules/commands/cs_set_successor.cpp b/modules/commands/cs_set_successor.cpp new file mode 100644 index 000000000..f7ed8dba9 --- /dev/null +++ b/modules/commands/cs_set_successor.cpp @@ -0,0 +1,119 @@ +/* ChanServ core 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 "module.h" + +class CommandCSSetSuccessor : public Command +{ + public: + CommandCSSetSuccessor(Module *creator, const Anope::string &cname = "chanserv/set/successor") : Command(creator, cname, 1, 2) + { + this->SetDesc(_("Set the successor for a channel")); + this->SetSyntax(_("\037channel\037 \037nick\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + ChannelInfo *ci = cs_findchan(params[0]); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + if (source.permission.empty() && !ci->HasPriv(u, CA_SET)) + { + source.Reply(ACCESS_DENIED); + return; + } + + if (source.permission.empty() && ci->HasFlag(CI_SECUREFOUNDER) ? !IsFounder(u, ci) : !ci->HasPriv(u, CA_FOUNDER)) + { + source.Reply(ACCESS_DENIED); + return; + } + + NickCore *nc; + + if (params.size() > 1) + { + NickAlias *na = findnick(params[1]); + + if (!na) + { + source.Reply(NICK_X_NOT_REGISTERED, params[1].c_str()); + return; + } + if (na->nc == ci->GetFounder()) + { + source.Reply(_("%s cannot be the successor on channel %s they are the founder."), na->nick.c_str(), ci->name.c_str()); + return; + } + nc = na->nc; + } + else + nc = NULL; + + Log(!source.permission.empty() ? LOG_ADMIN : LOG_COMMAND, u, this, ci) << "to change the successor from " << (ci->successor ? ci->successor->display : "none") << " to " << (nc ? nc->display : "none"); + + ci->successor = nc; + + if (nc) + source.Reply(_("Successor for %s changed to \002%s\002."), ci->name.c_str(), nc->display.c_str()); + else + source.Reply(_("Successor for \002%s\002 unset."), ci->name.c_str()); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Changes the successor of a channel. If the founder's\n" + "nickname expires or is dropped while the channel is still\n" + "registered, the successor will become the new founder of the\n" + "channel. However, if the successor already has too many\n" + "channels registered (%d), the channel will be dropped\n" + "instead, just as if no successor had been set. The new\n" + "nickname must be a registered one."), Config->CSMaxReg); + return true; + } +}; + +class CommandCSSASetSuccessor : public CommandCSSetSuccessor +{ + public: + CommandCSSASetSuccessor(Module *creator) : CommandCSSetSuccessor(creator, "chanserv/saset/successor") + { + } +}; + +class CSSetSuccessor : public Module +{ + CommandCSSetSuccessor commandcssetsuccessor; + CommandCSSASetSuccessor commandcssasetsuccessor; + + public: + CSSetSuccessor(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandcssetsuccessor(this), commandcssasetsuccessor(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandcssetsuccessor); + ModuleManager::RegisterService(&commandcssasetsuccessor); + } +}; + +MODULE_INIT(CSSetSuccessor) diff --git a/modules/commands/cs_set_topiclock.cpp b/modules/commands/cs_set_topiclock.cpp new file mode 100644 index 000000000..e670385ba --- /dev/null +++ b/modules/commands/cs_set_topiclock.cpp @@ -0,0 +1,92 @@ +/* ChanServ core 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 "module.h" + +class CommandCSSetTopicLock : public Command +{ + public: + CommandCSSetTopicLock(Module *creator, const Anope::string &cname = "chanserv/set/topiclock") : Command(creator, cname, 2, 2) + { + this->SetDesc(_("Topic can only be changed with TOPIC")); + this->SetSyntax(_("\037channel\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + ChannelInfo *ci = cs_findchan(params[0]); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + if (source.permission.empty() && !ci->HasPriv(u, CA_SET)) + { + source.Reply(ACCESS_DENIED); + return; + } + + if (params[1].equals_ci("ON")) + { + ci->SetFlag(CI_TOPICLOCK); + source.Reply(_("Topic lock option for %s is now \002on\002."), ci->name.c_str()); + } + else if (params[1].equals_ci("OFF")) + { + ci->UnsetFlag(CI_TOPICLOCK); + source.Reply(_("Topic lock option for %s is now \002off\002."), ci->name.c_str()); + } + else + this->OnSyntaxError(source, "TOPICLOCK"); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Enables or disables the \002topic lock\002 option for a channel.\n" + "When \002topic lock\002 is set, the channel topic will be unchangable\n" + " except via the \002TOPIC\002 command.")); + return true; + } +}; + +class CommandCSSASetTopicLock : public CommandCSSetTopicLock +{ + public: + CommandCSSASetTopicLock(Module *creator) : CommandCSSetTopicLock(creator, "chanserv/saset/topiclock") + { + } +}; + +class CSSetTopicLock : public Module +{ + CommandCSSetTopicLock commandcssettopiclock; + CommandCSSASetTopicLock commandcssasettopiclock; + + public: + CSSetTopicLock(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandcssettopiclock(this), commandcssasettopiclock(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandcssettopiclock); + ModuleManager::RegisterService(&commandcssasettopiclock); + } +}; + +MODULE_INIT(CSSetTopicLock) diff --git a/modules/commands/cs_suspend.cpp b/modules/commands/cs_suspend.cpp new file mode 100644 index 000000000..d59ffb44b --- /dev/null +++ b/modules/commands/cs_suspend.cpp @@ -0,0 +1,158 @@ +/* ChanServ core 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 "module.h" + +class CommandCSSuspend : public Command +{ + public: + CommandCSSuspend(Module *creator) : Command(creator, "chanserv/suspend", 1, 2) + { + this->SetDesc(_("Prevent a channel from being used preserving channel data and settings")); + this->SetSyntax(_("\037channel\037 [\037reason\037]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &reason = params.size() > 1 ? params[1] : ""; + + User *u = source.u; + + if (Config->ForceForbidReason && reason.empty()) + { + this->OnSyntaxError(source, ""); + return; + } + + if (readonly) + source.Reply(READ_ONLY_MODE); + + ChannelInfo *ci = cs_findchan(params[0]); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + ci->SetFlag(CI_SUSPENDED); + ci->Extend("suspend_by", new ExtensibleItemRegular<Anope::string>(u->nick)); + if (!reason.empty()) + ci->Extend("suspend_reason", new ExtensibleItemRegular<Anope::string>(u->nick)); + + if (ci->c) + { + for (CUserList::iterator it = ci->c->users.begin(), it_end = ci->c->users.end(); it != it_end; ) + { + UserContainer *uc = *it++; + + if (uc->user->HasMode(UMODE_OPER)) + continue; + + ci->c->Kick(NULL, uc->user, "%s", !reason.empty() ? reason.c_str() : translate(uc->user, _("This channel has been suspended."))); + } + } + + Log(LOG_ADMIN, u, this, ci) << (!reason.empty() ? reason : "No reason"); + source.Reply(_("Channel \002%s\002 is now suspended."), ci->name.c_str()); + + FOREACH_MOD(I_OnChanSuspend, OnChanSuspend(ci)); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Disallows anyone from using the given channel.\n" + "May be cancelled by using the UNSUSPEND\n" + "command to preserve all previous channel data/settings.\n" + " \n" + "Reason may be required on certain networks.")); + return true; + } +}; + +class CommandCSUnSuspend : public Command +{ + public: + CommandCSUnSuspend(Module *creator) : Command(creator, "chanserv/unsuspend", 1, 1) + { + this->SetDesc(_("Releases a suspended channel")); + this->SetSyntax(_("\037channel\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + + if (readonly) + source.Reply(READ_ONLY_MODE); + + ChannelInfo *ci = cs_findchan(params[0]); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + /* Only UNSUSPEND already suspended channels */ + if (!ci->HasFlag(CI_SUSPENDED)) + { + source.Reply(_("Couldn't release channel \002%s\002!"), ci->name.c_str()); + return; + } + + Anope::string by, reason; + ci->GetExtRegular("suspend_by", by); + ci->GetExtRegular("suspend_reason", reason); + Log(LOG_ADMIN, u, this, ci) << " which was suspended by " << by << " for: " << (!reason.empty() ? reason : "No reason"); + + ci->UnsetFlag(CI_SUSPENDED); + ci->Shrink("suspend_by"); + ci->Shrink("suspend_reason"); + + source.Reply(_("Channel \002%s\002 is now released."), ci->name.c_str()); + + FOREACH_MOD(I_OnChanUnsuspend, OnChanUnsuspend(ci)); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Releases a suspended channel. All data and settings\n" + "are preserved from before the suspension.")); + return true; + } +}; + +class CSSuspend : public Module +{ + CommandCSSuspend commandcssuspend; + CommandCSUnSuspend commandcsunsuspend; + + public: + CSSuspend(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandcssuspend(this), commandcsunsuspend(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandcssuspend); + ModuleManager::RegisterService(&commandcsunsuspend); + } +}; + +MODULE_INIT(CSSuspend) diff --git a/modules/commands/cs_sync.cpp b/modules/commands/cs_sync.cpp new file mode 100644 index 000000000..2937cc4fc --- /dev/null +++ b/modules/commands/cs_sync.cpp @@ -0,0 +1,63 @@ +/* + * + * (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 "module.h" + +class CommandCSSync : public Command +{ + public: + CommandCSSync(Module *creator) : Command(creator, "chanserv/sync", 1, 1) + { + this->SetDesc(_("Sync users channel modes")); + this->SetSyntax(_("\037channel\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + ChannelInfo *ci = cs_findchan(params[0]); + + if (ci == NULL) + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + else if (ci->c == NULL) + source.Reply(CHAN_X_NOT_IN_USE, params[0].c_str()); + else + { + for (CUserList::iterator it = ci->c->users.begin(), it_end = ci->c->users.end(); it != it_end; ++it) + chan_set_correct_modes((*it)->user, ci->c, 1); + + source.Reply(_("All user modes on \2%s\2 have been synced."), ci->name.c_str()); + } + } + + bool OnHelp(CommandSource &source, const Anope::string ¶ms) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Syncs all modes set on users on the channel with the modes\n" + "they should have based on their access.")); + return true; + } +}; + +class CSSync : public Module +{ + CommandCSSync commandcssync; + public: + CSSync(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, SUPPORTED), + commandcssync(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandcssync); + } +}; + +MODULE_INIT(CSSync) diff --git a/modules/commands/cs_tban.cpp b/modules/commands/cs_tban.cpp new file mode 100644 index 000000000..404279bef --- /dev/null +++ b/modules/commands/cs_tban.cpp @@ -0,0 +1,116 @@ +/* cs_tban.c - Bans the user for a given length of time + * + * (C) 2003-2011 Anope Team + * Contact us at team@anope.org + * + * Based on the original module by Rob <rob@anope.org> + * Included in the Anope module pack since Anope 1.7.8 + * Anope Coder: Rob <rob@anope.org> + * + * Please read COPYING and README for further details. + * + * Send bug reports to the Anope Coder instead of the module + * author, because any changes since the inclusion into anope + * are not supported by the original author. + */ +/*************************************************************************/ + +#include "module.h" + +static Module *me; + +class TempBan : public CallBack +{ + private: + dynamic_reference<Channel> chan; + Anope::string mask; + + public: + TempBan(time_t seconds, Channel *c, const Anope::string &banmask) : CallBack(me, seconds), chan(c), mask(banmask) { } + + void Tick(time_t ctime) + { + if (chan && chan->ci) + chan->RemoveMode(NULL, CMODE_BAN, mask); + } +}; + +static bool CanBanUser(CommandSource &source, Channel *c, User *u2) +{ + User *u = source.u; + ChannelInfo *ci = c->ci; + bool ok = false; + if (!ci->HasPriv(u, CA_BAN)) + source.Reply(ACCESS_DENIED); + else if (matches_list(c, u2, CMODE_EXCEPT)) + source.Reply(CHAN_EXCEPTED, u2->nick.c_str(), ci->name.c_str()); + else if (u2->IsProtected()) + source.Reply(ACCESS_DENIED); + else + ok = true; + + return ok; +} + +class CommandCSTBan : public Command +{ + public: + CommandCSTBan(Module *m) : Command(m, "TBAN", 3, 3) + { + this->SetDesc(_("Bans the user for a given length of time")); + this->SetSyntax(_("\037channel\037 \037nick\037 \037time\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + Channel *c = findchan(params[0]); + + const Anope::string &nick = params[1]; + const Anope::string &time = params[2]; + + User *u2; + if (!c) + source.Reply(CHAN_X_NOT_IN_USE, params[0].c_str()); + else if (!(u2 = finduser(nick))) + source.Reply(NICK_X_NOT_IN_USE, nick.c_str()); + else + if (CanBanUser(source, c, u2)) + { + Anope::string mask; + get_idealban(c->ci, u2, mask); + c->SetMode(NULL, CMODE_BAN, mask); + new TempBan(dotime(time), c, mask); + source.Reply(_("%s banned from %s, will auto-expire in %s"), mask.c_str(), c->name.c_str(), time.c_str()); + } + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->OnSyntaxError(source, ""); + source.Reply(" "); + source.Reply(_("Bans the given user from a channel for a specified length of\n" + "time. If the ban is removed before by hand, it will NOT be replaced.")); + + return true; + } +}; + +class CSTBan : public Module +{ + CommandCSTBan commandcstban; + + public: + CSTBan(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, SUPPORTED), + commandcstban(this) + { + this->SetAuthor("Anope"); + + me = this; + + ModuleManager::RegisterService(&commandcstban); + } +}; + +MODULE_INIT(CSTBan) diff --git a/modules/commands/cs_topic.cpp b/modules/commands/cs_topic.cpp new file mode 100644 index 000000000..1b5de11c3 --- /dev/null +++ b/modules/commands/cs_topic.cpp @@ -0,0 +1,84 @@ +/* ChanServ core 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 "module.h" + +class CommandCSTopic : public Command +{ + public: + CommandCSTopic(Module *creator) : Command(creator, "chanserv/topic", 1, 2) + { + this->SetDesc(_("Manipulate the topic of the specified channel")); + this->SetSyntax(_("\037channel\037 [\037topic\037]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &topic = params.size() > 1 ? params[1] : ""; + + User *u = source.u; + + ChannelInfo *ci = cs_findchan(params[0]); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + if (!ci->c) + source.Reply(CHAN_X_NOT_IN_USE, ci->name.c_str()); + else if (!ci->HasPriv(u, CA_TOPIC) && !u->HasCommand("chanserv/chanserv/topic")) + source.Reply(ACCESS_DENIED); + else + { + bool has_topiclock = ci->HasFlag(CI_TOPICLOCK); + ci->UnsetFlag(CI_TOPICLOCK); + ci->c->ChangeTopic(u->nick, topic, Anope::CurTime); + if (has_topiclock) + ci->SetFlag(CI_TOPICLOCK); + + bool override = !ci->HasPriv(u, CA_TOPIC); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "to change the topic to " << (!topic.empty() ? topic : "No topic"); + } + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Causes %s to set the channel topic to the one\n" + "specified. If \002topic\002 is not given, then an empty topic\n" + "is set. This command is most useful in conjunction\n" + "with topic lock.\n" + "By default, limited to those with founder access on the\n" + "channel."), source.owner->nick.c_str()); + return true; + } +}; + +class CSTopic : public Module +{ + CommandCSTopic commandcstopic; + + public: + CSTopic(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandcstopic(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandcstopic); + } +}; + +MODULE_INIT(CSTopic) diff --git a/modules/commands/cs_unban.cpp b/modules/commands/cs_unban.cpp new file mode 100644 index 000000000..cc3c68e91 --- /dev/null +++ b/modules/commands/cs_unban.cpp @@ -0,0 +1,94 @@ +/* ChanServ core 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 "module.h" + +class CommandCSUnban : public Command +{ + public: + CommandCSUnban(Module *creator) : Command(creator, "chanserv/unban", 1, 2) + { + this->SetDesc(_("Remove all bans preventing a user from entering a channel")); + this->SetSyntax(_("\037channel\037 [\037nick\037]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + + ChannelInfo *ci = cs_findchan(params[0]); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + if (ci->c == NULL) + { + source.Reply(CHAN_X_NOT_IN_USE, ci->name.c_str()); + return; + } + + if (!ci->HasPriv(u, CA_UNBAN)) + { + source.Reply(ACCESS_DENIED); + return; + } + + User *u2 = u; + if (params.size() > 1) + u2 = finduser(params[1]); + + if (!u2) + { + source.Reply(NICK_X_NOT_IN_USE, params[1].c_str()); + return; + } + + common_unban(ci, u2, u == u2); + if (u2 == u) + source.Reply(_("You have been unbanned from \002%s\002."), ci->c->name.c_str()); + else + source.Reply(_("\002%s\002 has been unbanned from \002%s\002."), u2->nick.c_str(), ci->c->name.c_str()); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Tells %s to remove all bans preventing you or the given\n" + "user from entering the given channel. \n" + " \n" + "By default, limited to AOPs or those with level 5 and above\n" + "on the channel."), source.owner->nick.c_str()); + return true; + } +}; + +class CSUnban : public Module +{ + CommandCSUnban commandcsunban; + + public: + CSUnban(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandcsunban(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandcsunban); + } +}; + +MODULE_INIT(CSUnban) diff --git a/modules/commands/cs_xop.cpp b/modules/commands/cs_xop.cpp new file mode 100644 index 000000000..1b9348026 --- /dev/null +++ b/modules/commands/cs_xop.cpp @@ -0,0 +1,880 @@ +/* ChanServ core 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 "module.h" + +enum XOPType +{ + XOP_QOP, + XOP_SOP, + XOP_AOP, + XOP_HOP, + XOP_VOP, + XOP_UNKNOWN +}; + +static struct XOPAccess +{ + XOPType type; + Anope::string name; + ChannelAccess access[CA_SIZE]; +} xopAccess[] = { + { XOP_QOP, "QOP", + { + CA_SIGNKICK, + CA_SET, + CA_AUTOOWNER, + CA_OWNERME, + CA_PROTECT, + CA_INFO, + CA_SIZE + } + }, + { XOP_SOP, "SOP", + { + CA_AUTOPROTECT, + CA_AKICK, + CA_BADWORDS, + CA_ASSIGN, + CA_MEMO, + CA_ACCESS_CHANGE, + CA_PROTECTME, + CA_OPDEOP, + CA_SIZE + } + }, + { XOP_AOP, "AOP", + { + CA_TOPIC, + CA_MODE, + CA_GETKEY, + CA_INVITE, + CA_UNBAN, + CA_AUTOOP, + CA_OPDEOPME, + CA_HALFOP, + CA_SAY, + CA_NOKICK, + CA_SIZE + } + }, + { XOP_HOP, "HOP", + { + CA_AUTOHALFOP, + CA_HALFOPME, + CA_KICK, + CA_BAN, + CA_FANTASIA, + CA_SIZE + } + }, + { XOP_VOP, "VOP", + { + CA_AUTOVOICE, + CA_VOICEME, + CA_ACCESS_LIST, + CA_SIZE + } + }, + { XOP_UNKNOWN, "", { } + } +}; + +class XOPChanAccess : public ChanAccess +{ + public: + XOPType type; + + XOPChanAccess(AccessProvider *p) : ChanAccess(p) + { + } + + bool Matches(User *u, NickCore *nc) + { + if (u && (Anope::Match(u->nick, this->mask) || Anope::Match(u->GetMask(), this->mask))) + return true; + else if (nc && Anope::Match(nc->display, this->mask)) + return true; + return false; + } + + bool HasPriv(ChannelAccess priv) + { + for (int i = 0; xopAccess[i].type != XOP_UNKNOWN; ++i) + { + XOPAccess &x = xopAccess[i]; + + if (this->type > x.type) + continue; + + for (int j = 0; x.access[j] != CA_SIZE; ++j) + if (x.access[j] == priv) + return true; + } + + return false; + } + + Anope::string Serialize() + { + for (int i = 0; xopAccess[i].type != XOP_UNKNOWN; ++i) + { + XOPAccess &x = xopAccess[i]; + + if (this->type == x.type) + return x.name; + } + + return ""; + } + + void Unserialize(const Anope::string &data) + { + for (int i = 0; xopAccess[i].type != XOP_UNKNOWN; ++i) + { + XOPAccess &x = xopAccess[i]; + + if (data == x.name) + { + this->type = x.type; + return; + } + } + + this->type = XOP_UNKNOWN; + } + + static XOPType DetermineLevel(ChanAccess *access) + { + if (access->provider->name == "access/xop") + { + XOPChanAccess *xaccess = debug_cast<XOPChanAccess *>(access); + return xaccess->type; + } + else + { + int count[XOP_UNKNOWN]; + for (int i = 0; i < XOP_UNKNOWN; ++i) + count[i] = 0; + + for (int i = 0; xopAccess[i].type != XOP_UNKNOWN; ++i) + { + XOPAccess &x = xopAccess[i]; + + for (int j = 0; x.access[j] != CA_SIZE; ++j) + if (access->HasPriv(x.access[j])) + ++count[x.type]; + } + + XOPType max = XOP_UNKNOWN; + int maxn = 0; + for (int i = 0; i < XOP_UNKNOWN; ++i) + if (count[i] > maxn) + { + max = static_cast<XOPType>(i); + maxn = count[i]; + } + + return max; + } + } +}; + +class XOPAccessProvider : public AccessProvider +{ + public: + XOPAccessProvider(Module *o) : AccessProvider(o, "access/xop") + { + } + + ChanAccess *Create() + { + return new XOPChanAccess(this); + } +}; + +class XOPListCallback : public NumberList +{ + CommandSource &source; + ChannelInfo *ci; + XOPType type; + Command *c; + bool SentHeader; + public: + XOPListCallback(CommandSource &_source, ChannelInfo *_ci, const Anope::string &numlist, XOPType _type, Command *_c) : NumberList(numlist, false), source(_source), ci(_ci), type(_type), c(_c), SentHeader(false) + { + } + + void HandleNumber(unsigned Number) + { + if (!Number || Number > ci->GetAccessCount()) + return; + + ChanAccess *access = ci->GetAccess(Number - 1); + + if (this->type != XOPChanAccess::DetermineLevel(access)) + return; + + if (!SentHeader) + { + SentHeader = true; + source.Reply(_("%s list for %s:\n Num Nick"), source.command.c_str(), ci->name.c_str()); + } + + DoList(source, access, Number - 1, this->type); + } + + static void DoList(CommandSource &source, ChanAccess *access, unsigned index, XOPType type) + { + source.Reply(_(" %3d %s"), index, access->mask.c_str()); + } +}; + +class XOPDelCallback : public NumberList +{ + CommandSource &source; + ChannelInfo *ci; + Command *c; + unsigned Deleted; + Anope::string Nicks; + bool override; + XOPType type; + public: + XOPDelCallback(CommandSource &_source, ChannelInfo *_ci, Command *_c, bool _override, XOPType _type, const Anope::string &numlist) : NumberList(numlist, true), source(_source), ci(_ci), c(_c), Deleted(0), override(_override), type(_type) + { + } + + ~XOPDelCallback() + { + if (!Deleted) + source.Reply(_("No matching entries on %s %s list."), ci->name.c_str(), source.command.c_str()); + else + { + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source.u, c, ci) << "deleted access of users " << Nicks; + + if (Deleted == 1) + source.Reply(_("Deleted one entry from %s %s list."), ci->name.c_str(), source.command.c_str()); + else + source.Reply(_("Deleted %d entries from %s %s list."), Deleted, ci->name.c_str(), source.command.c_str()); + } + } + + void HandleNumber(unsigned Number) + { + if (!Number || Number > ci->GetAccessCount()) + return; + + ChanAccess *access = ci->GetAccess(Number - 1); + + if (this->type != XOPChanAccess::DetermineLevel(access)) + return; + + ++Deleted; + if (!Nicks.empty()) + Nicks += ", " + access->mask; + else + Nicks = access->mask; + + FOREACH_MOD(I_OnAccessDel, OnAccessDel(ci, source.u, access)); + + ci->EraseAccess(Number - 1); + } +}; + +class XOPBase : public Command +{ + private: + void DoAdd(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> ¶ms, XOPType level) + { + User *u = source.u; + + Anope::string mask = params.size() > 2 ? params[2] : ""; + + if (mask.empty()) + { + this->OnSyntaxError(source, "ADD"); + return; + } + + if (readonly) + { + source.Reply(_("Sorry, channel %s list modification is temporarily disabled."), source.command.c_str()); + return; + } + + AccessGroup access = ci->AccessFor(u); + ChanAccess *highest = access.Highest(); + int u_level = (highest ? XOPChanAccess::DetermineLevel(highest) : 0); + + if (((access.HasPriv(CA_FOUNDER) || level >= u_level) || !access.HasPriv(CA_ACCESS_CHANGE)) && !u->HasPriv("chanserv/access/modify")) + { + source.Reply(ACCESS_DENIED); + return; + } + + for (unsigned i = 0; i < ci->GetAccessCount(); ++i) + { + ChanAccess *a = ci->GetAccess(i); + + if (a->mask.equals_ci(mask)) + { + if (XOPChanAccess::DetermineLevel(a) >= u_level && !u->HasPriv("chanserv/access/modify")) + { + source.Reply(ACCESS_DENIED); + return; + } + + ci->EraseAccess(i); + break; + } + } + + if (ci->GetAccessCount() >= Config->CSAccessMax) + { + source.Reply(_("Sorry, you can only have %d %s entries on a channel."), Config->CSAccessMax, source.command.c_str()); + return; + } + + if (mask.find_first_of("!*@") == Anope::string::npos && findnick(mask) == NULL) + mask += "!*@*"; + + service_reference<AccessProvider> provider("access/xop"); + if (!provider) + return; + XOPChanAccess *acc = debug_cast<XOPChanAccess *>(provider->Create()); + acc->ci = ci; + acc->mask = mask; + acc->creator = u->nick; + acc->type = level; + acc->last_seen = 0; + acc->created = Anope::CurTime; + ci->AddAccess(acc); + + bool override = level >= u_level || !access.HasPriv(CA_ACCESS_CHANGE); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "ADD " << mask << " as level " << level; + + FOREACH_MOD(I_OnAccessAdd, OnAccessAdd(ci, u, acc)); + source.Reply(("\002%s\002 added to %s %s list."), acc->mask.c_str(), ci->name.c_str(), source.command.c_str()); + } + + void DoDel(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> ¶ms, XOPType level) + { + User *u = source.u; + + const Anope::string &mask = params.size() > 2 ? params[2] : ""; + + if (mask.empty()) + { + this->OnSyntaxError(source, "DEL"); + return; + } + + if (readonly) + { + source.Reply(_("Sorry, channel %s list modification is temporarily disabled."), source.command.c_str()); + return; + } + + if (!ci->GetAccessCount()) + { + source.Reply(_("%s %s list is empty."), ci->name.c_str(), source.command.c_str()); + return; + } + + AccessGroup access = ci->AccessFor(u); + ChanAccess *highest = access.Highest(); + bool override = false; + if (!mask.equals_ci(u->Account()->display) && !access.HasPriv(CA_ACCESS_CHANGE) && (!highest || level >= XOPChanAccess::DetermineLevel(highest))) + { + if (u->HasPriv("chanserv/access/modify")) + override = true; + else + { + source.Reply(ACCESS_DENIED); + return; + } + } + + /* Special case: is it a number/list? Only do search if it isn't. */ + if (isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos) + { + XOPDelCallback list(source, ci, this, override, level, mask); + list.Process(); + } + else + { + for (unsigned i = 0; i < ci->GetAccessCount(); ++i) + { + ChanAccess *a = ci->GetAccess(i); + + if (a->mask.equals_ci(mask) && XOPChanAccess::DetermineLevel(a) == level) + { + Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "DEL " << a->mask; + + source.Reply(_("\002%s\002 deleted from %s %s list."), a->mask.c_str(), ci->name.c_str(), source.command.c_str()); + + FOREACH_MOD(I_OnAccessDel, OnAccessDel(ci, u, a)); + ci->EraseAccess(a); + + return; + } + } + + source.Reply(_("\002%s\002 not found on %s %s list."), mask.c_str(), ci->name.c_str(), source.command.c_str()); + } + } + + void DoList(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> ¶ms, XOPType level) + { + User *u = source.u; + + const Anope::string &nick = params.size() > 2 ? params[2] : ""; + + AccessGroup access = ci->AccessFor(u); + bool override = false; + + if (!access.HasPriv(CA_ACCESS_LIST)) + { + if (u->HasCommand("chanserv/access/list")) + override = true; + else + { + source.Reply(ACCESS_DENIED); + return; + } + } + + Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci); + + if (!ci->GetAccessCount()) + { + source.Reply(_("%s %s list is empty."), ci->name.c_str(), source.command.c_str()); + return; + } + + if (!nick.empty() && nick.find_first_not_of("1234567890,-") == Anope::string::npos) + { + XOPListCallback list(source, ci, nick, level, this); + list.Process(); + } + else + { + bool SentHeader = false; + + for (unsigned i = 0, end = ci->GetAccessCount(); i < end; ++i) + { + ChanAccess *a = ci->GetAccess(i); + + if (XOPChanAccess::DetermineLevel(a) != level) + continue; + else if (!nick.empty() && !Anope::Match(a->mask, nick)) + continue; + + if (!SentHeader) + { + SentHeader = true; + source.Reply(_("%s list for %s:\n Num Nick"), source.command.c_str(), ci->name.c_str()); + } + + XOPListCallback::DoList(source, a, i + 1, level); + } + + if (!SentHeader) + source.Reply(_("No matching entries on %s %s list."), ci->name.c_str(), source.command.c_str()); + } + + return; + } + + void DoClear(CommandSource &source, ChannelInfo *ci, XOPType level) + { + User *u = source.u; + + if (readonly) + { + source.Reply(_("Sorry, channel %s list modification is temporarily disabled."), source.command.c_str()); + return; + } + + if (!ci->GetAccessCount()) + { + source.Reply(_("%s %s list is empty."), ci->name.c_str(), source.command.c_str()); + return; + } + + if (!ci->HasPriv(u, CA_FOUNDER) && !u->HasPriv("chanserv/access/modify")) + { + source.Reply(ACCESS_DENIED); + return; + } + + bool override = !ci->HasPriv(u, CA_FOUNDER); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "CLEAR level " << level; + + for (unsigned i = ci->GetAccessCount(); i > 0; --i) + { + ChanAccess *access = ci->GetAccess(i - 1); + if (XOPChanAccess::DetermineLevel(access) == level) + ci->EraseAccess(i - 1); + } + + FOREACH_MOD(I_OnAccessClear, OnAccessClear(ci, u)); + + source.Reply(_("Channel %s %s list has been cleared."), ci->name.c_str(), source.command.c_str()); + + return; + } + + protected: + void DoXop(CommandSource &source, const std::vector<Anope::string> ¶ms, XOPType level) + { + ChannelInfo *ci = cs_findchan(params[0]); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + const Anope::string &cmd = params[1]; + + if (cmd.equals_ci("ADD")) + return this->DoAdd(source, ci, params, level); + else if (cmd.equals_ci("DEL")) + return this->DoDel(source, ci, params, level); + else if (cmd.equals_ci("LIST")) + return this->DoList(source, ci, params, level); + else if (cmd.equals_ci("CLEAR")) + return this->DoClear(source, ci, level); + else + this->OnSyntaxError(source, ""); + + return; + } + public: + XOPBase(Module *modname, const Anope::string &command) : Command(modname, command, 2, 4) + { + this->SetSyntax("\037channel\037 ADD \037mask\037"); + this->SetSyntax("\037channel\037 DEL {\037mask\037 | \037entry-num\037 | \037list\037}"); + this->SetSyntax("\037channel\037 LIST [\037mask\037 | \037list\037]"); + this->SetSyntax("\037channel\037 CLEAR"); + } + + virtual ~XOPBase() + { + } + + virtual void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) = 0; + + virtual bool OnHelp(CommandSource &source, const Anope::string &subcommand) = 0; +}; + +class CommandCSQOP : public XOPBase +{ + public: + CommandCSQOP(Module *creator) : XOPBase(creator, "chanserv/qop") + { + this->SetDesc(_("Modify the list of QOP users")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + return this->DoXop(source, params, XOP_QOP); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Maintains the \002QOP\002 (AutoOwner) \002list\002 for a channel. The QOP \n" + "list gives users the right to be auto-owner on your channel,\n" + "which gives them almost (or potentially, total) access.\n" + " \n" + "The \002QOP ADD\002 command adds the given nickname to the\n" + "QOP list.\n" + " \n" + "The \002QOP DEL\002 command removes the given nick from the\n" + "QOP list. If a list of entry numbers is given, those\n" + "entries are deleted. (See the example for LIST below.)\n" + " \n" + "The \002QOP LIST\002 command displays the QOP list. If\n" + "a wildcard mask is given, only those entries matching the\n" + "mask are displayed. If a list of entry numbers is given,\n" + "only those entries are shown; for example:\n" + " \002QOP #channel LIST 2-5,7-9\002\n" + " Lists QOP entries numbered 2 through 5 and\n" + " 7 through 9.\n" + " \n" + "The \002QOP CLEAR\002 command clears all entries of the\n" + "QOP list.\n")); + source.Reply(_(" \n" + "The \002QOP\002 commands are limited to\n" + "founders (unless SECUREOPS is off). However, any user on the\n" + "QOP list may use the \002QOP LIST\002 command.\n" + " \n")); + source.Reply(_("This command may have been disabled for your channel, and\n" + "in that case you need to use the access list. See \n" + "\002%s%s HELP ACCESS\002 for information about the access list,\n" + "and \002%s%s HELP SET XOP\002 to know how to toggle between \n" + "the access list and xOP list systems."), + Config->UseStrictPrivMsgString.c_str(), source.owner->nick.c_str(), + Config->UseStrictPrivMsgString.c_str(), source.owner->nick.c_str()); + return true; + } +}; + +class CommandCSAOP : public XOPBase +{ + public: + CommandCSAOP(Module *creator) : XOPBase(creator, "chanserv/aop") + { + this->SetDesc(_("Modify the list of AOP users")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + return this->DoXop(source, params, XOP_AOP); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Maintains the \002AOP\002 (AutoOP) \002list\002 for a channel. The AOP \n" + "list gives users the right to be auto-opped on your channel,\n" + "to unban or invite themselves if needed, to have their\n" + "greet message showed on join, and so on.\n" + " \n" + "The \002AOP ADD\002 command adds the given nickname to the\n" + "AOP list.\n" + " \n" + "The \002AOP DEL\002 command removes the given nick from the\n" + "AOP list. If a list of entry numbers is given, those\n" + "entries are deleted. (See the example for LIST below.)\n" + " \n" + "The \002AOP LIST\002 command displays the AOP list. If\n" + "a wildcard mask is given, only those entries matching the\n" + "mask are displayed. If a list of entry numbers is given,\n" + "only those entries are shown; for example:\n" + " \002AOP #channel LIST 2-5,7-9\002\n" + " Lists AOP entries numbered 2 through 5 and\n" + " 7 through 9.\n" + " \n" + "The \002AOP CLEAR\002 command clears all entries of the\n" + "AOP list.\n")); + source.Reply(_(" \n" + "The \002AOP ADD\002 and \002AOP DEL\002 commands are limited to\n" + "SOPs or above, while the \002AOP CLEAR\002 command can only\n" + "be used by the channel founder. However, any user on the\n" + "AOP list may use the \002AOP LIST\002 command.\n" + " \n")); + source.Reply(_("This command may have been disabled for your channel, and\n" + "in that case you need to use the access list. See \n" + "\002%s%s HELP ACCESS\002 for information about the access list,\n" + "and \002%s%s HELP SET XOP\002 to know how to toggle between \n" + "the access list and xOP list systems."), + Config->UseStrictPrivMsgString.c_str(), source.owner->nick.c_str(), + Config->UseStrictPrivMsgString.c_str(), source.owner->nick.c_str()); + return true; + } +}; + +class CommandCSHOP : public XOPBase +{ + public: + CommandCSHOP(Module *creator) : XOPBase(creator, "chanserv/hop") + { + this->SetDesc(_("Maintains the HOP (HalfOP) list for a channel")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + return this->DoXop(source, params, XOP_HOP); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Maintains the \002HOP\002 (HalfOP) \002list\002 for a channel. The HOP \n" + "list gives users the right to be auto-halfopped on your \n" + "channel.\n" + " \n" + "The \002HOP ADD\002 command adds the given nickname to the\n" + "HOP list.\n" + " \n" + "The \002HOP DEL\002 command removes the given nick from the\n" + "HOP list. If a list of entry numbers is given, those\n" + "entries are deleted. (See the example for LIST below.)\n" + " \n" + "The \002HOP LIST\002 command displays the HOP list. If\n" + "a wildcard mask is given, only those entries matching the\n" + "mask are displayed. If a list of entry numbers is given,\n" + "only those entries are shown; for example:\n" + " \002HOP #channel LIST 2-5,7-9\002\n" + " Lists HOP entries numbered 2 through 5 and\n" + " 7 through 9.\n" + " \n" + "The \002HOP CLEAR\002 command clears all entries of the\n" + "HOP list.\n")); + source.Reply(_(" \n" + "The \002HOP ADD\002, \002HOP DEL\002 and \002HOP LIST\002 commands are \n" + "limited to AOPs or above, while the \002HOP CLEAR\002 command \n" + "can only be used by the channel founder.\n" + " \n")); + source.Reply(_("This command may have been disabled for your channel, and\n" + "in that case you need to use the access list. See \n" + "\002%s%s HELP ACCESS\002 for information about the access list,\n" + "and \002%s%s HELP SET XOP\002 to know how to toggle between \n" + "the access list and xOP list systems."), + Config->UseStrictPrivMsgString.c_str(), source.owner->nick.c_str(), + Config->UseStrictPrivMsgString.c_str(), source.owner->nick.c_str()); + return true; + } +}; + +class CommandCSSOP : public XOPBase +{ + public: + CommandCSSOP(Module *creator) : XOPBase(creator, "chanserv/sop") + { + this->SetDesc(_("Modify the list of SOP users")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + return this->DoXop(source, params, XOP_SOP); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Maintains the \002SOP\002 (SuperOP) \002list\002 for a channel. The SOP \n" + "list gives users all rights given by the AOP list, and adds\n" + "those needed to use the AutoKick and the BadWords lists, \n" + "to send and read channel memos, and so on.\n" + " \n" + "The \002SOP ADD\002 command adds the given nickname to the\n" + "SOP list.\n" + " \n" + "The \002SOP DEL\002 command removes the given nick from the\n" + "SOP list. If a list of entry numbers is given, those\n" + "entries are deleted. (See the example for LIST below.)\n" + " \n" + "The \002SOP LIST\002 command displays the SOP list. If\n" + "a wildcard mask is given, only those entries matching the\n" + "mask are displayed. If a list of entry numbers is given,\n" + "only those entries are shown; for example:\n" + " \002SOP #channel LIST 2-5,7-9\002\n" + " Lists AOP entries numbered 2 through 5 and\n" + " 7 through 9.\n" + " \n" + "The \002SOP CLEAR\002 command clears all entries of the\n" + "SOP list.\n")); + source.Reply(_( + " \n" + "The \002SOP ADD\002, \002SOP DEL\002 and \002SOP CLEAR\002 commands are \n" + "limited to the channel founder. However, any user on the\n" + "AOP list may use the \002SOP LIST\002 command.\n" + " \n")); + source.Reply(_("This command may have been disabled for your channel, and\n" + "in that case you need to use the access list. See \n" + "\002%s%s HELP ACCESS\002 for information about the access list,\n" + "and \002%s%s HELP SET XOP\002 to know how to toggle between \n" + "the access list and xOP list systems."), + Config->UseStrictPrivMsgString.c_str(), source.owner->nick.c_str(), + Config->UseStrictPrivMsgString.c_str(), source.owner->nick.c_str()); + return true; + } +}; + +class CommandCSVOP : public XOPBase +{ + public: + CommandCSVOP(Module *creator) : XOPBase(creator, "chanserv/vop") + { + this->SetDesc(_("Maintains the VOP (VOicePeople) list for a channel")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + return this->DoXop(source, params, XOP_VOP); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Maintains the \002VOP\002 (VOicePeople) \002list\002 for a channel. \n" + "The VOP list allows users to be auto-voiced and to voice \n" + "themselves if they aren't.\n" + " \n" + "The \002VOP ADD\002 command adds the given nickname to the\n" + "VOP list.\n" + " \n" + "The \002VOP DEL\002 command removes the given nick from the\n" + "VOP list. If a list of entry numbers is given, those\n" + "entries are deleted. (See the example for LIST below.)\n" + " \n" + "The \002VOP LIST\002 command displays the VOP list. If\n" + "a wildcard mask is given, only those entries matching the\n" + "mask are displayed. If a list of entry numbers is given,\n" + "only those entries are shown; for example:\n" + " \002VOP #channel LIST 2-5,7-9\002\n" + " Lists VOP entries numbered 2 through 5 and\n" + " 7 through 9.\n" + " \n" + "The \002VOP CLEAR\002 command clears all entries of the\n" + "VOP list.\n")); + source.Reply(_( + " \n" + "The \002VOP ADD\002, \002VOP DEL\002 and \002VOP LIST\002 commands are \n" + "limited to AOPs or above, while the \002VOP CLEAR\002 command \n" + "can only be used by the channel founder.\n" + " \n")); + source.Reply(_("This command may have been disabled for your channel, and\n" + "in that case you need to use the access list. See \n" + "\002%s%s HELP ACCESS\002 for information about the access list,\n" + "and \002%s%s HELP SET XOP\002 to know how to toggle between \n" + "the access list and xOP list systems."), + Config->UseStrictPrivMsgString.c_str(), source.owner->nick.c_str(), + Config->UseStrictPrivMsgString.c_str(), source.owner->nick.c_str()); + return true; + } +}; + +class CSXOP : public Module +{ + XOPAccessProvider accessprovider; + CommandCSQOP commandcsqop; + CommandCSSOP commandcssop; + CommandCSAOP commandcsaop; + CommandCSHOP commandcshop; + CommandCSVOP commandcsvop; + + public: + CSXOP(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + accessprovider(this), commandcsqop(this), commandcssop(this), commandcsaop(this), commandcshop(this), commandcsvop(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&accessprovider); + ModuleManager::RegisterService(&commandcssop); + ModuleManager::RegisterService(&commandcsaop); + ModuleManager::RegisterService(&commandcsqop); + ModuleManager::RegisterService(&commandcsvop); + ModuleManager::RegisterService(&commandcshop); + } +}; + +MODULE_INIT(CSXOP) diff --git a/modules/commands/gl_global.cpp b/modules/commands/gl_global.cpp new file mode 100644 index 000000000..80c42e017 --- /dev/null +++ b/modules/commands/gl_global.cpp @@ -0,0 +1,64 @@ +/* Global core 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 "module.h" +#include "global.h" + +class CommandGLGlobal : public Command +{ + public: + CommandGLGlobal(Module *creator) : Command(creator, "global/global", 1, 1) + { + this->SetDesc(_("Send a message to all users")); + this->SetSyntax(_("\037message\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + const Anope::string &msg = params[0]; + + if (!global) + source.Reply("No global reference, is gl_main loaded?"); + else + { + Log(LOG_ADMIN, u, this); + global->SendGlobal(findbot(Config->Global), u->nick, msg); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Allows Administrators to send messages to all users on the \n" + "network. The message will be sent from the nick \002%s\002."), source.owner->nick.c_str()); + return true; + } +}; + +class GLGlobal : public Module +{ + CommandGLGlobal commandglglobal; + + public: + GLGlobal(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandglglobal(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandglglobal); + } +}; + +MODULE_INIT(GLGlobal) diff --git a/modules/commands/help.cpp b/modules/commands/help.cpp new file mode 100644 index 000000000..2a4f25b6c --- /dev/null +++ b/modules/commands/help.cpp @@ -0,0 +1,130 @@ +/* Core 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 "module.h" + +class CommandHelp : public Command +{ + public: + CommandHelp(Module *creator) : Command(creator, "generic/help", 0) + { + this->SetDesc(_("Displays this list and give information about commands")); + this->SetFlag(CFLAG_STRIP_CHANNEL); + this->SetFlag(CFLAG_ALLOW_UNREGISTERED); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + FOREACH_MOD(I_OnPreHelp, OnPreHelp(source, params)); + + User *u = source.u; + BotInfo *bi = source.owner; + + if (params.empty()) + { + for (BotInfo::command_map::iterator it = bi->commands.begin(), it_end = bi->commands.end(); it != it_end; ++it) + { + const Anope::string &c_name = it->first; + CommandInfo &info = it->second; + + // Smaller command exists + Anope::string cmd = myStrGetToken(c_name, ' ', 0); + if (cmd != it->first && bi->commands.count(cmd)) + continue; + + service_reference<Command> c(info.name); + if (!c) + continue; + if (!Config->HidePrivilegedCommands || info.permission.empty() || u->HasCommand(info.permission)) + { + source.command = c_name; + c->OnServHelp(source); + } + } + } + else + { + bool helped = false; + for (unsigned max = params.size(); max > 0; --max) + { + Anope::string full_command; + for (unsigned i = 0; i < max; ++i) + full_command += " " + params[i]; + full_command.erase(full_command.begin()); + + BotInfo::command_map::iterator it = bi->commands.find(full_command); + if (it == bi->commands.end()) + continue; + + CommandInfo &info = it->second; + + service_reference<Command> c(info.name); + if (!c) + continue; + + if (Config->HidePrivilegedCommands && !info.permission.empty() && !u->HasCommand(info.permission)) + continue; + + const Anope::string &subcommand = params.size() > max ? params[max] : ""; + source.command = full_command; + if (!c->OnHelp(source, subcommand)) + continue; + + helped = true; + + /* Inform the user what permission is required to use the command */ + if (!info.permission.empty()) + { + source.Reply(" "); + source.Reply(_("Access to this command requires the permission \002%s\002 to be present in your opertype."), info.permission.c_str()); + } + if (!c->HasFlag(CFLAG_ALLOW_UNREGISTERED) && !u->IsIdentified()) + { + if (info.permission.empty()) + source.Reply(" "); + source.Reply( _("You need to be identified to use this command.")); + } + /* User doesn't have the proper permission to use this command */ + else if (!info.permission.empty() && !u->HasCommand(info.permission)) + { + source.Reply(_("You cannot use this command.")); + } + + break; + } + + if (helped == false) + source.Reply(_("No help available for \002%s\002."), params[0].c_str()); + } + + FOREACH_MOD(I_OnPostHelp, OnPostHelp(source, params)); + + return; + } +}; + +class Help : public Module +{ + CommandHelp commandhelp; + + public: + Help(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandhelp(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandhelp); + } +}; + +MODULE_INIT(Help) diff --git a/modules/commands/hs_del.cpp b/modules/commands/hs_del.cpp new file mode 100644 index 000000000..541986aff --- /dev/null +++ b/modules/commands/hs_del.cpp @@ -0,0 +1,107 @@ +/* HostServ core 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 "module.h" + +class CommandHSDel : public Command +{ + public: + CommandHSDel(Module *creator) : Command(creator, "hostserv/del", 1, 1) + { + this->SetDesc(_("Delete the vhost of another user")); + this->SetSyntax(_("\037nick\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + const Anope::string &nick = params[0]; + NickAlias *na = findnick(nick); + if (na) + { + Log(LOG_ADMIN, u, this) << "for user " << na->nick; + FOREACH_MOD(I_OnDeleteVhost, OnDeleteVhost(na)); + na->hostinfo.RemoveVhost(); + source.Reply(_("Vhost for \002%s\002 removed."), nick.c_str()); + } + else + source.Reply(NICK_X_NOT_REGISTERED, nick.c_str()); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Deletes the vhost assigned to the given nick from the\n" + "database.")); + return true; + } +}; + +class CommandHSDelAll : public Command +{ + public: + CommandHSDelAll(Module *creator) : Command(creator, "hostserv/delall", 1, 1) + { + this->SetDesc(_("Delete the vhost for all nicks in a group")); + this->SetSyntax(_("\037nick\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &nick = params[0]; + User *u = source.u; + NickAlias *na = findnick(nick); + if (na) + { + FOREACH_MOD(I_OnDeleteVhost, OnDeleteVhost(na)); + NickCore *nc = na->nc; + for (std::list<NickAlias *>::iterator it = nc->aliases.begin(), it_end = nc->aliases.end(); it != it_end; ++it) + { + na = *it; + na->hostinfo.RemoveVhost(); + } + Log(LOG_ADMIN, u, this) << "for all nicks in group " << nc->display; + source.Reply(_("vhosts for group \002%s\002 have been removed."), nc->display.c_str()); + } + else + source.Reply(NICK_X_NOT_REGISTERED, nick.c_str()); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Deletes the vhost for all nicks in the same group as\n" + "that of the given nick.")); + return true; + } +}; + +class HSDel : public Module +{ + CommandHSDel commandhsdel; + CommandHSDelAll commandhsdelall; + + public: + HSDel(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandhsdel(this), commandhsdelall(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandhsdel); + ModuleManager::RegisterService(&commandhsdelall); + } +}; + +MODULE_INIT(HSDel) diff --git a/modules/commands/hs_group.cpp b/modules/commands/hs_group.cpp new file mode 100644 index 000000000..8d6fbd630 --- /dev/null +++ b/modules/commands/hs_group.cpp @@ -0,0 +1,80 @@ +/* HostServ core 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 "module.h" + +class CommandHSGroup : public Command +{ + void Sync(NickAlias *na) + { + if (!na || !na->hostinfo.HasVhost()) + return; + + for (std::list<NickAlias *>::iterator it = na->nc->aliases.begin(), it_end = na->nc->aliases.end(); it != it_end; ++it) + { + NickAlias *nick = *it; + nick->hostinfo.SetVhost(na->hostinfo.GetIdent(), na->hostinfo.GetHost(), na->hostinfo.GetCreator()); + } + } + + public: + CommandHSGroup(Module *creator) : Command(creator, "hostserv/group", 0, 0) + { + this->SetDesc(_("Syncs the vhost for all nicks in a group")); + this->SetSyntax(""); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + NickAlias *na = findnick(u->nick); + if (na && u->Account() == na->nc && na->hostinfo.HasVhost()) + { + this->Sync(na); + if (!na->hostinfo.GetIdent().empty()) + source.Reply(_("All vhost's in the group \002%s\002 have been set to \002%s\002@\002%s\002"), u->Account()->display.c_str(), na->hostinfo.GetIdent().c_str(), na->hostinfo.GetHost().c_str()); + else + source.Reply(_("All vhost's in the group \002%s\002 have been set to \002%s\002"), u->Account()->display.c_str(), na->hostinfo.GetHost().c_str()); + } + else + source.Reply(HOST_NOT_ASSIGNED); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("This command allows users to set the vhost of their\n" + "CURRENT nick to be the vhost for all nicks in the same\n" + "group.")); + return true; + } +}; + +class HSGroup : public Module +{ + CommandHSGroup commandhsgroup; + + public: + HSGroup(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandhsgroup(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandhsgroup); + } +}; + +MODULE_INIT(HSGroup) diff --git a/modules/commands/hs_list.cpp b/modules/commands/hs_list.cpp new file mode 100644 index 000000000..589eef3e1 --- /dev/null +++ b/modules/commands/hs_list.cpp @@ -0,0 +1,137 @@ +/* HostServ core 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 "module.h" + +class CommandHSList : public Command +{ + public: + CommandHSList(Module *creator) : Command(creator, "hostserv/list", 0, 1) + { + this->SetDesc(_("Displays one or more vhost entries.")); + this->SetSyntax(_("\002[<key>|<#X-Y>]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &key = !params.empty() ? params[0] : ""; + int from = 0, to = 0, counter = 1; + unsigned display_counter = 0; + + /** + * Do a check for a range here, then in the next loop + * we'll only display what has been requested.. + **/ + if (!key.empty() && key[0] == '#') + { + size_t tmp = key.find('-'); + if (tmp == Anope::string::npos || tmp == key.length() || tmp == 1) + { + source.Reply(LIST_INCORRECT_RANGE); + return; + } + for (unsigned i = 1, end = key.length(); i < end; ++i) + { + if (!isdigit(key[i]) && i != tmp) + { + source.Reply(LIST_INCORRECT_RANGE); + return; + } + try + { + from = convertTo<int>(key.substr(1, tmp - 1)); + to = convertTo<int>(key.substr(tmp + 1)); + } + catch (const ConvertException &) { } + } + } + + for (nickalias_map::const_iterator it = NickAliasList.begin(), it_end = NickAliasList.end(); it != it_end; ++it) + { + NickAlias *na = it->second; + + if (!na->hostinfo.HasVhost()) + continue; + + if (!key.empty() && key[0] != '#') + { + if ((Anope::Match(na->nick, key) || Anope::Match(na->hostinfo.GetHost(), key)) && display_counter < Config->NSListMax) + { + ++display_counter; + if (!na->hostinfo.GetIdent().empty()) + source.Reply(_("#%d Nick:\002%s\002, vhost:\002%s\002@\002%s\002 (%s - %s)"), counter, na->nick.c_str(), na->hostinfo.GetIdent().c_str(), na->hostinfo.GetHost().c_str(), na->hostinfo.GetCreator().c_str(), do_strftime(na->hostinfo.GetTime()).c_str()); + else + source.Reply(_("#%d Nick:\002%s\002, vhost:\002%s\002 (%s - %s)"), counter, na->nick.c_str(), na->hostinfo.GetHost().c_str(), na->hostinfo.GetCreator().c_str(), do_strftime(na->hostinfo.GetTime()).c_str()); + } + } + else + { + /** + * List the host if its in the display range, and not more + * than NSListMax records have been displayed... + **/ + if (((counter >= from && counter <= to) || (!from && !to)) && display_counter < Config->NSListMax) + { + ++display_counter; + if (!na->hostinfo.GetIdent().empty()) + source.Reply(_("#%d Nick:\002%s\002, vhost:\002%s\002@\002%s\002 (%s - %s)"), counter, na->nick.c_str(), na->hostinfo.GetIdent().c_str(), na->hostinfo.GetHost().c_str(), na->hostinfo.GetCreator().c_str(), do_strftime(na->hostinfo.GetTime()).c_str()); + else + source.Reply(_("#%d Nick:\002%s\002, vhost:\002%s\002 (%s - %s)"), counter, na->nick.c_str(), na->hostinfo.GetHost().c_str(), na->hostinfo.GetCreator().c_str(), do_strftime(na->hostinfo.GetTime()).c_str()); + } + } + ++counter; + } + if (!key.empty()) + source.Reply(_("Displayed records matching key \002%s\002 (Count: \002%d\002)"), key.c_str(), display_counter); + else + { + if (from) + source.Reply(_("Displayed records from \002%d\002 to \002%d\002"), from, to); + else + source.Reply(_("Displayed all records (Count: \002%d\002)"), display_counter); + } + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("This command lists registered vhosts to the operator\n" + "if a Key is specified, only entries whos nick or vhost match\n" + "the pattern given in <key> are displayed e.g. Rob* for all\n" + "entries beginning with \"Rob\"\n" + "If a #X-Y style is used, only entries between the range of X\n" + "and Y will be displayed, e.g. #1-3 will display the first 3\n" + "nick/vhost entries.\n" + "The list uses the value of NSListMax as a hard limit for the\n" + "number of items to display to a operator at any 1 time.")); + return true; + } +}; + +class HSList : public Module +{ + CommandHSList commandhslist; + + public: + HSList(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandhslist(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandhslist); + } +}; + +MODULE_INIT(HSList) diff --git a/modules/commands/hs_off.cpp b/modules/commands/hs_off.cpp new file mode 100644 index 000000000..a3f11c2bd --- /dev/null +++ b/modules/commands/hs_off.cpp @@ -0,0 +1,67 @@ +/* HostServ core 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 "module.h" + +class CommandHSOff : public Command +{ + public: + CommandHSOff(Module *creator) : Command(creator, "hostserv/off", 0, 0) + { + this->SetDesc(_("Deactivates your assigned vhost")); + this->SetSyntax(""); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + NickAlias *na = findnick(u->nick); + + if (!na || !na->hostinfo.HasVhost()) + source.Reply(HOST_NOT_ASSIGNED); + else + { + ircdproto->SendVhostDel(u); + Log(LOG_COMMAND, u, this) << "to disable their vhost"; + source.Reply(_("Your vhost was removed and the normal cloaking restored.")); + } + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Deactivates the vhost currently assigned to the nick in use.\n" + "When you use this command any user who performs a /whois\n" + "on you will see your real IP address.")); + return true; + } +}; + +class HSOff : public Module +{ + CommandHSOff commandhsoff; + + public: + HSOff(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandhsoff(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandhsoff); + } +}; + +MODULE_INIT(HSOff) diff --git a/modules/commands/hs_on.cpp b/modules/commands/hs_on.cpp new file mode 100644 index 000000000..0ba124ba1 --- /dev/null +++ b/modules/commands/hs_on.cpp @@ -0,0 +1,77 @@ +/* HostServ core 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 "module.h" + +class CommandHSOn : public Command +{ + public: + CommandHSOn(Module *creator) : Command(creator, "hostserv/on", 0, 0) + { + this->SetDesc(_("Activates your assigned vhost")); + this->SetSyntax(""); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + NickAlias *na = findnick(u->nick); + if (na && u->Account() == na->nc && na->hostinfo.HasVhost()) + { + if (!na->hostinfo.GetIdent().empty()) + source.Reply(_("Your vhost of \002%s\002@\002%s\002 is now activated."), na->hostinfo.GetIdent().c_str(), na->hostinfo.GetHost().c_str()); + else + source.Reply(_("Your vhost of \002%s\002 is now activated."), na->hostinfo.GetHost().c_str()); + Log(LOG_COMMAND, u, this) << "to enable their vhost of " << (!na->hostinfo.GetIdent().empty() ? na->hostinfo.GetIdent() + "@" : "") << na->hostinfo.GetHost(); + ircdproto->SendVhost(u, na->hostinfo.GetIdent(), na->hostinfo.GetHost()); + if (ircd->vhost) + u->vhost = na->hostinfo.GetHost(); + if (ircd->vident) + { + if (!na->hostinfo.GetIdent().empty()) + u->SetVIdent(na->hostinfo.GetIdent()); + } + u->UpdateHost(); + } + else + source.Reply(HOST_NOT_ASSIGNED); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Activates the vhost currently assigned to the nick in use.\n" + "When you use this command any user who performs a /whois\n" + "on you will see the vhost instead of your real IP address.")); + return true; + } +}; + +class HSOn : public Module +{ + CommandHSOn commandhson; + + public: + HSOn(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandhson(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandhson); + } +}; + +MODULE_INIT(HSOn) diff --git a/modules/commands/hs_request.cpp b/modules/commands/hs_request.cpp new file mode 100644 index 000000000..7686794f5 --- /dev/null +++ b/modules/commands/hs_request.cpp @@ -0,0 +1,424 @@ +/* hs_request.c - Add request and activate functionality to HostServ, + * + * + * (C) 2003-2011 Anope Team + * Contact us at team@anope.org + * + * Based on the original module by Rob <rob@anope.org> + * Included in the Anope module pack since Anope 1.7.11 + * Anope Coder: GeniusDex <geniusdex@anope.org> + * + * Please read COPYING and README for further details. + * + * Send bug reports to the Anope Coder instead of the module + * author, because any changes since the inclusion into anope + * are not supported by the original author. + */ + +#include "module.h" +#include "memoserv.h" + +static bool HSRequestMemoUser = false; +static bool HSRequestMemoOper = false; + +void my_add_host_request(const Anope::string &nick, const Anope::string &vIdent, const Anope::string &vhost, const Anope::string &creator, time_t tmp_time); +void req_send_memos(CommandSource &source, const Anope::string &vIdent, const Anope::string &vHost); + +struct HostRequest +{ + Anope::string ident; + Anope::string host; + time_t time; +}; + +typedef std::map<Anope::string, HostRequest *, std::less<ci::string> > RequestMap; +RequestMap Requests; + +class CommandHSRequest : public Command +{ + bool isvalidchar(char c) + { + if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '.' || c == '-') + return true; + return false; + } + + public: + CommandHSRequest(Module *creator) : Command(creator, "hostserv/request", 1, 1) + { + this->SetDesc(_("Request a vHost for your nick")); + this->SetSyntax(_("vhost")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + + Anope::string rawhostmask = params[0]; + + Anope::string user, host; + size_t a = rawhostmask.find('@'); + + if (a == Anope::string::npos) + host = rawhostmask; + else + { + user = rawhostmask.substr(0, a); + host = rawhostmask.substr(a + 1); + } + + if (host.empty()) + { + this->OnSyntaxError(source, ""); + return; + } + + if (!user.empty()) + { + if (user.length() > Config->UserLen) + { + source.Reply(HOST_SET_IDENTTOOLONG, Config->UserLen); + return; + } + else if (!ircd->vident) + { + source.Reply(HOST_NO_VIDENT); + return; + } + for (Anope::string::iterator s = user.begin(), s_end = user.end(); s != s_end; ++s) + if (!isvalidchar(*s)) + { + source.Reply(HOST_SET_IDENT_ERROR); + return; + } + } + + if (host.length() > Config->HostLen) + { + source.Reply(HOST_SET_TOOLONG, Config->HostLen); + return; + } + + if (!isValidHost(host, 3)) + { + source.Reply(HOST_SET_ERROR); + return; + } + + if (HSRequestMemoOper && Config->MSSendDelay > 0 && u && u->lastmemosend + Config->MSSendDelay > Anope::CurTime) + { + source.Reply(_("Please wait %d seconds before requesting a new vHost"), Config->MSSendDelay); + u->lastmemosend = Anope::CurTime; + return; + } + my_add_host_request(u->nick, user, host, u->nick, Anope::CurTime); + + source.Reply(_("Your vHost has been requested")); + req_send_memos(source, user, host); + Log(LOG_COMMAND, u, this, NULL) << "to request new vhost " << (!user.empty() ? user + "@" : "") << host; + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Request the given vHost to be actived for your nick by the\n" + "network administrators. Please be patient while your request\n" + "is being considered.")); + return true; + } +}; + +class CommandHSActivate : public Command +{ + public: + CommandHSActivate(Module *creator) : Command(creator, "hostserv/activate", 1, 1) + { + this->SetDesc(_("Approve the requested vHost of a user")); + this->SetSyntax(_("\037nick\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + + const Anope::string &nick = params[0]; + + NickAlias *na = findnick(nick); + if (na) + { + RequestMap::iterator it = Requests.find(na->nick); + if (it != Requests.end()) + { + na->hostinfo.SetVhost(it->second->ident, it->second->host, u->nick, it->second->time); + FOREACH_MOD(I_OnSetVhost, OnSetVhost(na)); + + if (HSRequestMemoUser && memoserv) + memoserv->Send(Config->HostServ, na->nick, _("[auto memo] Your requested vHost has been approved."), true); + + source.Reply(_("vHost for %s has been activated"), na->nick.c_str()); + Log(LOG_COMMAND, u, this, NULL) << "for " << na->nick << " for vhost " << (!it->second->ident.empty() ? it->second->ident + "@" : "") << it->second->host; + delete it->second; + Requests.erase(it); + } + else + source.Reply(_("No request for nick %s found."), nick.c_str()); + } + else + source.Reply(NICK_X_NOT_REGISTERED, nick.c_str()); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Activate the requested vHost for the given nick.")); + if (HSRequestMemoUser) + source.Reply(_("A memo informing the user will also be sent.")); + + return true; + } +}; + +class CommandHSReject : public Command +{ + public: + CommandHSReject(Module *creator) : Command(creator, "hostserv/reject", 1, 2) + { + this->SetDesc(_("Reject the requested vHost of a user")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + + const Anope::string &nick = params[0]; + const Anope::string &reason = params.size() > 1 ? params[1] : ""; + + RequestMap::iterator it = Requests.find(nick); + if (it != Requests.end()) + { + delete it->second; + Requests.erase(it); + + if (HSRequestMemoUser && memoserv) + { + Anope::string message; + if (!reason.empty()) + message = Anope::printf(_("[auto memo] Your requested vHost has been rejected. Reason: %s"), reason.c_str()); + else + message = _("[auto memo] Your requested vHost has been rejected."); + + memoserv->Send(Config->HostServ, nick, message, true); + } + + source.Reply(_("vHost for %s has been rejected"), nick.c_str()); + Log(LOG_COMMAND, u, this, NULL) << "to reject vhost for " << nick << " (" << (!reason.empty() ? reason : "") << ")"; + } + else + source.Reply(_("No request for nick %s found."), nick.c_str()); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Reject the requested vHost for the given nick.")); + if (HSRequestMemoUser) + source.Reply(_("A memo informing the user will also be sent.")); + + return true; + } +}; + +class HSListBase : public Command +{ + protected: + void DoList(CommandSource &source) + { + int counter = 1; + int from = 0, to = 0; + unsigned display_counter = 0; + + for (RequestMap::iterator it = Requests.begin(), it_end = Requests.end(); it != it_end; ++it) + { + HostRequest *hr = it->second; + if (((counter >= from && counter <= to) || (!from && !to)) && display_counter < Config->NSListMax) + { + ++display_counter; + if (!hr->ident.empty()) + source.Reply(_("#%d Nick:\002%s\002, vhost:\002%s\002@\002%s\002 (%s - %s)"), counter, it->first.c_str(), hr->ident.c_str(), hr->host.c_str(), it->first.c_str(), do_strftime(hr->time).c_str()); + else + source.Reply(_("#%d Nick:\002%s\002, vhost:\002%s\002 (%s - %s)"), counter, it->first.c_str(), hr->host.c_str(), it->first.c_str(), do_strftime(hr->time).c_str()); + } + ++counter; + } + source.Reply(_("Displayed all records (Count: \002%d\002)"), display_counter); + + return; + } + public: + HSListBase(Module *creator, const Anope::string &cmd, int min, int max) : Command(creator, cmd, min, max) + { + } +}; + +class CommandHSWaiting : public HSListBase +{ + public: + CommandHSWaiting(Module *creator) : HSListBase(creator, "hostserv/waiting", 0, 0) + { + this->SetDesc(_("Retrieves the vhost requests")); + this->SetSyntax(""); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + return this->DoList(source); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("This command retrieves the vhost requests")); + + return true; + } +}; + +class HSRequest : public Module +{ + CommandHSRequest commandhsrequest; + CommandHSActivate commandhsactive; + CommandHSReject commandhsreject; + CommandHSWaiting commandhswaiting; + + public: + HSRequest(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, SUPPORTED), + commandhsrequest(this), commandhsactive(this), commandhsreject(this), commandhswaiting(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandhsrequest); + ModuleManager::RegisterService(&commandhsactive); + ModuleManager::RegisterService(&commandhsreject); + ModuleManager::RegisterService(&commandhswaiting); + + Implementation i[] = { I_OnDelNick, I_OnDatabaseRead, I_OnDatabaseWrite, I_OnReload }; + ModuleManager::Attach(i, this, sizeof(i) / sizeof(Implementation)); + + this->OnReload(); + } + + ~HSRequest() + { + /* Clean up all open host requests */ + while (!Requests.empty()) + { + delete Requests.begin()->second; + Requests.erase(Requests.begin()); + } + } + + void OnDelNick(NickAlias *na) + { + RequestMap::iterator it = Requests.find(na->nick); + + if (it != Requests.end()) + { + delete it->second; + Requests.erase(it); + } + } + + EventReturn OnDatabaseRead(const std::vector<Anope::string> ¶ms) + { + if (params[0].equals_ci("HS_REQUEST") && params.size() >= 5) + { + Anope::string vident = params[2].equals_ci("(null)") ? "" : params[2]; + my_add_host_request(params[1], vident, params[3], params[1], params[4].is_pos_number_only() ? convertTo<time_t>(params[4]) : 0); + + return EVENT_STOP; + } + + return EVENT_CONTINUE; + } + + void OnDatabaseWrite(void (*Write)(const Anope::string &)) + { + for (RequestMap::iterator it = Requests.begin(), it_end = Requests.end(); it != it_end; ++it) + { + HostRequest *hr = it->second; + std::stringstream buf; + buf << "HS_REQUEST " << it->first << " " << (hr->ident.empty() ? "(null)" : hr->ident) << " " << hr->host << " " << hr->time; + Write(buf.str()); + } + } + + void OnReload() + { + ConfigReader config; + HSRequestMemoUser = config.ReadFlag("hs_request", "memouser", "no", 0); + HSRequestMemoOper = config.ReadFlag("hs_request", "memooper", "no", 0); + + Log(LOG_DEBUG) << "[hs_request] Set config vars: MemoUser=" << HSRequestMemoUser << " MemoOper=" << HSRequestMemoOper; + } +}; + +void req_send_memos(CommandSource &source, const Anope::string &vIdent, const Anope::string &vHost) +{ + Anope::string host; + std::list<std::pair<Anope::string, Anope::string> >::iterator it, it_end; + + if (!vIdent.empty()) + host = vIdent + "@" + vHost; + else + host = vHost; + + if (HSRequestMemoOper == 1 && memoserv) + for (unsigned i = 0; i < Config->Opers.size(); ++i) + { + Oper *o = Config->Opers[i]; + + NickAlias *na = findnick(o->name); + if (!na) + continue; + + Anope::string message = Anope::printf(_("[auto memo] vHost \002%s\002 has been requested by %s."), host.c_str(), source.u->GetMask().c_str()); + + memoserv->Send(Config->HostServ, na->nick, message, true); + } +} + +void my_add_host_request(const Anope::string &nick, const Anope::string &vIdent, const Anope::string &vhost, const Anope::string &creator, time_t tmp_time) +{ + HostRequest *hr = new HostRequest; + hr->ident = vIdent; + hr->host = vhost; + hr->time = tmp_time; + RequestMap::iterator it = Requests.find(nick); + if (it != Requests.end()) + { + delete it->second; + Requests.erase(it); + } + Requests.insert(std::make_pair(nick, hr)); +} + +void my_load_config() +{ + ConfigReader config; + HSRequestMemoUser = config.ReadFlag("hs_request", "memouser", "no", 0); + HSRequestMemoOper = config.ReadFlag("hs_request", "memooper", "no", 0); + + Log(LOG_DEBUG) << "[hs_request] Set config vars: MemoUser=" << HSRequestMemoUser << " MemoOper=" << HSRequestMemoOper; +} + +MODULE_INIT(HSRequest) diff --git a/modules/commands/hs_set.cpp b/modules/commands/hs_set.cpp new file mode 100644 index 000000000..4711022db --- /dev/null +++ b/modules/commands/hs_set.cpp @@ -0,0 +1,236 @@ +/* HostServ core 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 "module.h" + +class CommandHSSet : public Command +{ + public: + CommandHSSet(Module *creator) : Command(creator, "hostserv/set", 2, 2) + { + this->SetDesc(_("Set the vhost of another user")); + this->SetSyntax(_("\037nick\037 \037hostmask\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + + const Anope::string &nick = params[0]; + + NickAlias *na = findnick(nick); + if (na == NULL) + { + source.Reply(NICK_X_NOT_REGISTERED, nick.c_str()); + return; + } + + Anope::string rawhostmask = params[1]; + + Anope::string user, host; + size_t a = rawhostmask.find('@'); + + if (a == Anope::string::npos) + host = rawhostmask; + else + { + user = rawhostmask.substr(0, a); + host = rawhostmask.substr(a + 1); + } + + if (host.empty()) + { + this->OnSyntaxError(source, ""); + return; + } + + if (!user.empty()) + { + if (user.length() > Config->UserLen) + { + source.Reply(HOST_SET_IDENTTOOLONG, Config->UserLen); + return; + } + else if (!ircd->vident) + { + source.Reply(HOST_NO_VIDENT); + return; + } + for (Anope::string::iterator s = user.begin(), s_end = user.end(); s != s_end; ++s) + if (!isvalidchar(*s)) + { + source.Reply(HOST_SET_IDENT_ERROR); + return; + } + } + + if (host.length() > Config->HostLen) + { + source.Reply(HOST_SET_TOOLONG, Config->HostLen); + return; + } + + if (!isValidHost(host, 3)) + { + source.Reply(HOST_SET_ERROR); + return; + } + + Log(LOG_ADMIN, u, this) << "to set the vhost of " << na->nick << " to " << (!user.empty() ? user + "@" : "") << host; + + na->hostinfo.SetVhost(user, host, u->nick); + FOREACH_MOD(I_OnSetVhost, OnSetVhost(na)); + if (!user.empty()) + source.Reply(_("VHost for \002%s\002 set to \002%s\002@\002%s\002."), nick.c_str(), user.c_str(), host.c_str()); + else + source.Reply(_("VHost for \002%s\002 set to \002%s\002."), nick.c_str(), host.c_str()); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Sets the vhost for the given nick to that of the given\n" + "hostmask. If your IRCD supports vIdents, then using\n" + "SET <nick> <ident>@<hostmask> set idents for users as \n" + "well as vhosts.")); + return true; + } +}; + +class CommandHSSetAll : public Command +{ + void Sync(NickAlias *na) + { + if (!na || !na->hostinfo.HasVhost()) + return; + + for (std::list<NickAlias *>::iterator it = na->nc->aliases.begin(), it_end = na->nc->aliases.end(); it != it_end; ++it) + { + NickAlias *nick = *it; + nick->hostinfo.SetVhost(na->hostinfo.GetIdent(), na->hostinfo.GetHost(), na->hostinfo.GetCreator()); + } + } + + public: + CommandHSSetAll(Module *creator) : Command(creator, "hostserv/setall", 2, 2) + { + this->SetDesc(_("Set the vhost for all nicks in a group")); + this->SetSyntax(_("\037nick\037 \037hostmask>\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + + Anope::string nick = params[0]; + + NickAlias *na = findnick(nick); + if (na == NULL) + { + source.Reply(NICK_X_NOT_REGISTERED, nick.c_str()); + return; + } + + Anope::string rawhostmask = params[1]; + + Anope::string user, host; + size_t a = rawhostmask.find('@'); + + if (a == Anope::string::npos) + host = rawhostmask; + else + { + user = rawhostmask.substr(0, a); + host = rawhostmask.substr(a + 1); + } + + if (host.empty()) + { + this->OnSyntaxError(source, ""); + return; + } + + if (!user.empty()) + { + if (user.length() > Config->UserLen) + { + source.Reply(HOST_SET_IDENTTOOLONG, Config->UserLen); + return; + } + else if (!ircd->vident) + { + source.Reply(HOST_NO_VIDENT); + return; + } + for (Anope::string::iterator s = user.begin(), s_end = user.end(); s != s_end; ++s) + if (!isvalidchar(*s)) + { + source.Reply(HOST_SET_IDENT_ERROR); + return; + } + } + + if (host.length() > Config->HostLen) + { + source.Reply(HOST_SET_TOOLONG, Config->HostLen); + return; + } + + if (!isValidHost(host, 3)) + { + source.Reply(HOST_SET_ERROR); + return; + } + + Log(LOG_ADMIN, u, this) << "to set the vhost of " << na->nick << " to " << (!user.empty() ? user + "@" : "") << host; + + na->hostinfo.SetVhost(user, host, u->nick); + this->Sync(na); + FOREACH_MOD(I_OnSetVhost, OnSetVhost(na)); + if (!user.empty()) + source.Reply(_("VHost for group \002%s\002 set to \002%s\002@\002%s\002."), nick.c_str(), user.c_str(), host.c_str()); + else + source.Reply(_("VHost for group \002%s\002 set to \002%s\002."), nick.c_str(), host.c_str()); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Sets the vhost for all nicks in the same group as that\n" + "of the given nick. If your IRCD supports vIdents, then\n" + "using SETALL <nick> <ident>@<hostmask> will set idents\n" + "for users as well as vhosts.\n" + "* NOTE, this will not update the vhost for any nicks\n" + "added to the group after this command was used.")); + return true; + } +}; + +class HSSet : public Module +{ + CommandHSSet commandhsset; + CommandHSSetAll commandhssetall; + + public: + HSSet(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), commandhsset(this), commandhssetall(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandhsset); + ModuleManager::RegisterService(&commandhssetall); + } +}; + +MODULE_INIT(HSSet) diff --git a/modules/commands/ms_cancel.cpp b/modules/commands/ms_cancel.cpp new file mode 100644 index 000000000..e5beffa10 --- /dev/null +++ b/modules/commands/ms_cancel.cpp @@ -0,0 +1,77 @@ +/* MemoServ core 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 "module.h" +#include "memoserv.h" + +class CommandMSCancel : public Command +{ + public: + CommandMSCancel(Module *creator) : Command(creator, "memoserv/cancel", 1, 1) + { + this->SetDesc(_("Cancel the last memo you sent")); + this->SetSyntax(_("{\037nick\037 | \037channel\037}")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + + const Anope::string &nname = params[0]; + + bool ischan; + MemoInfo *mi = memoserv->GetMemoInfo(nname, ischan); + + if (mi == NULL) + source.Reply(ischan ? CHAN_X_NOT_REGISTERED : _(NICK_X_NOT_REGISTERED), nname.c_str()); + else + { + for (int i = mi->memos.size() - 1; i >= 0; --i) + if (mi->memos[i]->HasFlag(MF_UNREAD) && u->Account()->display.equals_ci(mi->memos[i]->sender)) + { + FOREACH_MOD(I_OnMemoDel, OnMemoDel(findnick(nname)->nc, mi, mi->memos[i])); + mi->Del(mi->memos[i]); + source.Reply(_("Last memo to \002%s\002 has been cancelled."), nname.c_str()); + return; + } + + source.Reply(_("No memo was cancelable.")); + } + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Cancels the last memo you sent to the given nick or channel,\n" + "provided it has not been read at the time you use the command.")); + return true; + } +}; + +class MSCancel : public Module +{ + CommandMSCancel commandmscancel; + + public: + MSCancel(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandmscancel(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandmscancel); + } +}; + +MODULE_INIT(MSCancel) diff --git a/modules/commands/ms_check.cpp b/modules/commands/ms_check.cpp new file mode 100644 index 000000000..3019e270f --- /dev/null +++ b/modules/commands/ms_check.cpp @@ -0,0 +1,89 @@ +/* MemoServ core 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 "module.h" + +class CommandMSCheck : public Command +{ + public: + CommandMSCheck(Module *creator) : Command(creator, "memoserv/check", 1, 1) + { + this->SetDesc(_("Checks if last memo to a nick was read")); + this->SetSyntax(_("\037nick\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + + const Anope::string &recipient = params[0]; + + bool found = false; + + NickAlias *na = findnick(recipient); + if (!na) + { + source.Reply(NICK_X_NOT_REGISTERED, recipient.c_str()); + return; + } + + MemoInfo *mi = &na->nc->memos; + + /* Okay, I know this looks strange but we wanna get the LAST memo, so we + have to loop backwards */ + + for (int i = mi->memos.size() - 1; i >= 0; --i) + { + if (u->Account()->display.equals_ci(mi->memos[i]->sender)) + { + found = true; /* Yes, we've found the memo */ + + if (mi->memos[i]->HasFlag(MF_UNREAD)) + source.Reply(_("The last memo you sent to %s (sent on %s) has not yet been read."), na->nick.c_str(), do_strftime(mi->memos[i]->time).c_str()); + else + source.Reply(_("The last memo you sent to %s (sent on %s) has been read."), na->nick.c_str(), do_strftime(mi->memos[i]->time).c_str()); + break; + } + } + + if (!found) + source.Reply(_("Nick %s doesn't have a memo from you."), na->nick.c_str()); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Checks whether the _last_ memo you sent to \037nick\037 has been read\n" + "or not. Note that this does only work with nicks, not with chans.")); + return true; + } +}; + +class MSCheck : public Module +{ + CommandMSCheck commandmscheck; + + public: + MSCheck(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandmscheck(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandmscheck); + } +}; + +MODULE_INIT(MSCheck) diff --git a/modules/commands/ms_del.cpp b/modules/commands/ms_del.cpp new file mode 100644 index 000000000..991adff2b --- /dev/null +++ b/modules/commands/ms_del.cpp @@ -0,0 +1,163 @@ +/* MemoServ core 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 "module.h" + +class MemoDelCallback : public NumberList +{ + CommandSource &source; + ChannelInfo *ci; + MemoInfo *mi; + public: + MemoDelCallback(CommandSource &_source, ChannelInfo *_ci, MemoInfo *_mi, const Anope::string &list) : NumberList(list, true), source(_source), ci(_ci), mi(_mi) + { + } + + void HandleNumber(unsigned Number) + { + if (!Number || Number > mi->memos.size()) + return; + + if (ci) + FOREACH_MOD(I_OnMemoDel, OnMemoDel(ci, mi, mi->memos[Number - 1])); + else + FOREACH_MOD(I_OnMemoDel, OnMemoDel(source.u->Account(), mi, mi->memos[Number - 1])); + + mi->Del(Number - 1); + source.Reply(_("Memo %d has been deleted."), Number); + } +}; + +class CommandMSDel : public Command +{ + public: + CommandMSDel(Module *creator) : Command(creator, "memoserv/del", 0, 2) + { + this->SetDesc(_("Delete a memo or memos")); + this->SetSyntax(_("[\037channel\037] {\037num\037 | \037list\037 | LAST | ALL}")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + + MemoInfo *mi; + ChannelInfo *ci = NULL; + Anope::string numstr = !params.empty() ? params[0] : "", chan; + + if (!numstr.empty() && numstr[0] == '#') + { + chan = numstr; + numstr = params.size() > 1 ? params[1] : ""; + + if (!(ci = cs_findchan(chan))) + { + source.Reply(CHAN_X_NOT_REGISTERED, chan.c_str()); + return; + } + else if (readonly) + { + source.Reply(READ_ONLY_MODE); + return; + } + else if (!ci->HasPriv(u, CA_MEMO)) + { + source.Reply(ACCESS_DENIED); + return; + } + mi = &ci->memos; + } + else + mi = &u->Account()->memos; + if (numstr.empty() || (!isdigit(numstr[0]) && !numstr.equals_ci("ALL") && !numstr.equals_ci("LAST"))) + this->OnSyntaxError(source, numstr); + else if (mi->memos.empty()) + { + if (!chan.empty()) + source.Reply(MEMO_X_HAS_NO_MEMOS, chan.c_str()); + else + source.Reply(MEMO_HAVE_NO_MEMOS); + } + else + { + if (isdigit(numstr[0])) + { + MemoDelCallback list(source, ci, mi, numstr); + list.Process(); + } + else if (numstr.equals_ci("LAST")) + { + /* Delete last memo. */ + if (ci) + FOREACH_MOD(I_OnMemoDel, OnMemoDel(ci, mi, mi->memos[mi->memos.size() - 1])); + else + FOREACH_MOD(I_OnMemoDel, OnMemoDel(u->Account(), mi, mi->memos[mi->memos.size() - 1])); + mi->Del(mi->memos[mi->memos.size() - 1]); + source.Reply(_("Memo %d has been deleted."), mi->memos.size() + 1); + } + else + { + if (ci) + FOREACH_MOD(I_OnMemoDel, OnMemoDel(ci, mi, NULL)); + else + FOREACH_MOD(I_OnMemoDel, OnMemoDel(u->Account(), mi, NULL)); + /* Delete all memos. */ + for (unsigned i = 0, end = mi->memos.size(); i < end; ++i) + delete mi->memos[i]; + mi->memos.clear(); + if (!chan.empty()) + source.Reply(_("All memos for channel %s have been deleted."), chan.c_str()); + else + source.Reply(_("All of your memos have been deleted.")); + } + } + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Deletes the specified memo or memos. You can supply\n" + "multiple memo numbers or ranges of numbers instead of a\n" + "single number, as in the second example below.\n" + " \n" + "If \002LAST\002 is given, the last memo will be deleted.\n" + "If \002ALL\002 is given, deletes all of your memos.\n" + " \n" + "Examples:\n" + " \n" + " \002DEL 1\002\n" + " Deletes your first memo.\n" + " \n" + " \002DEL 2-5,7-9\002\n" + " Deletes memos numbered 2 through 5 and 7 through 9.")); + return true; + } +}; + +class MSDel : public Module +{ + CommandMSDel commandmsdel; + + public: + MSDel(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandmsdel(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandmsdel); + } +}; + +MODULE_INIT(MSDel) diff --git a/modules/commands/ms_ignore.cpp b/modules/commands/ms_ignore.cpp new file mode 100644 index 000000000..c8b9c19e6 --- /dev/null +++ b/modules/commands/ms_ignore.cpp @@ -0,0 +1,110 @@ +/* MemoServ core 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 "module.h" +#include "memoserv.h" + +class CommandMSIgnore : public Command +{ + public: + CommandMSIgnore(Module *creator) : Command(creator, "memoserv/ignore", 1, 3) + { + this->SetDesc(_("Manage your memo ignore list")); + this->SetSyntax(_("[\037channel\037] {\002ADD|DEL|LIST\002} [\037entry\037]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + + Anope::string channel = params[0]; + Anope::string command = (params.size() > 1 ? params[1] : ""); + Anope::string param = (params.size() > 2 ? params[2] : ""); + + if (channel[0] != '#') + { + param = command; + command = channel; + channel = u->nick; + } + + bool ischan; + MemoInfo *mi = memoserv->GetMemoInfo(channel, ischan); + if (!mi) + source.Reply(ischan ? CHAN_X_NOT_REGISTERED : _(NICK_X_NOT_REGISTERED), channel.c_str()); + else if (ischan && !cs_findchan(channel)->HasPriv(u, CA_MEMO)) + source.Reply(ACCESS_DENIED); + else if (command.equals_ci("ADD") && !param.empty()) + { + if (std::find(mi->ignores.begin(), mi->ignores.end(), param.ci_str()) == mi->ignores.end()) + { + mi->ignores.push_back(param.ci_str()); + source.Reply(_("\002%s\002 added to your ignore list."), param.c_str()); + } + else + source.Reply(_("\002%s\002 is already on your ignore list."), param.c_str()); + } + else if (command.equals_ci("DEL") && !param.empty()) + { + std::vector<Anope::string>::iterator it = std::find(mi->ignores.begin(), mi->ignores.end(), param.ci_str()); + + if (it != mi->ignores.end()) + { + mi->ignores.erase(it); + source.Reply(_("\002%s\002 removed from your ignore list."), param.c_str()); + } + else + source.Reply(_("\002%s\002 is not on your ignore list."), param.c_str()); + } + else if (command.equals_ci("LIST")) + { + if (mi->ignores.empty()) + source.Reply(_("Your memo ignore list is empty.")); + else + { + source.Reply(_("Ignore list:")); + for (unsigned i = 0; i < mi->ignores.size(); ++i) + source.Reply(" %s", mi->ignores[i].c_str()); + } + } + else + this->OnSyntaxError(source, ""); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Allows you to ignore users by nick or host from memoing you. If someone on your\n" + "memo ignore list tries to memo you, they will not be told that you have them ignored.")); + return true; + } +}; + +class MSIgnore : public Module +{ + CommandMSIgnore commandmsignore; + + public: + MSIgnore(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandmsignore(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandmsignore); + } +}; + +MODULE_INIT(MSIgnore) diff --git a/modules/commands/ms_info.cpp b/modules/commands/ms_info.cpp new file mode 100644 index 000000000..9fdb467c9 --- /dev/null +++ b/modules/commands/ms_info.cpp @@ -0,0 +1,219 @@ +/* MemoServ core 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 "module.h" + +class CommandMSInfo : public Command +{ + public: + CommandMSInfo(Module *creator) : Command(creator, "memoserv/info", 0, 1) + { + this->SetDesc(_("Displays information about your memos")); + this->SetSyntax(_("[\037nick\037 | \037channel\037]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + + const MemoInfo *mi; + NickAlias *na = NULL; + ChannelInfo *ci = NULL; + const Anope::string &nname = !params.empty() ? params[0] : ""; + int hardmax = 0; + + if (!nname.empty() && nname[0] != '#' && u->HasPriv("memoserv/info")) + { + na = findnick(nname); + if (!na) + { + source.Reply(NICK_X_NOT_REGISTERED, nname.c_str()); + return; + } + mi = &na->nc->memos; + hardmax = na->nc->HasFlag(NI_MEMO_HARDMAX) ? 1 : 0; + } + else if (!nname.empty() && nname[0] == '#') + { + if (!(ci = cs_findchan(nname))) + { + source.Reply(CHAN_X_NOT_REGISTERED, nname.c_str()); + return; + } + else if (!ci->HasPriv(u, CA_MEMO)) + { + source.Reply(ACCESS_DENIED); + return; + } + mi = &ci->memos; + hardmax = ci->HasFlag(CI_MEMO_HARDMAX) ? 1 : 0; + } + else if (!nname.empty()) /* It's not a chan and we aren't services admin */ + { + source.Reply(ACCESS_DENIED); + return; + } + else + { + mi = &u->Account()->memos; + hardmax = u->Account()->HasFlag(NI_MEMO_HARDMAX) ? 1 : 0; + } + + if (!nname.empty() && (ci || na->nc != u->Account())) + { + if (mi->memos.empty()) + source.Reply(_("%s currently has no memos."), nname.c_str()); + else if (mi->memos.size() == 1) + { + if (mi->memos[0]->HasFlag(MF_UNREAD)) + source.Reply(_("%s currently has \0021\002 memo, and it has not yet been read."), nname.c_str()); + else + source.Reply(_("%s currently has \0021\002 memo."), nname.c_str()); + } + else + { + unsigned count = 0, i, end; + for (i = 0, end = mi->memos.size(); i < end; ++i) + if (mi->memos[i]->HasFlag(MF_UNREAD)) + ++count; + if (count == mi->memos.size()) + source.Reply(_("%s currently has \002%d\002 memos; all of them are unread."), nname.c_str(), count); + else if (!count) + source.Reply(_("%s currently has \002%d\002 memos."), nname.c_str(), mi->memos.size()); + else if (count == 1) + source.Reply(_("%s currently has \002%d\002 memos, of which \0021\002 is unread."), nname.c_str(), mi->memos.size()); + else + source.Reply(_("%s currently has \002%d\002 memos, of which \002%d\002 are unread."), nname.c_str(), mi->memos.size(), count); + } + if (!mi->memomax) + { + if (hardmax) + source.Reply(_("%s's memo limit is \002%d\002, and may not be changed."), nname.c_str(), mi->memomax); + else + source.Reply(_("%s's memo limit is \002%d\002."), nname.c_str(), mi->memomax); + } + else if (mi->memomax > 0) + { + if (hardmax) + source.Reply(_("%s's memo limit is \002%d\002, and may not be changed."), nname.c_str(), mi->memomax); + else + source.Reply(_("%s's memo limit is \002%d\002."), nname.c_str(), mi->memomax); + } + else + source.Reply(_("%s has no memo limit."), nname.c_str()); + + /* I ripped this code out of ircservices 4.4.5, since I didn't want + to rewrite the whole thing (it pisses me off). */ + if (na) + { + if (na->nc->HasFlag(NI_MEMO_RECEIVE) && na->nc->HasFlag(NI_MEMO_SIGNON)) + source.Reply(_("%s is notified of new memos at logon and when they arrive."), nname.c_str()); + else if (na->nc->HasFlag(NI_MEMO_RECEIVE)) + source.Reply(_("%s is notified when new memos arrive."), nname.c_str()); + else if (na->nc->HasFlag(NI_MEMO_SIGNON)) + source.Reply(_("%s is notified of news memos at logon."), nname.c_str()); + else + source.Reply(_("%s is not notified of new memos."), nname.c_str()); + } + } + else /* !nname || (!ci || na->nc == u->Account()) */ + { + if (mi->memos.empty()) + source.Reply(_("You currently have no memos.")); + else if (mi->memos.size() == 1) + { + if (mi->memos[0]->HasFlag(MF_UNREAD)) + source.Reply(_("You currently have \0021\002 memo, and it has not yet been read.")); + else + source.Reply(_("You currently have \0021\002 memo.")); + } + else + { + unsigned count = 0, i, end; + for (i = 0, end = mi->memos.size(); i < end; ++i) + if (mi->memos[i]->HasFlag(MF_UNREAD)) + ++count; + if (count == mi->memos.size()) + source.Reply(_("You currently have \002%d\002 memos; all of them are unread."), count); + else if (!count) + source.Reply(_("You currently have \002%d\002 memos."), mi->memos.size()); + else if (count == 1) + source.Reply(_("You currently have \002%d\002 memos, of which \0021\002 is unread."), mi->memos.size()); + else + source.Reply(_("You currently have \002%d\002 memos, of which \002%d\002 are unread."), mi->memos.size(), count); + } + + if (!mi->memomax) + { + if (!u->IsServicesOper() && hardmax) + source.Reply(_("Your memo limit is \0020\002; you will not receive any new memos. You cannot change this limit.")); + else + source.Reply(_("Your memo limit is \0020\002; you will not receive any new memos.")); + } + else if (mi->memomax > 0) + { + if (!u->IsServicesOper() && hardmax) + source.Reply(_("Your memo limit is \002%d\002, and may not be changed."), mi->memomax); + else + source.Reply(_("Your memo limit is \002%d\002."), mi->memomax); + } + else + source.Reply(_("You have no limit on the number of memos you may keep.")); + + /* Ripped too. But differently because of a seg fault (loughs) */ + if (u->Account()->HasFlag(NI_MEMO_RECEIVE) && u->Account()->HasFlag(NI_MEMO_SIGNON)) + source.Reply(_("You will be notified of new memos at logon and when they arrive.")); + else if (u->Account()->HasFlag(NI_MEMO_RECEIVE)) + source.Reply(_("You will be notified when new memos arrive.")); + else if (u->Account()->HasFlag(NI_MEMO_SIGNON)) + source.Reply(_("You will be notified of new memos at logon.")); + else + source.Reply(_("You will not be notified of new memos.")); + } + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Without a parameter, displays information on the number of\n" + "memos you have, how many of them are unread, and how many\n" + "total memos you can receive.\n" + " \n" + "With a channel parameter, displays the same information for\n" + "the given channel.\n" + " \n" + "With a nickname parameter, displays the same information\n" + "for the given nickname. This use limited to \002Services\n" + "Operators\002.")); + + return true; + } +}; + +class MSInfo : public Module +{ + CommandMSInfo commandmsinfo; + + public: + MSInfo(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandmsinfo(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandmsinfo); + } +}; + +MODULE_INIT(MSInfo) diff --git a/modules/commands/ms_list.cpp b/modules/commands/ms_list.cpp new file mode 100644 index 000000000..26821a413 --- /dev/null +++ b/modules/commands/ms_list.cpp @@ -0,0 +1,168 @@ +/* MemoServ core 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 "module.h" + +class MemoListCallback : public NumberList +{ + CommandSource &source; + ChannelInfo *ci; + const MemoInfo *mi; + bool SentHeader; + public: + MemoListCallback(CommandSource &_source, ChannelInfo *_ci, const MemoInfo *_mi, const Anope::string &list) : NumberList(list, false), source(_source), ci(_ci), mi(_mi), SentHeader(false) + { + } + + void HandleNumber(unsigned Number) + { + if (!Number || Number > mi->memos.size()) + return; + + if (!SentHeader) + { + SentHeader = true; + source.Reply(_("Memos for %s:"), ci ? ci->name.c_str() : source.u->nick.c_str()); + source.Reply(_(" Num Sender Date/Time")); + } + + DoList(source, mi, Number - 1); + } + + static void DoList(CommandSource &source, const MemoInfo *mi, unsigned index) + { + Memo *m = mi->memos[index]; + source.Reply(_("%c%3d %-16s %s"), (m->HasFlag(MF_UNREAD)) ? '*' : ' ', index + 1, m->sender.c_str(), do_strftime(m->time).c_str()); + } +}; + +class CommandMSList : public Command +{ + public: + CommandMSList(Module *creator) : Command(creator, "memoserv/list", 0, 2) + { + this->SetDesc(_("List your memos")); + this->SetSyntax(_("[\037channel\037] [\037list\037 | NEW]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + + Anope::string param = !params.empty() ? params[0] : "", chan; + ChannelInfo *ci = NULL; + const MemoInfo *mi; + int i, end; + + if (!param.empty() && param[0] == '#') + { + chan = param; + param = params.size() > 1 ? params[1] : ""; + + if (!(ci = cs_findchan(chan))) + { + source.Reply(CHAN_X_NOT_REGISTERED, chan.c_str()); + return; + } + else if (!ci->HasPriv(u, CA_MEMO)) + { + source.Reply(ACCESS_DENIED); + return; + } + mi = &ci->memos; + } + else + mi = &u->Account()->memos; + if (!param.empty() && !isdigit(param[0]) && !param.equals_ci("NEW")) + this->OnSyntaxError(source, param); + else if (!mi->memos.size()) + { + if (!chan.empty()) + source.Reply(MEMO_X_HAS_NO_MEMOS, chan.c_str()); + else + source.Reply(MEMO_HAVE_NO_MEMOS); + } + else + { + if (!param.empty() && isdigit(param[0])) + { + MemoListCallback list(source, ci, mi, param); + list.Process(); + } + else + { + if (!param.empty()) + { + for (i = 0, end = mi->memos.size(); i < end; ++i) + if (mi->memos[i]->HasFlag(MF_UNREAD)) + break; + if (i == end) + { + if (!chan.empty()) + source.Reply(MEMO_X_HAS_NO_NEW_MEMOS, chan.c_str()); + else + source.Reply(MEMO_HAVE_NO_NEW_MEMOS); + return; + } + } + + bool SentHeader = false; + + for (i = 0, end = mi->memos.size(); i < end; ++i) + { + if (!param.empty() && !mi->memos[i]->HasFlag(MF_UNREAD)) + continue; + + if (!SentHeader) + { + SentHeader = true; + source.Reply(_("New memo for %s."), ci ? ci->name.c_str() : u->nick.c_str()); + source.Reply(_(" Num Sender Date/Time")); + } + + MemoListCallback::DoList(source, mi, i); + } + } + } + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Lists any memos you currently have. With \002NEW\002, lists only\n" + "new (unread) memos. Unread memos are marked with a \"*\"\n" + "to the left of the memo number. You can also specify a list\n" + "of numbers, as in the example below:\n" + " \002LIST 2-5,7-9\002\n" + " Lists memos numbered 2 through 5 and 7 through 9.")); + return true; + } +}; + +class MSList : public Module +{ + CommandMSList commandmslist; + + public: + MSList(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandmslist(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandmslist); + } +}; + +MODULE_INIT(MSList) diff --git a/modules/commands/ms_read.cpp b/modules/commands/ms_read.cpp new file mode 100644 index 000000000..f275f8d7d --- /dev/null +++ b/modules/commands/ms_read.cpp @@ -0,0 +1,195 @@ +/* MemoServ core 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 "module.h" +#include "memoserv.h" + +void rsend_notify(CommandSource &source, MemoInfo *mi, Memo *m, const Anope::string &targ) +{ + /* Only send receipt if memos are allowed */ + if (memoserv && !readonly) + { + /* Get nick alias for sender */ + NickAlias *na = findnick(m->sender); + + if (!na) + return; + + /* Get nick core for sender */ + NickCore *nc = na->nc; + + if (!nc) + return; + + /* Text of the memo varies if the recepient was a + nick or channel */ + Anope::string text = Anope::printf(translate(na->nc, _("\002[auto-memo]\002 The memo you sent to %s has been viewed.")), targ.c_str()); + + /* Send notification */ + memoserv->Send(source.u->nick, m->sender, text, true); + + /* Notify recepient of the memo that a notification has + been sent to the sender */ + source.Reply(_("A notification memo has been sent to %s informing him/her you have\n" + "read his/her memo."), nc->display.c_str()); + } + + /* Remove receipt flag from the original memo */ + m->UnsetFlag(MF_RECEIPT); +} + +class MemoListCallback : public NumberList +{ + CommandSource &source; + MemoInfo *mi; + public: + MemoListCallback(CommandSource &_source, MemoInfo *_mi, const Anope::string &numlist) : NumberList(numlist, false), source(_source), mi(_mi) + { + } + + void HandleNumber(unsigned Number) + { + if (!Number || Number > mi->memos.size()) + return; + + MemoListCallback::DoRead(source, mi, NULL, Number - 1); + } + + static void DoRead(CommandSource &source, MemoInfo *mi, ChannelInfo *ci, unsigned index) + { + Memo *m = mi->memos[index]; + if (ci) + source.Reply(_("Memo %d from %s (%s). To delete, type: \002%s%s DEL %s %d\002"), index + 1, m->sender.c_str(), do_strftime(m->time).c_str(), Config->UseStrictPrivMsgString.c_str(), Config->MemoServ.c_str(), ci->name.c_str(), index + 1); + else + source.Reply(_("Memo %d from %s (%s). To delete, type: \002%s%s DEL %d\002"), index + 1, m->sender.c_str(), do_strftime(m->time).c_str(), Config->UseStrictPrivMsgString.c_str(), Config->MemoServ.c_str(), index + 1); + source.Reply("%s", m->text.c_str()); + m->UnsetFlag(MF_UNREAD); + + /* Check if a receipt notification was requested */ + if (m->HasFlag(MF_RECEIPT)) + rsend_notify(source, mi, m, ci ? ci->name : source.u->nick); + } +}; + +class CommandMSRead : public Command +{ + public: + CommandMSRead(Module *creator) : Command(creator, "memoserv/read", 1, 2) + { + this->SetDesc(_("Read a memo or memos")); + this->SetSyntax(_("[\037channel\037] {\037num\037 | \037list\037 | LAST | NEW}")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + + MemoInfo *mi; + ChannelInfo *ci = NULL; + Anope::string numstr = params[0], chan; + + if (!numstr.empty() && numstr[0] == '#') + { + chan = numstr; + numstr = params.size() > 1 ? params[1] : ""; + + if (!(ci = cs_findchan(chan))) + { + source.Reply(CHAN_X_NOT_REGISTERED, chan.c_str()); + return; + } + else if (!ci->HasPriv(u, CA_MEMO)) + { + source.Reply(ACCESS_DENIED); + return; + } + mi = &ci->memos; + } + else + mi = &u->Account()->memos; + + if (numstr.empty() || (!numstr.equals_ci("LAST") && !numstr.equals_ci("NEW") && !numstr.is_number_only())) + this->OnSyntaxError(source, numstr); + else if (mi->memos.empty()) + { + if (!chan.empty()) + source.Reply(MEMO_X_HAS_NO_MEMOS, chan.c_str()); + else + source.Reply(MEMO_HAVE_NO_MEMOS); + } + else + { + int i, end; + + if (numstr.equals_ci("NEW")) + { + int readcount = 0; + for (i = 0, end = mi->memos.size(); i < end; ++i) + if (mi->memos[i]->HasFlag(MF_UNREAD)) + { + MemoListCallback::DoRead(source, mi, ci, i); + ++readcount; + } + if (!readcount) + { + if (!chan.empty()) + source.Reply(MEMO_X_HAS_NO_NEW_MEMOS, chan.c_str()); + else + source.Reply(MEMO_HAVE_NO_NEW_MEMOS); + } + } + else if (numstr.equals_ci("LAST")) + { + for (i = 0, end = mi->memos.size() - 1; i < end; ++i); + MemoListCallback::DoRead(source, mi, ci, i); + } + else /* number[s] */ + { + MemoListCallback list(source, mi, numstr); + list.Process(); + } + } + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Sends you the text of the memos specified. If LAST is\n" + "given, sends you the memo you most recently received. If\n" + "NEW is given, sends you all of your new memos. Otherwise,\n" + "sends you memo number \037num\037. You can also give a list of\n" + "numbers, as in this example:\n" + " \n" + " \002READ 2-5,7-9\002\n" + " Displays memos numbered 2 through 5 and 7 through 9.")); + return true; + } +}; + +class MSRead : public Module +{ + CommandMSRead commandmsread; + + public: + MSRead(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandmsread(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandmsread); + } +}; + +MODULE_INIT(MSRead) diff --git a/modules/commands/ms_rsend.cpp b/modules/commands/ms_rsend.cpp new file mode 100644 index 000000000..37c633e02 --- /dev/null +++ b/modules/commands/ms_rsend.cpp @@ -0,0 +1,106 @@ +/* MemoServ core 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 "module.h" +#include "memoserv.h" + +class CommandMSRSend : public Command +{ + public: + CommandMSRSend(Module *creator) : Command(creator, "memoserv/rsend", 2, 2) + { + this->SetDesc(_("Sends a memo and requests a read receipt")); + this->SetSyntax(_("{\037nick\037 | \037channel\037} \037memo-text\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + + const Anope::string &nick = params[0]; + const Anope::string &text = params[1]; + NickAlias *na = NULL; + + /* prevent user from rsend to themselves */ + if ((na = findnick(nick)) && na->nc == u->Account()) + { + source.Reply(_("You can not request a receipt when sending a memo to yourself.")); + return; + } + + if (Config->MSMemoReceipt == 1 && !u->IsServicesOper()) + source.Reply(ACCESS_DENIED); + else if (Config->MSMemoReceipt > 2 || Config->MSMemoReceipt == 0) + { + Log() << "MSMemoReceipt is set misconfigured to " << Config->MSMemoReceipt; + source.Reply(_("Sorry, RSEND has been disabled on this network.")); + } + else + { + MemoServService::MemoResult result = memoserv->Send(u->nick, nick, text); + if (result == MemoServService::MEMO_INVALID_TARGET) + source.Reply(_("\002%s\002 is not a registered unforbidden nick or channel."), nick.c_str()); + else if (result == MemoServService::MEMO_TOO_FAST) + source.Reply(_("Please wait %d seconds before using the SEND command again."), Config->MSSendDelay); + else if (result == MemoServService::MEMO_TARGET_FULL) + source.Reply(_("%s currently has too many memos and cannot receive more."), nick.c_str()); + else + { + source.Reply(_("Memo sent to \002%s\002."), name.c_str()); + + bool ischan; + MemoInfo *mi = memoserv->GetMemoInfo(nick, ischan); + if (mi == NULL) + throw CoreException("NULL mi in ms_rsend"); + Memo *m = (mi->memos.size() ? mi->memos[mi->memos.size() - 1] : NULL); + if (m != NULL) + m->SetFlag(MF_RECEIPT); + } + } + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Sends the named \037nick\037 or \037channel\037 a memo containing\n" + "\037memo-text\037. When sending to a nickname, the recipient will\n" + "receive a notice that he/she has a new memo. The target\n" + "nickname/channel must be registered.\n" + "Once the memo is read by its recipient, an automatic notification\n" + "memo will be sent to the sender informing him/her that the memo\n" + "has been read.")); + return true; + } +}; + +class MSRSend : public Module +{ + CommandMSRSend commandmsrsend; + + public: + MSRSend(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandmsrsend(this) + { + this->SetAuthor("Anope"); + + if (!Config->MSMemoReceipt) + throw ModuleException("Invalid value for memoreceipt"); + + ModuleManager::RegisterService(&commandmsrsend); + } +}; + +MODULE_INIT(MSRSend) diff --git a/modules/commands/ms_send.cpp b/modules/commands/ms_send.cpp new file mode 100644 index 000000000..9397e7ad7 --- /dev/null +++ b/modules/commands/ms_send.cpp @@ -0,0 +1,70 @@ +/* MemoServ core 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 "module.h" +#include "memoserv.h" + +class CommandMSSend : public Command +{ + public: + CommandMSSend(Module *creator) : Command(creator, "memoserv/send", 2, 2) + { + this->SetDesc(_("Send a memo to a nick or channel")); + this->SetSyntax(_("{\037nick\037 | \037channel\037} \037memo-text\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &nick = params[0]; + const Anope::string &text = params[1]; + + MemoServService::MemoResult result = memoserv->Send(source.u->nick, nick, text); + if (result == MemoServService::MEMO_SUCCESS) + source.Reply(_("Memo sent to \002%s\002."), nick.c_str()); + else if (result == MemoServService::MEMO_INVALID_TARGET) + source.Reply(_("\002%s\002 is not a registered unforbidden nick or channel."), nick.c_str()); + else if (result == MemoServService::MEMO_TOO_FAST) + source.Reply(_("Please wait %d seconds before using the SEND command again."), Config->MSSendDelay); + else if (result == MemoServService::MEMO_TARGET_FULL) + source.Reply(_("%s currently has too many memos and cannot receive more."), nick.c_str()); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Sends the named \037nick\037 or \037channel\037 a memo containing\n" + "\037memo-text\037. When sending to a nickname, the recipient will\n" + "receive a notice that he/she has a new memo. The target\n" + "nickname/channel must be registered.")); + return true; + } +}; + +class MSSend : public Module +{ + CommandMSSend commandmssend; + + public: + MSSend(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandmssend(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandmssend); + } +}; + +MODULE_INIT(MSSend) diff --git a/modules/commands/ms_sendall.cpp b/modules/commands/ms_sendall.cpp new file mode 100644 index 000000000..7af5ac7af --- /dev/null +++ b/modules/commands/ms_sendall.cpp @@ -0,0 +1,74 @@ +/* MemoServ core 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 "module.h" +#include "memoserv.h" + +class CommandMSSendAll : public Command +{ + public: + CommandMSSendAll(Module *creator) : Command(creator, "memoserv/sendall", 1, 1) + { + this->SetDesc(_("Send a memo to all registered users")); + this->SetSyntax(_("\037memo-text\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + const Anope::string &text = params[0]; + + if (readonly) + { + source.Reply(MEMO_SEND_DISABLED); + return; + } + + NickAlias *na = findnick(u->nick); + + for (nickcore_map::const_iterator it = NickCoreList.begin(), it_end = NickCoreList.end(); it != it_end; ++it) + { + NickCore *nc = it->second; + + if ((na && na->nc == nc) || !nc->display.equals_ci(u->nick)) + memoserv->Send(u->nick, nc->display, text); + } + + source.Reply(_("A massmemo has been sent to all registered users.")); + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Sends all registered users a memo containing \037memo-text\037.")); + return true; + } +}; + +class MSSendAll : public Module +{ + CommandMSSendAll commandmssendall; + + public: + MSSendAll(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandmssendall(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandmssendall); + } +}; + +MODULE_INIT(MSSendAll) diff --git a/modules/commands/ms_set.cpp b/modules/commands/ms_set.cpp new file mode 100644 index 000000000..1aa3f469d --- /dev/null +++ b/modules/commands/ms_set.cpp @@ -0,0 +1,313 @@ +/* MemoServ core 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 "module.h" + +class CommandMSSet : public Command +{ + private: + void DoNotify(CommandSource &source, const std::vector<Anope::string> ¶ms, MemoInfo *mi) + { + User *u = source.u; + const Anope::string ¶m = params[1]; + + if (param.equals_ci("ON")) + { + u->Account()->SetFlag(NI_MEMO_SIGNON); + u->Account()->SetFlag(NI_MEMO_RECEIVE); + source.Reply(_("%s will now notify you of memos when you log on and when they are sent to you."), Config->MemoServ.c_str()); + } + else if (param.equals_ci("LOGON")) + { + u->Account()->SetFlag(NI_MEMO_SIGNON); + u->Account()->UnsetFlag(NI_MEMO_RECEIVE); + source.Reply(_("%s will now notify you of memos when you log on or unset /AWAY."), Config->MemoServ.c_str()); + } + else if (param.equals_ci("NEW")) + { + u->Account()->UnsetFlag(NI_MEMO_SIGNON); + u->Account()->SetFlag(NI_MEMO_RECEIVE); + source.Reply(_("%s will now notify you of memos when they are sent to you."), Config->MemoServ.c_str()); + } + else if (param.equals_ci("MAIL")) + { + if (!u->Account()->email.empty()) + { + u->Account()->SetFlag(NI_MEMO_MAIL); + source.Reply(_("You will now be informed about new memos via email.")); + } + else + source.Reply(_("There's no email address set for your nick.")); + } + else if (param.equals_ci("NOMAIL")) + { + u->Account()->UnsetFlag(NI_MEMO_MAIL); + source.Reply(_("You will no longer be informed via email.")); + } + else if (param.equals_ci("OFF")) + { + u->Account()->UnsetFlag(NI_MEMO_SIGNON); + u->Account()->UnsetFlag(NI_MEMO_RECEIVE); + u->Account()->UnsetFlag(NI_MEMO_MAIL); + source.Reply(_("%s will not send you any notification of memos."), Config->MemoServ.c_str()); + } + else + this->OnSyntaxError(source, param); + + return; + } + + void DoLimit(CommandSource &source, const std::vector<Anope::string> ¶ms, MemoInfo *mi) + { + User *u = source.u; + + Anope::string p1 = params[1]; + Anope::string p2 = params.size() > 2 ? params[2] : ""; + Anope::string p3 = params.size() > 3 ? params[3] : ""; + Anope::string user, chan; + int16 limit; + NickCore *nc = u->Account(); + ChannelInfo *ci = NULL; + bool is_servadmin = u->HasPriv("memoserv/set-limit"); + + if (p1[0] == '#') + { + chan = p1; + p1 = p2; + p2 = p3; + p3 = params.size() > 4 ? params[4] : ""; + if (!(ci = cs_findchan(chan))) + { + source.Reply(CHAN_X_NOT_REGISTERED, chan.c_str()); + return; + } + else if (!is_servadmin && !ci->HasPriv(u, CA_MEMO)) + { + source.Reply(ACCESS_DENIED); + return; + } + mi = &ci->memos; + } + if (is_servadmin) + { + if (!p2.empty() && !p2.equals_ci("HARD") && chan.empty()) + { + NickAlias *na; + if (!(na = findnick(p1))) + { + source.Reply(NICK_X_NOT_REGISTERED, p1.c_str()); + return; + } + user = p1; + mi = &na->nc->memos; + nc = na->nc; + p1 = p2; + p2 = p3; + } + else if (p1.empty() || (!p1.is_pos_number_only() && !p1.equals_ci("NONE")) || (!p2.empty() && !p2.equals_ci("HARD"))) + { + this->OnSyntaxError(source, ""); + return; + } + if (!chan.empty()) + { + if (!p2.empty()) + ci->SetFlag(CI_MEMO_HARDMAX); + else + ci->UnsetFlag(CI_MEMO_HARDMAX); + } + else + { + if (!p2.empty()) + nc->SetFlag(NI_MEMO_HARDMAX); + else + nc->UnsetFlag(NI_MEMO_HARDMAX); + } + limit = -1; + try + { + limit = convertTo<int16>(p1); + } + catch (const ConvertException &) { } + } + else + { + if (p1.empty() || !p2.empty() || !isdigit(p1[0])) + { + this->OnSyntaxError(source, ""); + return; + } + if (!chan.empty() && ci->HasFlag(CI_MEMO_HARDMAX)) + { + source.Reply(_("The memo limit for %s may not be changed."), chan.c_str()); + return; + } + else if (chan.empty() && nc->HasFlag(NI_MEMO_HARDMAX)) + { + source.Reply(_("You are not permitted to change your memo limit.")); + return; + } + limit = -1; + try + { + limit = convertTo<int16>(p1); + } + catch (const ConvertException &) { } + /* The first character is a digit, but we could still go negative + * from overflow... watch out! */ + if (limit < 0 || (Config->MSMaxMemos > 0 && static_cast<unsigned>(limit) > Config->MSMaxMemos)) + { + if (!chan.empty()) + source.Reply(_("You cannot set the memo limit for %s higher than %d."), chan.c_str(), Config->MSMaxMemos); + else + source.Reply(_("You cannot set your memo limit higher than %d."), Config->MSMaxMemos); + return; + } + } + mi->memomax = limit; + if (limit > 0) + { + if (chan.empty() && nc == u->Account()) + source.Reply(_("Your memo limit has been set to \002%d\002."), limit); + else + source.Reply(_("Memo limit for %s set to \002%d\002."), !chan.empty() ? chan.c_str() : user.c_str(), limit); + } + else if (!limit) + { + if (chan.empty() && nc == u->Account()) + source.Reply(_("You will no longer be able to receive memos.")); + else + source.Reply(_("Memo limit for %s set to \0020\002."), !chan.empty() ? chan.c_str() : user.c_str()); + } + else + { + if (chan.empty() && nc == u->Account()) + source.Reply(_("Your memo limit has been disabled.")); + else + source.Reply(_("Memo limit \002disabled\002 for %s."), !chan.empty() ? chan.c_str() : user.c_str()); + } + return; + } + public: + CommandMSSet(Module *creator) : Command(creator, "memoserv/set", 2, 5) + { + this->SetDesc(_("Set options related to memos")); + this->SetSyntax(_("\037option\037 \037parameters\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + const Anope::string &cmd = params[0]; + MemoInfo *mi = &u->Account()->memos; + + if (readonly) + source.Reply(_("Sorry, memo option setting is temporarily disabled.")); + else if (cmd.equals_ci("NOTIFY")) + return this->DoNotify(source, params, mi); + else if (cmd.equals_ci("LIMIT")) + return this->DoLimit(source, params, mi); + else + { + source.Reply(NICK_SET_UNKNOWN_OPTION, Config->UseStrictPrivMsgString.c_str(), cmd.c_str()); + source.Reply(MORE_INFO, Config->UseStrictPrivMsgString.c_str(), source.owner->nick.c_str(), "SET"); + } + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + if (subcommand.empty()) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Sets various memo options. \037option\037 can be one of:\n" + " \n" + " NOTIFY Changes when you will be notified about\n" + " new memos (only for nicknames)\n" + " LIMIT Sets the maximum number of memos you can\n" + " receive\n" + " \n" + "Type \002%s%s HELP %s \037option\037\002 for more information\n" + "on a specific option."), Config->UseStrictPrivMsgString.c_str(), source.owner->nick.c_str(), source.command.c_str()); + } + else if (subcommand.equals_ci("NOTIFY")) + source.Reply(_("Syntax: \002NOTIFY {ON | LOGON | NEW | MAIL | NOMAIL | OFF}\002\n" + "Changes when you will be notified about new memos:\n" + " \n" + " ON You will be notified of memos when you log on,\n" + " when you unset /AWAY, and when they are sent\n" + " to you.\n" + " LOGON You will only be notified of memos when you log\n" + " on or when you unset /AWAY.\n" + " NEW You will only be notified of memos when they\n" + " are sent to you.\n" + " MAIL You will be notified of memos by email aswell as\n" + " any other settings you have.\n" + " NOMAIL You will not be notified of memos by email.\n" + " OFF You will not receive any notification of memos.\n" + " \n" + "\002ON\002 is essentially \002LOGON\002 and \002NEW\002 combined.")); + else if (subcommand.equals_ci("LIMIT")) + { + User *u = source.u; + if (u->IsServicesOper()) + source.Reply(_("Syntax: \002LIMIT [\037user\037 | \037channel\037] {\037limit\037 | NONE} [HARD]\002\n" + " \n" + "Sets the maximum number of memos a user or channel is\n" + "allowed to have. Setting the limit to 0 prevents the user\n" + "from receiving any memos; setting it to \002NONE\002 allows the\n" + "user to receive and keep as many memos as they want. If\n" + "you do not give a nickname or channel, your own limit is\n" + "set.\n" + " \n" + "Adding \002HARD\002 prevents the user from changing the limit. Not\n" + "adding \002HARD\002 has the opposite effect, allowing the user to\n" + "change the limit (even if a previous limit was set with\n" + "\002HARD\002).\n" + "This use of the \002SET LIMIT\002 command is limited to \002Services\002\n" + "\002admins\002. Other users may only enter a limit for themselves\n" + "or a channel on which they have such privileges, may not\n" + "remove their limit, may not set a limit above %d, and may\n" + "not set a hard limit."), Config->MSMaxMemos); + else + source.Reply(_("Syntax: \002LIMIT [\037channel\037] \037limit\037\002\n" + " \n" + "Sets the maximum number of memos you (or the given channel)\n" + "are allowed to have. If you set this to 0, no one will be\n" + "able to send any memos to you. However, you cannot set\n" + "this any higher than %d."), Config->MSMaxMemos); + } + else + return false; + + return true; + } +}; + +class MSSet : public Module +{ + CommandMSSet commandmsset; + + public: + MSSet(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandmsset(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandmsset); + } +}; + +MODULE_INIT(MSSet) diff --git a/modules/commands/ms_staff.cpp b/modules/commands/ms_staff.cpp new file mode 100644 index 000000000..6264dbea4 --- /dev/null +++ b/modules/commands/ms_staff.cpp @@ -0,0 +1,71 @@ +/* MemoServ core 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 "module.h" +#include "memoserv.h" + +class CommandMSStaff : public Command +{ + public: + CommandMSStaff(Module *creator) : Command(creator, "memoserv/staff", 1, 1) + { + this->SetDesc(_("Send a memo to all opers/admins")); + this->SetSyntax(_("\037memo-text\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &text = params[0]; + + if (readonly) + { + source.Reply(MEMO_SEND_DISABLED); + return; + } + + for (nickcore_map::const_iterator it = NickCoreList.begin(), it_end = NickCoreList.end(); it != it_end; ++it) + { + NickCore *nc = it->second; + + if (nc->IsServicesOper()) + memoserv->Send(source.u->nick, nc->display, text, true); + } + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Sends all services staff a memo containing \037memo-text\037.")); + + return true; + } +}; + +class MSStaff : public Module +{ + CommandMSStaff commandmsstaff; + + public: + MSStaff(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandmsstaff(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandmsstaff); + } +}; + +MODULE_INIT(MSStaff) diff --git a/modules/commands/ns_access.cpp b/modules/commands/ns_access.cpp new file mode 100644 index 000000000..df9ae6eed --- /dev/null +++ b/modules/commands/ns_access.cpp @@ -0,0 +1,195 @@ +/* NickServ core 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 "module.h" + +class CommandNSAccess : public Command +{ + private: + void DoServAdminList(CommandSource &source, const std::vector<Anope::string> ¶ms, NickCore *nc) + { + Anope::string mask = params.size() > 2 ? params[2] : ""; + unsigned i, end; + + if (nc->access.empty()) + { + source.Reply(_("Access list for \002%s\002 is empty."), nc->display.c_str()); + return; + } + + if (nc->HasFlag(NI_SUSPENDED)) + { + source.Reply(NICK_X_SUSPENDED, nc->display.c_str()); + return; + } + + source.Reply(_("Access list for \002%s\002:"), params[1].c_str()); + for (i = 0, end = nc->access.size(); i < end; ++i) + { + Anope::string access = nc->GetAccess(i); + if (!mask.empty() && !Anope::Match(access, mask)) + continue; + source.Reply(" %s", access.c_str()); + } + + return; + } + + void DoAdd(CommandSource &source, NickCore *nc, const Anope::string &mask) + { + + if (mask.empty()) + { + this->OnSyntaxError(source, "ADD"); + return; + } + + if (nc->access.size() >= Config->NSAccessMax) + { + source.Reply(_("Sorry, you can only have %d access entries for a nickname."), Config->NSAccessMax); + return; + } + + if (nc->FindAccess(mask)) + { + source.Reply(_("Mask \002%s\002 already present on your access list."), mask.c_str()); + return; + } + + nc->AddAccess(mask); + source.Reply(_("\002%s\002 added to your access list."), mask.c_str()); + + return; + } + + void DoDel(CommandSource &source, NickCore *nc, const Anope::string &mask) + { + if (mask.empty()) + { + this->OnSyntaxError(source, "DEL"); + return; + } + + if (!nc->FindAccess(mask)) + { + source.Reply(_("\002%s\002 not found on your access list."), mask.c_str()); + return; + } + + source.Reply(_("\002%s\002 deleted from your access list."), mask.c_str()); + nc->EraseAccess(mask); + + return; + } + + void DoList(CommandSource &source, NickCore *nc, const Anope::string &mask) + { + User *u = source.u; + unsigned i, end; + + if (nc->access.empty()) + { + source.Reply(_("Your access list is empty."), u->nick.c_str()); + return; + } + + source.Reply(_("Access list:")); + for (i = 0, end = nc->access.size(); i < end; ++i) + { + Anope::string access = nc->GetAccess(i); + if (!mask.empty() && !Anope::Match(access, mask)) + continue; + source.Reply(" %s", access.c_str()); + } + + return; + } + public: + CommandNSAccess(Module *creator) : Command(creator, "nickserv/access", 1, 3) + { + this->SetDesc(_("Modify the list of authorized addresses")); + this->SetSyntax(_("ADD \037mask\037")); + this->SetSyntax(_("DEL \037mask\037")); + this->SetSyntax(_("LIST")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + const Anope::string &cmd = params[0]; + const Anope::string &mask = params.size() > 1 ? params[1] : ""; + + NickAlias *na; + if (cmd.equals_ci("LIST") && u->IsServicesOper() && !mask.empty() && (na = findnick(params[1]))) + return this->DoServAdminList(source, params, na->nc); + + if (!mask.empty() && mask.find('@') == Anope::string::npos) + { + source.Reply(BAD_USERHOST_MASK); + source.Reply(MORE_INFO, Config->UseStrictPrivMsgString.c_str(), Config->NickServ.c_str(), this->name.c_str()); + } + else if (u->Account()->HasFlag(NI_SUSPENDED)) + source.Reply(NICK_X_SUSPENDED, u->Account()->display.c_str()); + else if (cmd.equals_ci("ADD")) + return this->DoAdd(source, u->Account(), mask); + else if (cmd.equals_ci("DEL")) + return this->DoDel(source, u->Account(), mask); + else if (cmd.equals_ci("LIST")) + return this->DoList(source, u->Account(), mask); + else + this->OnSyntaxError(source, ""); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Modifies or displays the access list for your nick. This\n" + "is the list of addresses which will be automatically\n" + "recognized by %s as allowed to use the nick. If\n" + "you want to use the nick from a different address, you\n" + "need to send an \002IDENTIFY\002 command to make %s\n" + "recognize you.\n" + " \n" + "Examples:\n" + " \n" + " \002ACCESS ADD anyone@*.bepeg.com\002\n" + " Allows access to user \002anyone\002 from any machine in\n" + " the \002bepeg.com\002 domain.\n" + " \n" + " \002ACCESS DEL anyone@*.bepeg.com\002\n" + " Reverses the previous command.\n" + " \n" + " \002ACCESS LIST\002\n" + " Displays the current access list."), Config->NickServ.c_str(), Config->NickServ.c_str()); + return true; + } +}; + +class NSAccess : public Module +{ + CommandNSAccess commandnsaccess; + + public: + NSAccess(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandnsaccess(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandnsaccess); + } +}; + +MODULE_INIT(NSAccess) diff --git a/modules/commands/ns_ajoin.cpp b/modules/commands/ns_ajoin.cpp new file mode 100644 index 000000000..4f35f28b8 --- /dev/null +++ b/modules/commands/ns_ajoin.cpp @@ -0,0 +1,239 @@ +/* NickServ core 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 "module.h" + +class CommandNSAJoin : public Command +{ + void DoList(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + std::vector<std::pair<Anope::string, Anope::string> > channels; + source.u->Account()->GetExtRegular("ns_ajoin_channels", channels); + + if (channels.empty()) + source.Reply(_("Your auto join list is empty.")); + else + { + source.Reply(_("Your auto join list:\n" + " Num Channel Key")); + for (unsigned i = 0; i < channels.size(); ++i) + source.Reply(" %3d %-12s %s", i + 1, channels[i].first.c_str(), channels[i].second.c_str()); + } + } + + void DoAdd(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + std::vector<std::pair<Anope::string, Anope::string> > channels; + source.u->Account()->GetExtRegular("ns_ajoin_channels", channels); + + if (channels.size() >= Config->AJoinMax) + source.Reply(_("Your auto join list is full.")); + else if (ircdproto->IsChannelValid(params[1]) == false) + source.Reply(CHAN_X_INVALID, params[1].c_str()); + else + { + channels.push_back(std::make_pair(params[1], params.size() > 2 ? params[2] : "")); + source.Reply(_("Added %s to your auto join list."), params[1].c_str()); + source.u->Account()->Extend("ns_ajoin_channels", new ExtensibleItemRegular<std::vector< + std::pair<Anope::string, Anope::string> > >(channels)); + } + } + + void DoDel(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + std::vector<std::pair<Anope::string, Anope::string> > channels; + source.u->Account()->GetExtRegular("ns_ajoin_channels", channels); + + unsigned i; + for (i = 0; i < channels.size(); ++i) + if (channels[i].first.equals_ci(params[1])) + break; + + if (i == channels.size()) + source.Reply(_("%s was not found on your auto join list."), params[1].c_str()); + else + { + channels.erase(channels.begin() + i); + source.u->Account()->Extend("ns_ajoin_channels", new ExtensibleItemRegular<std::vector< + std::pair<Anope::string, Anope::string> > >(channels)); + source.Reply(_("%s was removed from your auto join list."), params[1].c_str()); + } + } + + public: + CommandNSAJoin(Module *creator) : Command(creator, "nickserv/ajoin", 1, 3) + { + this->SetDesc(_("Manage your auto join list")); + this->SetSyntax(_("{ADD | DEL | LIST} [\037channel\037] [\037key\037]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + if (params[0].equals_ci("LIST")) + this->DoList(source, params); + else if (params.size() < 2) + this->OnSyntaxError(source, ""); + else if (params[0].equals_ci("ADD")) + this->DoAdd(source, params); + else if (params[0].equals_ci("DEL")) + this->DoDel(source, params); + else + this->OnSyntaxError(source, ""); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("This command manages your auto join list. When you identify\n" + "you will automatically join the channels on your auto join list")); + return true; + } +}; + +class NSAJoin : public Module +{ + CommandNSAJoin commandnsajoin; + + public: + NSAJoin(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandnsajoin(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandnsajoin); + + Implementation i[] = { I_OnNickIdentify, I_OnDatabaseWriteMetadata, I_OnDatabaseReadMetadata }; + ModuleManager::Attach(i, this, sizeof(i) / sizeof(Implementation)); + } + + void OnNickIdentify(User *u) + { + std::vector<std::pair<Anope::string, Anope::string> > channels; + u->Account()->GetExtRegular("ns_ajoin_channels", channels); + + for (unsigned i = 0; i < channels.size(); ++i) + { + Channel *c = findchan(channels[i].first); + ChannelInfo *ci = c != NULL ? c->ci : cs_findchan(channels[i].first); + if (c == NULL && ci != NULL) + c = ci->c; + + bool need_invite = false; + Anope::string key = channels[i].second; + + if (ci != NULL) + { + if (ci->HasFlag(CI_SUSPENDED)) + continue; + } + if (c != NULL) + { + if (c->FindUser(u) != NULL) + continue; + else if (c->HasMode(CMODE_OPERONLY) && !u->HasMode(UMODE_OPER)) + continue; + else if (c->HasMode(CMODE_ADMINONLY) && !u->HasMode(UMODE_ADMIN)) + continue; + else if (c->HasMode(CMODE_SSL) && !u->HasMode(UMODE_SSL)) + continue; + else if (matches_list(c, u, CMODE_BAN) == true && matches_list(c, u, CMODE_EXCEPT) == false) + need_invite = true; + else if (c->HasMode(CMODE_INVITE) && matches_list(c, u, CMODE_INVITEOVERRIDE) == false) + need_invite = true; + + if (c->HasMode(CMODE_KEY)) + { + Anope::string k; + if (c->GetParam(CMODE_KEY, k)) + { + if (ci->HasPriv(u, CA_GETKEY)) + key = k; + else if (key != k) + need_invite = true; + } + } + if (c->HasMode(CMODE_LIMIT)) + { + Anope::string l; + if (c->GetParam(CMODE_LIMIT, l)) + { + try + { + unsigned limit = convertTo<unsigned>(l); + if (c->users.size() >= limit) + need_invite = true; + } + catch (const ConvertException &) { } + } + } + } + + if (need_invite) + { + BotInfo *bi = findbot(Config->NickServ); + if (!bi || !ci->HasPriv(u, CA_INVITE)) + continue; + ircdproto->SendInvite(bi, channels[i].first, u->nick); + } + + ircdproto->SendSVSJoin(Config->NickServ, u->nick, channels[i].first, key); + } + } + + void OnDatabaseWriteMetadata(void (*WriteMetadata)(const Anope::string &, const Anope::string &), NickCore *nc) + { + std::vector<std::pair<Anope::string, Anope::string> > channels; + nc->GetExtRegular("ns_ajoin_channels", channels); + + Anope::string chans; + for (unsigned i = 0; i < channels.size(); ++i) + chans += " " + channels[i].first + "," + channels[i].second; + + if (!chans.empty()) + { + chans.erase(chans.begin()); + WriteMetadata("NS_AJOIN", chans); + } + } + + EventReturn OnDatabaseReadMetadata(NickCore *nc, const Anope::string &key, const std::vector<Anope::string> ¶ms) + { + if (key == "NS_AJOIN") + { + std::vector<std::pair<Anope::string, Anope::string> > channels; + nc->GetExtRegular("ns_ajoin_channels", channels); + + for (unsigned i = 0; i < params.size(); ++i) + { + Anope::string chan, chankey; + commasepstream sep(params[i]); + sep.GetToken(chan); + sep.GetToken(chankey); + + channels.push_back(std::make_pair(chan, chankey)); + } + + nc->Extend("ns_ajoin_channels", new ExtensibleItemRegular<std::vector< + std::pair<Anope::string, Anope::string> > >(channels)); + + return EVENT_STOP; + } + + return EVENT_CONTINUE; + } +}; + +MODULE_INIT(NSAJoin) diff --git a/modules/commands/ns_alist.cpp b/modules/commands/ns_alist.cpp new file mode 100644 index 000000000..f502b4344 --- /dev/null +++ b/modules/commands/ns_alist.cpp @@ -0,0 +1,96 @@ +/* NickServ core 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 "module.h" + +class CommandNSAList : public Command +{ + public: + CommandNSAList(Module *creator) : Command(creator, "nickserv/alist", 0, 2) + { + this->SetDesc(_("List channels you have access on")); + this->SetSyntax(_("[\037nickname\037]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + Anope::string nick = u->Account()->display; + + if (params.size() && u->IsServicesOper()) + nick = params[0]; + + NickAlias *na = findnick(nick); + + if (!na) + source.Reply(NICK_X_NOT_REGISTERED, nick.c_str()); + else + { + int chan_count = 0; + + source.Reply(_("Channels that \002%s\002 has access on:\n" + " Num Channel Level"), na->nick.c_str()); + + for (registered_channel_map::const_iterator it = RegisteredChannelList.begin(), it_end = RegisteredChannelList.end(); it != it_end; ++it) + { + ChannelInfo *ci = it->second; + + AccessGroup access = ci->AccessFor(na->nc); + if (access.empty()) + continue; + + ++chan_count; + + if (access.size() > 1) + { + source.Reply(_(" %3d You match %d access entries on %c%s, they are"), chan_count, access.size(), ci->HasFlag(CI_NO_EXPIRE) ? '!' : ' ', ci->name.c_str()); + for (unsigned i = 0; i < access.size(); ++i) + source.Reply(_(" %3d %-8s"), i + 1, access[i]->Serialize().c_str()); + } + else + source.Reply(_(" %3d %c%-20s %-8s"), chan_count, ci->HasFlag(CI_NO_EXPIRE) ? '!' : ' ', ci->name.c_str(), access[0]->Serialize().c_str()); + } + + source.Reply(_("End of list - %d channels shown."), chan_count); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Lists all channels you have access on.\n" + " \n" + "Channels that have the \037NOEXPIRE\037 option set will be\n" + "prefixed by an exclamation mark. The nickname parameter is\n" + "limited to Services Operators")); + + return true; + } +}; + +class NSAList : public Module +{ + CommandNSAList commandnsalist; + + public: + NSAList(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandnsalist(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandnsalist); + } +}; + +MODULE_INIT(NSAList) diff --git a/modules/commands/ns_cert.cpp b/modules/commands/ns_cert.cpp new file mode 100644 index 000000000..fbd75d597 --- /dev/null +++ b/modules/commands/ns_cert.cpp @@ -0,0 +1,231 @@ +/* NickServ core 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 "module.h" + +class CommandNSCert : public Command +{ + private: + void DoServAdminList(CommandSource &source, NickCore *nc) + { + if (nc->cert.empty()) + { + source.Reply(_("Certificate list for \002%s\002 is empty."), nc->display.c_str()); + return; + } + + if (nc->HasFlag(NI_SUSPENDED)) + { + source.Reply(NICK_X_SUSPENDED, nc->display.c_str()); + return; + } + + source.Reply(_("Certificate list for \002%s\002:"), nc->display.c_str()); + for (unsigned i = 0, end = nc->cert.size(); i < end; ++i) + { + Anope::string fingerprint = nc->GetCert(i); + source.Reply(" %s", fingerprint.c_str()); + } + + return; + } + + void DoAdd(CommandSource &source, NickCore *nc, const Anope::string &mask) + { + + if (nc->cert.size() >= Config->NSAccessMax) + { + source.Reply(_("Sorry, you can only have %d certificate entries for a nickname."), Config->NSAccessMax); + return; + } + + if (!source.u->fingerprint.empty() && !nc->FindCert(source.u->fingerprint)) + { + nc->AddCert(source.u->fingerprint); + source.Reply(_("\002%s\002 added to your certificate list."), source.u->fingerprint.c_str()); + return; + } + + if (mask.empty()) + { + this->OnSyntaxError(source, "ADD"); + return; + } + + if (nc->FindCert(mask)) + { + source.Reply(_("Fingerprint \002%s\002 already present on your certificate list."), mask.c_str()); + return; + } + + nc->AddCert(mask); + source.Reply(_("\002%s\002 added to your certificate list."), mask.c_str()); + return; + } + + void DoDel(CommandSource &source, NickCore *nc, const Anope::string &mask) + { + + if (!source.u->fingerprint.empty() && nc->FindCert(source.u->fingerprint)) + { + nc->EraseCert(source.u->fingerprint); + source.Reply(_("\002%s\002 deleted from your certificate list."), source.u->fingerprint.c_str()); + return; + } + + if (mask.empty()) + { + this->OnSyntaxError(source, "DEL"); + return; + } + + if (!nc->FindCert(mask)) + { + source.Reply(_("\002%s\002 not found on your certificate list."), mask.c_str()); + return; + } + + source.Reply(_("\002%s\002 deleted from your certificate list."), mask.c_str()); + nc->EraseCert(mask); + + return; + } + + void DoList(CommandSource &source, NickCore *nc) + { + User *u = source.u; + + if (nc->cert.empty()) + { + source.Reply(_("Your certificate list is empty."), u->nick.c_str()); + return; + } + + source.Reply(_("Cert list:")); + for (unsigned i = 0, end = nc->cert.size(); i < end; ++i) + { + Anope::string fingerprint = nc->GetCert(i); + source.Reply(" %s", fingerprint.c_str()); + } + + return; + } + + public: + CommandNSCert(Module *creator) : Command(creator, "nickserv/cert", 1, 2) + { + this->SetDesc("Modify the nickname client certificate list"); + this->SetSyntax("ADD \037fingerprint\037"); + this->SetSyntax("DEL \037fingerprint\037"); + this->SetSyntax("LIST"); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + const Anope::string &cmd = params[0]; + const Anope::string &mask = params.size() > 1 ? params[1] : ""; + + NickAlias *na; + if (cmd.equals_ci("LIST") && u->IsServicesOper() && !mask.empty() && (na = findnick(mask))) + return this->DoServAdminList(source, na->nc); + + if (u->Account()->HasFlag(NI_SUSPENDED)) + source.Reply(NICK_X_SUSPENDED, u->Account()->display.c_str()); + else if (cmd.equals_ci("ADD")) + return this->DoAdd(source, u->Account(), mask); + else if (cmd.equals_ci("DEL")) + return this->DoDel(source, u->Account(), mask); + else if (cmd.equals_ci("LIST")) + return this->DoList(source, u->Account()); + else + this->OnSyntaxError(source, cmd); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Modifies or displays the certificate list for your nick.\n" + "If you connect to IRC and provide a client certificate with a\n" + "matching fingerprint in the cert list, your nick will be\n" + "automatically identified to %s.\n" + " \n"), Config->NickServ.c_str(), Config->NickServ.c_str()); + source.Reply(_("Examples:\n" + " \n" + " \002CERT ADD <fingerprint>\002\n" + " Adds this fingerprint to the certificate list and\n" + " automatically identifies you when you connect to IRC\n" + " using this certificate.\n" + " \n" + " \002CERT DEL <fingerprint>\002\n" + " Reverses the previous command.\n" + " \n" + " \002CERT LIST\002\n" + " Displays the current certificate list."), Config->NickServ.c_str()); + return true; + } +}; + +class NSCert : public Module +{ + CommandNSCert commandnscert; + + void DoAutoIdentify(User *u) + { + BotInfo *bi = findbot(Config->NickServ); + NickAlias *na = findnick(u->nick); + if (!bi || !na) + return; + if (u->IsIdentified() && u->Account() == na->nc) + return; + if (na->nc->HasFlag(NI_SUSPENDED)) + return; + if (!na->nc->FindCert(u->fingerprint)) + return; + + u->Identify(na); + u->SendMessage(bi, _("SSL Fingerprint accepted. You are now identified.")); + return; + } + + public: + NSCert(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandnscert(this) + { + this->SetAuthor("Anope"); + + if (!ircd || !ircd->certfp) + throw ModuleException("Your IRCd does not support ssl client certificates"); + + Implementation i[] = { I_OnUserNickChange, I_OnFingerprint }; + ModuleManager::Attach(i, this, sizeof(i) / sizeof(Implementation)); + + ModuleManager::RegisterService(&commandnscert); + } + + void OnFingerprint(User *u) + { + DoAutoIdentify(u); + } + + void OnUserNickChange(User *u, const Anope::string &oldnick) + { + if (!u->fingerprint.empty()) + DoAutoIdentify(u); + } +}; + +MODULE_INIT(NSCert) diff --git a/modules/commands/ns_drop.cpp b/modules/commands/ns_drop.cpp new file mode 100644 index 000000000..7960652ff --- /dev/null +++ b/modules/commands/ns_drop.cpp @@ -0,0 +1,130 @@ +/* NickServ core 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 "module.h" + +class CommandNSDrop : public Command +{ + public: + CommandNSDrop(Module *creator) : Command(creator, "nickserv/drop", 0, 1) + { + this->SetFlag(CFLAG_ALLOW_UNREGISTERED); + this->SetDesc(_("Cancel the registration of a nickname")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + Anope::string nick = !params.empty() ? params[0] : ""; + + if (readonly) + { + source.Reply(_("Sorry, nickname de-registration is temporarily disabled.")); + return; + } + + NickAlias *na = findnick((u->Account() && !nick.empty() ? nick : u->nick)); + if (!na) + { + source.Reply(NICK_NOT_REGISTERED); + return; + } + + if (!u->Account()) + { + source.Reply(NICK_IDENTIFY_REQUIRED, Config->UseStrictPrivMsgString.c_str(), Config->NickServ.c_str()); + return; + } + + bool is_mine = u->Account() == na->nc; + Anope::string my_nick; + if (is_mine && nick.empty()) + my_nick = na->nick; + + if (!is_mine && !u->HasPriv("nickserv/drop")) + source.Reply(ACCESS_DENIED); + else if (Config->NSSecureAdmins && !is_mine && na->nc->IsServicesOper()) + source.Reply(ACCESS_DENIED); + else + { + if (readonly) + source.Reply(READ_ONLY_MODE); + + FOREACH_MOD(I_OnNickDrop, OnNickDrop(u, na)); + + Log(!is_mine ? LOG_OVERRIDE : LOG_COMMAND, u, this) << "to drop nickname " << na->nick << " (group: " << na->nc->display << ") (email: " << (!na->nc->email.empty() ? na->nc->email : "none") << ")"; + delete na; + + if (!is_mine) + { + source.Reply(_("Nickname \002%s\002 has been dropped."), nick.c_str()); + } + else + { + if (!nick.empty()) + source.Reply(_("Nickname \002%s\002 has been dropped."), nick.c_str()); + else + source.Reply(_("Your nickname has been dropped.")); + } + } + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + User *u = source.u; + if (u->Account() && u->HasPriv("nickserv/drop")) + source.Reply(_("Syntax: \002%s [\037nickname\037]\002\n" + " \n" + "Without a parameter, deletes your nickname.\n" + " \n" + "With a parameter, drops the named nick from the database.\n" + "You may drop any nick within your group without any \n" + "special privileges. Dropping any nick is limited to \n" + "\002Services Operators\002."), source.command.c_str()); + else + source.Reply(_("Syntax: \002%s [\037nickname\037 | \037password\037]\002\n" + " \n" + "Deltes your nickname. A nick\n" + "that has been dropped is free for anyone to re-register.\n" + " \n" + "You may drop a nick within your group by passing it\n" + "as the \002nick\002 parameter.\n" + " \n" + "If you have a nickname registration pending but can not confirm\n" + "it for any reason, you can cancel your registration by passing\n" + "your password as the \002password\002 parameter.\n" + " \n" + "In order to use this command, you must first identify\n" + "with your password."), source.command.c_str()); + + return true; + } +}; + +class NSDrop : public Module +{ + CommandNSDrop commandnsdrop; + + public: + NSDrop(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandnsdrop(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandnsdrop); + } +}; + +MODULE_INIT(NSDrop) diff --git a/modules/commands/ns_getemail.cpp b/modules/commands/ns_getemail.cpp new file mode 100644 index 000000000..fa27e5174 --- /dev/null +++ b/modules/commands/ns_getemail.cpp @@ -0,0 +1,82 @@ +/* NickServ core 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. + * + * A simple call to check for all emails that a user may have registered + * with. It returns the nicks that match the email you provide. Wild + * Cards are not excepted. Must use user@email-host. + */ + +/*************************************************************************/ + +#include "module.h" + +class CommandNSGetEMail : public Command +{ + public: + CommandNSGetEMail(Module *creator) : Command(creator, "nickserv/getemail", 1, 1) + { + this->SetDesc(_("Matches and returns all users that registered using given email")); + this->SetSyntax(_("\037user@email-host\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + const Anope::string &email = params[0]; + int j = 0; + + Log(LOG_ADMIN, u, this) << "on " << email; + + for (nickcore_map::const_iterator it = NickCoreList.begin(), it_end = NickCoreList.end(); it != it_end; ++it) + { + NickCore *nc = it->second; + + if (!nc->email.empty() && nc->email.equals_ci(email)) + { + ++j; + source.Reply(_("Emails Match \002%s\002 to \002%s\002."), nc->display.c_str(), email.c_str()); + } + } + + if (j <= 0) + { + source.Reply(_("No Emails listed for \002%s\002."), email.c_str()); + return; + } + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Returns the matching nicks that used given email. \002Note\002 that\n" + "you can not use wildcards for either user or emailhost. Whenever\n" + "this command is used, a message including the person who issued\n" + "the command and the email it was used on will be logged.")); + return true; + } +}; + +class NSGetEMail : public Module +{ + CommandNSGetEMail commandnsgetemail; + public: + NSGetEMail(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandnsgetemail(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandnsgetemail); + } +}; + +MODULE_INIT(NSGetEMail) diff --git a/modules/commands/ns_getpass.cpp b/modules/commands/ns_getpass.cpp new file mode 100644 index 000000000..9ebea5263 --- /dev/null +++ b/modules/commands/ns_getpass.cpp @@ -0,0 +1,79 @@ +/* NickServ core 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 "module.h" + +class CommandNSGetPass : public Command +{ + public: + CommandNSGetPass(Module *creator) : Command(creator, "nickserv/getpass", 1, 1) + { + this->SetDesc(_("Retrieve the password for a nickname")); + this->SetSyntax(_("\037nickname\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + const Anope::string &nick = params[0]; + Anope::string tmp_pass; + NickAlias *na; + + if (!(na = findnick(nick))) + source.Reply(NICK_X_NOT_REGISTERED, nick.c_str()); + else if (Config->NSSecureAdmins && na->nc->IsServicesOper()) + source.Reply(ACCESS_DENIED); + else + { + if (enc_decrypt(na->nc->pass, tmp_pass) == 1) + { + Log(LOG_ADMIN, u, this) << "for " << nick; + source.Reply(_("Password for %s is \002%s\002."), nick.c_str(), tmp_pass.c_str()); + } + else + source.Reply(_("GETPASS command unavailable because encryption is in use.")); + } + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Returns the password for the given nickname. \002Note\002 that\n" + "whenever this command is used, a message including the\n" + "person who issued the command and the nickname it was used\n" + "on will be logged and sent out as a WALLOPS/GLOBOPS.")); + return true; + } +}; + +class NSGetPass : public Module +{ + CommandNSGetPass commandnsgetpass; + + public: + NSGetPass(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandnsgetpass(this) + { + this->SetAuthor("Anope"); + + Anope::string tmp_pass = "plain:tmp"; + if (enc_decrypt(tmp_pass, tmp_pass) == -1) + throw ModuleException("Incompatible with the encryption module being used"); + + ModuleManager::RegisterService(&commandnsgetpass); + } +}; + +MODULE_INIT(NSGetPass) diff --git a/modules/commands/ns_ghost.cpp b/modules/commands/ns_ghost.cpp new file mode 100644 index 000000000..37dcef9c7 --- /dev/null +++ b/modules/commands/ns_ghost.cpp @@ -0,0 +1,122 @@ +/* NickServ core 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 "module.h" + +class CommandNSGhost : public Command +{ + public: + CommandNSGhost(Module *creator) : Command(creator, "nickserv/ghost", 1, 2) + { + this->SetFlag(CFLAG_ALLOW_UNREGISTERED); + this->SetDesc(_("Disconnects a \"ghost\" IRC session using your nick")); + this->SetSyntax("\037nickname\037 [\037password\037]"); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &nick = params[0]; + const Anope::string &pass = params.size() > 1 ? params[1] : ""; + + User *u = source.u; + User *user = finduser(nick); + NickAlias *na = findnick(nick); + + if (!user) + source.Reply(NICK_X_NOT_IN_USE, nick.c_str()); + else if (!na) + source.Reply(NICK_X_NOT_REGISTERED, nick.c_str()); + else if (na->nc->HasFlag(NI_SUSPENDED)) + source.Reply(NICK_X_SUSPENDED, na->nick.c_str()); + else if (nick.equals_ci(u->nick)) + source.Reply(_("You can't ghost yourself!")); + else + { + bool ok = false; + if (u->Account() == na->nc) + ok = true; + else if (!na->nc->HasFlag(NI_SECURE) && is_on_access(u, na->nc)) + ok = true; + else if (!u->fingerprint.empty() && na->nc->FindCert(u->fingerprint)) + ok = true; + else if (!pass.empty()) + { + EventReturn MOD_RESULT; + FOREACH_RESULT(I_OnCheckAuthentication, OnCheckAuthentication(this, &source, params, na->nc->display, pass)); + if (MOD_RESULT == EVENT_STOP) + return; + else if (MOD_RESULT == EVENT_ALLOW) + ok = true; + } + + if (ok) + { + if (!user->IsIdentified()) + source.Reply(_("You may not ghost an unidentified user, use RECOVER instead.")); + else + { + Log(LOG_COMMAND, u, this) << "for " << nick; + Anope::string buf = "GHOST command used by " + u->nick; + user->Kill(Config->NickServ, buf); + source.Reply(_("Ghost with your nick has been killed."), nick.c_str()); + } + } + else + { + source.Reply(ACCESS_DENIED); + if (!pass.empty()) + { + Log(LOG_COMMAND, u, this) << "with an invalid password for " << nick; + bad_password(u); + } + } + } + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("itermminates a \"ghost\" IRC session using your nick. A\n" + "ghost\" session is one which is not actually connected,\n" + "but which the IRC server believes is still online for one\n" + "reason or another. Typically, this happens if your\n" + "computer crashes or your Internet or modem connection\n" + "goes down while you're on IRC.\n" + " \n" + "In order to use the \002GHOST\002 command for a nick, your\n" + "current address as shown in /WHOIS must be on that nick's\n" + "access list, you must be identified and in the group of\n" + "that nick, or you must supply the correct password for\n" + "the nickname.")); + return true; + } +}; + +class NSGhost : public Module +{ + CommandNSGhost commandnsghost; + + public: + NSGhost(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandnsghost(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandnsghost); + } +}; + +MODULE_INIT(NSGhost) diff --git a/modules/commands/ns_group.cpp b/modules/commands/ns_group.cpp new file mode 100644 index 000000000..2e23e1f51 --- /dev/null +++ b/modules/commands/ns_group.cpp @@ -0,0 +1,305 @@ +/* NickServ core 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 "module.h" + +class CommandNSGroup : public Command +{ + public: + CommandNSGroup(Module *creator) : Command(creator, "nickserv/group", 1, 2) + { + this->SetFlag(CFLAG_ALLOW_UNREGISTERED); + this->SetDesc(_("Join a group")); + this->SetSyntax(_("\037target\037 \037password\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + + const Anope::string &nick = params[0]; + const Anope::string &pass = params.size() > 1 ? params[1] : ""; + + if (readonly) + { + source.Reply(_("Sorry, nickname grouping is temporarily disabled.")); + return; + } + + if (Config->RestrictOperNicks) + for (unsigned i = 0; i < Config->Opers.size(); ++i) + { + Oper *o = Config->Opers[i]; + + if (!u->HasMode(UMODE_OPER) && u->nick.find_ci(o->name) != Anope::string::npos) + { + source.Reply(NICK_CANNOT_BE_REGISTERED, u->nick.c_str()); + return; + } + } + + NickAlias *target, *na = findnick(u->nick); + if (!(target = findnick(nick))) + source.Reply(NICK_X_NOT_REGISTERED, nick.c_str()); + else if (Anope::CurTime < u->lastnickreg + Config->NSRegDelay) + source.Reply(_("Please wait %d seconds before using the GROUP command again."), (Config->NSRegDelay + u->lastnickreg) - Anope::CurTime); + else if (target && target->nc->HasFlag(NI_SUSPENDED)) + { + Log(LOG_COMMAND, u, this) << "tried to use GROUP for SUSPENDED nick " << target->nick; + source.Reply(NICK_X_SUSPENDED, target->nick.c_str()); + } + else if (na && target->nc == na->nc) + source.Reply(_("You are already a member of the group of \002%s\002."), target->nick.c_str()); + else if (na && na->nc != u->Account()) + source.Reply(NICK_IDENTIFY_REQUIRED, Config->UseStrictPrivMsgString.c_str(), Config->NickServ.c_str()); + else if (na && Config->NSNoGroupChange) + source.Reply(_("Your nick is already registered.")); + else if (Config->NSMaxAliases && (target->nc->aliases.size() >= Config->NSMaxAliases) && !target->nc->IsServicesOper()) + source.Reply(_("There are too many nicks in %s's group.")); + else + { + bool ok = false; + if (!na && u->Account()) + ok = true; + else if (!u->fingerprint.empty() && target->nc->FindCert(u->fingerprint)) + ok = true; + else if (!pass.empty()) + { + EventReturn MOD_RESULT; + FOREACH_RESULT(I_OnCheckAuthentication, OnCheckAuthentication(this, &source, params, target->nc->display, pass)); + if (MOD_RESULT == EVENT_STOP) + return; + else if (MOD_RESULT == EVENT_ALLOW) + ok = true; + } + if (ok) + { + /* If the nick is already registered, drop it. + * If not, check that it is valid. + */ + if (na) + delete na; + else + { + size_t prefixlen = Config->NSGuestNickPrefix.length(); + size_t nicklen = u->nick.length(); + + if (nicklen <= prefixlen + 7 && nicklen >= prefixlen + 1 && !u->nick.find_ci(Config->NSGuestNickPrefix) && !u->nick.substr(prefixlen).find_first_not_of("1234567890")) + { + source.Reply(NICK_CANNOT_BE_REGISTERED, u->nick.c_str()); + return; + } + } + + na = new NickAlias(u->nick, target->nc); + + Anope::string last_usermask = u->GetIdent() + "@" + u->GetDisplayedHost(); + na->last_usermask = last_usermask; + na->last_realname = u->realname; + na->time_registered = na->last_seen = Anope::CurTime; + + u->Login(na->nc); + FOREACH_MOD(I_OnNickGroup, OnNickGroup(u, target)); + if (target->nc->HasFlag(NI_UNCONFIRMED) == false) + ircdproto->SendAccountLogin(u, u->Account()); + ircdproto->SetAutoIdentificationToken(u); + + Log(LOG_COMMAND, u, this) << "makes " << u->nick << " join group of " << target->nick << " (" << target->nc->display << ") (email: " << (!target->nc->email.empty() ? target->nc->email : "none") << ")"; + source.Reply(_("You are now in the group of \002%s\002."), target->nick.c_str()); + + u->lastnickreg = Anope::CurTime; + } + else + { + Log(LOG_COMMAND, u, this) << "failed group for " << target->nick; + source.Reply(PASSWORD_INCORRECT); + bad_password(u); + } + } + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("This command makes your nickname join the \037target\037 nickname's \n" + "group. \037password\037 is the password of the target nickname.\n" + " \n" + "Joining a group will allow you to share your configuration,\n" + "memos, and channel privileges with all the nicknames in the\n" + "group, and much more!\n" + " \n" + "A group exists as long as it is useful. This means that even\n" + "if a nick of the group is dropped, you won't lose the\n" + "shared things described above, as long as there is at\n" + "least one nick remaining in the group.\n" + " \n" + "You may be able to use this command even if you have not registered\n" + "your nick yet. If your nick is already registered, you'll\n" + "need to identify yourself before using this command.\n" + " \n" + "It is recommended to use this command with a non-registered\n" + "nick because it will be registered automatically when \n" + "using this command. You may use it with a registered nick (to \n" + "change your group) only if your network administrators allowed \n" + "it.\n" + " \n" + "You can only be in one group at a time. Group merging is\n" + "not possible.\n" + " \n" + "\037Note\037: all the nicknames of a group have the same password.")); + return true; + } +}; + +class CommandNSUngroup : public Command +{ + public: + CommandNSUngroup(Module *creator) : Command(creator, "nickserv/ungroup", 0, 1) + { + this->SetDesc(_("Remove a nick from a group")); + this->SetSyntax(_("[\037nick\037]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + Anope::string nick = !params.empty() ? params[0] : ""; + NickAlias *na = findnick(!nick.empty() ? nick : u->nick); + + if (u->Account()->aliases.size() == 1) + source.Reply(_("Your nick is not grouped to anything, you can't ungroup it.")); + else if (!na) + source.Reply(NICK_X_NOT_REGISTERED, !nick.empty() ? nick.c_str() : u->nick.c_str()); + else if (na->nc != u->Account()) + source.Reply(_("The nick %s is not in your group."), na->nick.c_str()); + else + { + NickCore *oldcore = na->nc; + + std::list<NickAlias *>::iterator it = std::find(oldcore->aliases.begin(), oldcore->aliases.end(), na); + if (it != oldcore->aliases.end()) + oldcore->aliases.erase(it); + + if (na->nick.equals_ci(oldcore->display)) + change_core_display(oldcore); + + na->nc = new NickCore(na->nick); + na->nc->aliases.push_back(na); + + na->nc->pass = oldcore->pass; + if (!oldcore->email.empty()) + na->nc->email = oldcore->email; + if (!oldcore->greet.empty()) + na->nc->greet = oldcore->greet; + na->nc->language = oldcore->language; + + source.Reply(_("Nick %s has been ungrouped from %s."), na->nick.c_str(), oldcore->display.c_str()); + + User *user = finduser(na->nick); + if (user) + /* The user on the nick who was ungrouped may be identified to the old group, set -r */ + user->RemoveMode(findbot(Config->NickServ), UMODE_REGISTERED); + } + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("This command ungroups your nick, or if given, the specificed nick,\n" + "from the group it is in. The ungrouped nick keeps its registration\n" + "time, password, email, greet, language, url, and icq. Everything\n" + "else is reset. You may not ungroup yourself if there is only one\n" + "nick in your group.")); + return true; + } +}; + +class CommandNSGList : public Command +{ + public: + CommandNSGList(Module *creator) : Command(creator, "nickserv/glist", 0, 1) + { + this->SetDesc(_("Lists all nicknames in your group")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + Anope::string nick = !params.empty() ? params[0] : ""; + + const NickCore *nc = u->Account(); + + if (!nick.empty() && (!nick.equals_ci(u->nick) && !u->IsServicesOper())) + source.Reply(ACCESS_DENIED, Config->NickServ.c_str()); + else if (!nick.empty() && (!findnick(nick) || !(nc = findnick(nick)->nc))) + source.Reply(nick.empty() ? NICK_NOT_REGISTERED : _(NICK_X_NOT_REGISTERED), nick.c_str()); + else + { + source.Reply(!nick.empty() ? _("List of nicknames in the group of \002%s\002:") : _("List of nicknames in your group:"), nc->display.c_str()); + for (std::list<NickAlias *>::const_iterator it = nc->aliases.begin(), it_end = nc->aliases.end(); it != it_end; ++it) + { + NickAlias *na2 = *it; + + source.Reply(na2->HasFlag(NS_NO_EXPIRE) || !Config->NSExpire ? _(" %s (does not expire)") : _(" %s (expires in %s)"), na2->nick.c_str(), do_strftime(na2->last_seen + Config->NSExpire).c_str()); + } + source.Reply(_("%d nicknames in the group."), nc->aliases.size()); + } + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + User *u = source.u; + if (u->IsServicesOper()) + source.Reply(_("Syntax: \002%s [\037nickname\037]\002\n" + " \n" + "Without a parameter, lists all nicknames that are in\n" + "your group.\n" + " \n" + "With a parameter, lists all nicknames that are in the\n" + "group of the given nick.\n" + "This use limited to \002Services Operators\002."), + source.command.c_str()); + else + source.Reply(_("Syntax: \002%s\002\n" + " \n" + "Lists all nicks in your group."), source.command.c_str()); + + return true; + } +}; + +class NSGroup : public Module +{ + CommandNSGroup commandnsgroup; + CommandNSUngroup commandnsungroup; + CommandNSGList commandnsglist; + + public: + NSGroup(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandnsgroup(this), commandnsungroup(this), commandnsglist(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandnsgroup); + ModuleManager::RegisterService(&commandnsungroup); + ModuleManager::RegisterService(&commandnsglist); + } +}; + +MODULE_INIT(NSGroup) diff --git a/modules/commands/ns_identify.cpp b/modules/commands/ns_identify.cpp new file mode 100644 index 000000000..b0a375cd2 --- /dev/null +++ b/modules/commands/ns_identify.cpp @@ -0,0 +1,93 @@ +/* NickServ core 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 "module.h" + +class CommandNSIdentify : public Command +{ + public: + CommandNSIdentify(Module *creator) : Command(creator, "nickserv/identify", 1, 2) + { + this->SetFlag(CFLAG_ALLOW_UNREGISTERED); + this->SetDesc(_("Identify yourself with your password")); + this->SetSyntax(_("[\037account\037] \037password\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + + const Anope::string &nick = params.size() == 2 ? params[0] : u->nick; + Anope::string pass = params[params.size() - 1]; + + NickAlias *na = findnick(nick); + if (na && na->nc->HasFlag(NI_SUSPENDED)) + source.Reply(NICK_X_SUSPENDED, na->nick.c_str()); + else if (u->Account() && na && u->Account() == na->nc) + source.Reply(_("You are already identified.")); + else + { + EventReturn MOD_RESULT; + FOREACH_RESULT(I_OnCheckAuthentication, OnCheckAuthentication(this, &source, params, na ? na->nc->display : nick, pass)); + if (MOD_RESULT == EVENT_STOP) + return; + + if (!na) + source.Reply(NICK_X_NOT_REGISTERED, nick.c_str()); + else if (MOD_RESULT != EVENT_ALLOW) + { + Log(LOG_COMMAND, u, this) << "and failed to identify"; + source.Reply(PASSWORD_INCORRECT); + bad_password(u); + } + else + { + if (u->IsIdentified()) + Log(LOG_COMMAND, u, this) << "to log out of account " << u->Account()->display; + + Log(LOG_COMMAND, u, this) << "and identified for account " << na->nc->display; + source.Reply(_("Password accepted - you are now recognized.")); + u->Identify(na); + } + } + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Tells %s that you are really the owner of this\n" + "nick. Many commands require you to authenticate yourself\n" + "with this command before you use them. The password\n" + "should be the same one you sent with the \002REGISTER\002\n" + "command."), source.owner->nick.c_str()); + return true; + } +}; + +class NSIdentify : public Module +{ + CommandNSIdentify commandnsidentify; + + public: + NSIdentify(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandnsidentify(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandnsidentify); + } +}; + +MODULE_INIT(NSIdentify) diff --git a/modules/commands/ns_info.cpp b/modules/commands/ns_info.cpp new file mode 100644 index 000000000..5a6d9dc0c --- /dev/null +++ b/modules/commands/ns_info.cpp @@ -0,0 +1,173 @@ +/* NickServ core 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 "module.h" + +class CommandNSInfo : public Command +{ + private: + // cannot be const, as it is modified + void CheckOptStr(Anope::string &buf, NickCoreFlag opt, const Anope::string &str, NickCore *nc, bool reverse_logic = false) + { + if (reverse_logic ? !nc->HasFlag(opt) : nc->HasFlag(opt)) + { + if (!buf.empty()) + buf += ", "; + + buf += str; + } + } + public: + CommandNSInfo(Module *creator) : Command(creator, "nickserv/info", 0, 2) + { + this->SetFlag(CFLAG_ALLOW_UNREGISTERED); + this->SetDesc(_("Displays information about a given nickname")); + this->SetSyntax(_("[\037nickname\037]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + + const Anope::string &nick = params.size() ? params[0] : (u->Account() ? u->Account()->display : u->nick); + NickAlias *na = findnick(nick); + bool has_auspex = u->IsIdentified() && u->HasPriv("nickserv/auspex"); + + if (!na) + { + if (nickIsServices(nick, true)) + source.Reply(_("Nick \002%s\002 is part of this Network's Services."), nick.c_str()); + else + source.Reply(NICK_X_NOT_REGISTERED, nick.c_str()); + } + else + { + bool nick_online = false, show_hidden = false; + + /* Is the real owner of the nick we're looking up online? -TheShadow */ + User *u2 = finduser(na->nick); + if (u2 && u2->Account() == na->nc) + nick_online = true; + + if (has_auspex || (u->Account() && na->nc == u->Account())) + show_hidden = true; + + source.Reply(_("%s is %s"), na->nick.c_str(), na->last_realname.c_str()); + + if (na->nc->IsServicesOper() && (show_hidden || !na->nc->HasFlag(NI_HIDE_STATUS))) + source.Reply(_("%s is a services operator of type %s."), na->nick.c_str(), na->nc->o->ot->GetName().c_str()); + + if (nick_online) + { + if (show_hidden || !na->nc->HasFlag(NI_HIDE_MASK)) + source.Reply(_(" Is online from: %s"), na->last_usermask.c_str()); + else + source.Reply(_("%s is currently online."), na->nick.c_str()); + } + else + { + if (show_hidden || !na->nc->HasFlag(NI_HIDE_MASK)) + source.Reply(_("Last seen address: %s"), na->last_usermask.c_str()); + } + + source.Reply(_(" Time registered: %s"), do_strftime(na->time_registered).c_str()); + + if (!nick_online) + { + source.Reply(_(" Last seen time: %s"), do_strftime(na->last_seen).c_str()); + } + + if (!na->last_quit.empty() && (show_hidden || !na->nc->HasFlag(NI_HIDE_QUIT))) + source.Reply(_("Last quit message: %s"), na->last_quit.c_str()); + + if (!na->nc->email.empty() && (show_hidden || !na->nc->HasFlag(NI_HIDE_EMAIL))) + source.Reply(_(" E-mail address: %s"), na->nc->email.c_str()); + + if (show_hidden) + { + if (na->hostinfo.HasVhost()) + { + if (ircd->vident && !na->hostinfo.GetIdent().empty()) + source.Reply(_(" vhost: %s@%s"), na->hostinfo.GetIdent().c_str(), na->hostinfo.GetHost().c_str()); + else + source.Reply(_(" vhost: %s"), na->hostinfo.GetHost().c_str()); + } + if (!na->nc->greet.empty()) + source.Reply(_(" Greet message: %s"), na->nc->greet.c_str()); + + Anope::string optbuf; + + CheckOptStr(optbuf, NI_KILLPROTECT, _("Protection"), na->nc); + CheckOptStr(optbuf, NI_SECURE, _("Security"), na->nc); + CheckOptStr(optbuf, NI_PRIVATE, _("Private"), na->nc); + CheckOptStr(optbuf, NI_MSG, _("Message mode"), na->nc); + CheckOptStr(optbuf, NI_AUTOOP, _("Auto-op"), na->nc); + + source.Reply(NICK_INFO_OPTIONS, optbuf.empty() ? _("None") : optbuf.c_str()); + + if (na->nc->HasFlag(NI_SUSPENDED)) + { + if (!na->last_quit.empty()) + source.Reply(_("This nickname is currently suspended, reason: %s"), na->last_quit.c_str()); + else + source.Reply(_("This nickname is currently suspended")); + } + + if (na->nc->HasFlag(NI_UNCONFIRMED) == false) + { + if (na->HasFlag(NS_NO_EXPIRE) || !Config->NSExpire) + source.Reply(_("This nickname will not expire.")); + else + source.Reply(_("Expires on: %s"), do_strftime(na->last_seen + Config->NSExpire).c_str()); + } + else + source.Reply(_("Expires on: %s"), do_strftime(na->time_registered + Config->NSUnconfirmedExpire).c_str()); + } + + FOREACH_MOD(I_OnNickInfo, OnNickInfo(source, na, show_hidden)); + + if (na->nc->HasFlag(NI_UNCONFIRMED)) + source.Reply(_("This nickname is unconfirmed.")); + } + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Displays information about the given nickname, such as\n" + "the nick's owner, last seen address and time, and nick\n" + "options. If no nick is given, and you are identified,\n" + "your account name is used, else your current nickname is\n" + "used.")); + + return true; + } +}; + +class NSInfo : public Module +{ + CommandNSInfo commandnsinfo; + + public: + NSInfo(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandnsinfo(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandnsinfo); + } +}; + +MODULE_INIT(NSInfo) diff --git a/modules/commands/ns_list.cpp b/modules/commands/ns_list.cpp new file mode 100644 index 000000000..96049ba02 --- /dev/null +++ b/modules/commands/ns_list.cpp @@ -0,0 +1,169 @@ +/* NickServ core 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 "module.h" + +class CommandNSList : public Command +{ + public: + CommandNSList(Module *creator) : Command(creator, "nickserv/list", 1, 2) + { + this->SetDesc(_("List all registered nicknames that match a given pattern")); + this->SetSyntax(_("\037pattern\037 [SUSPENDED] [NOEXPIRE] [UNCONFIRMED]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + + Anope::string pattern = params[0]; + const NickCore *mync; + unsigned nnicks; + bool is_servadmin = u->IsServicesOper(); + char noexpire_char = ' '; + int count = 0, from = 0, to = 0; + bool suspended, nsnoexpire, unconfirmed; + + suspended = nsnoexpire = unconfirmed = false; + + if (pattern[0] == '#') + { + Anope::string n1 = myStrGetToken(pattern.substr(1), '-', 0), /* Read FROM out */ + n2 = myStrGetToken(pattern, '-', 1); + + try + { + from = convertTo<int>(n1); + to = convertTo<int>(n2); + } + catch (const ConvertException &) + { + source.Reply(LIST_INCORRECT_RANGE); + return; + } + + pattern = "*"; + } + + nnicks = 0; + + if (is_servadmin && params.size() > 1) + { + Anope::string keyword; + spacesepstream keywords(params[1]); + while (keywords.GetToken(keyword)) + { + if (keyword.equals_ci("NOEXPIRE")) + nsnoexpire = true; + if (keyword.equals_ci("SUSPENDED")) + suspended = true; + if (keyword.equals_ci("UNCONFIRMED")) + unconfirmed = true; + } + } + + mync = u->Account(); + + source.Reply(LIST_HEADER, pattern.c_str()); + for (nickalias_map::const_iterator it = NickAliasList.begin(), it_end = NickAliasList.end(); it != it_end; ++it) + { + NickAlias *na = it->second; + + /* Don't show private nicks to non-services admins. */ + if (na->nc->HasFlag(NI_PRIVATE) && !is_servadmin && na->nc != mync) + continue; + else if (nsnoexpire && !na->HasFlag(NS_NO_EXPIRE)) + continue; + else if (suspended && !na->nc->HasFlag(NI_SUSPENDED)) + continue; + else if (unconfirmed && !na->nc->HasFlag(NI_UNCONFIRMED)) + continue; + + /* We no longer compare the pattern against the output buffer. + * Instead we build a nice nick!user@host buffer to compare. + * The output is then generated separately. -TheShadow */ + Anope::string buf = Anope::printf("%s!%s", na->nick.c_str(), !na->last_usermask.empty() ? na->last_usermask.c_str() : "*@*"); + if (na->nick.equals_ci(pattern) || Anope::Match(buf, pattern)) + { + if (((count + 1 >= from && count + 1 <= to) || (!from && !to)) && ++nnicks <= Config->NSListMax) + { + if (is_servadmin && na->HasFlag(NS_NO_EXPIRE)) + noexpire_char = '!'; + else + noexpire_char = ' '; + if (na->nc->HasFlag(NI_HIDE_MASK) && !is_servadmin && na->nc != mync) + buf = Anope::printf("%-20s [Hostname Hidden]", na->nick.c_str()); + else if (na->nc->HasFlag(NI_SUSPENDED)) + buf = Anope::printf("%-20s [Suspended]", na->nick.c_str()); + else if (na->nc->HasFlag(NI_UNCONFIRMED)) + buf = Anope::printf("%-20s [Unconfirmed]", na->nick.c_str()); + else + buf = Anope::printf("%-20s %s", na->nick.c_str(), na->last_usermask.c_str()); + source.Reply(" %c%s", noexpire_char, buf.c_str()); + } + ++count; + } + } + + + source.Reply(_("End of list - %d/%d matches shown."), nnicks > Config->NSListMax ? Config->NSListMax : nnicks, nnicks); + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Lists all registered nicknames which match the given\n" + "pattern, in \037nick!user@host\037 format. Nicks with the \002PRIVATE\002\n" + "option set will only be displayed to Services Operators. Nicks\n" + "with the \002NOEXPIRE\002 option set will have a \002!\002 appended to\n" + "the nickname for Services Operators.\n" + " \n" + "If the SUSPENDED, NOEXPIRE or UNCONFIRMED options are given, only\n" + "nicks which, respectively, are SUSPENDED, UNCONFIRMED or have the\n" + "NOEXPIRE flag set will be displayed. If multiple options are\n" + "given, all nicks matching at least one option will be displayed.\n" + "These options are limited to \037Services Operators\037. \n" + "\n" + "Examples:\n" + " \n" + " \002LIST *!joeuser@foo.com\002\n" + " Lists all registered nicks owned by joeuser@foo.com.\n" + " \n" + " \002LIST *Bot*!*@*\002\n" + " Lists all registered nicks with \002Bot\002 in their\n" + " names (case insensitive).\n" + " \n" + " \002LIST * NOEXPIRE\002\n" + " Lists all registered nicks which have been set to not expire.\n")); + + return true; + } +}; + +class NSList : public Module +{ + CommandNSList commandnslist; + + public: + NSList(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandnslist(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandnslist); + } +}; + +MODULE_INIT(NSList) diff --git a/modules/commands/ns_logout.cpp b/modules/commands/ns_logout.cpp new file mode 100644 index 000000000..476330a11 --- /dev/null +++ b/modules/commands/ns_logout.cpp @@ -0,0 +1,97 @@ +/* NickServ core 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 "module.h" +#include "nickserv.h" + +class CommandNSLogout : public Command +{ + public: + CommandNSLogout(Module *creator) : Command(creator, "nickserv/logout", 0, 2) + { + this->SetDesc(_("Reverses the effect of the IDENTIFY command")); + this->SetSyntax(_("[\037nickname\037 [REVALIDATE]]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + + const Anope::string &nick = !params.empty() ? params[0] : ""; + const Anope::string ¶m = params.size() > 1 ? params[1] : ""; + + User *u2; + if (!u->IsServicesOper() && !nick.empty()) + this->OnSyntaxError(source, ""); + else if (!(u2 = (!nick.empty() ? finduser(nick) : u))) + source.Reply(NICK_X_NOT_IN_USE, nick.c_str()); + else if (!nick.empty() && !u2->IsServicesOper()) + source.Reply(_("You can't logout %s because they are a Services Operator."), nick.c_str()); + else + { + if (!nick.empty() && !param.empty() && param.equals_ci("REVALIDATE") && nickserv) + nickserv->Validate(u2); + + u2->isSuperAdmin = 0; /* Dont let people logout and remain a SuperAdmin */ + Log(LOG_COMMAND, u, this) << "to logout " << u2->nick; + + /* Remove founder status from this user in all channels */ + if (!nick.empty()) + source.Reply(_("Nick %s has been logged out."), nick.c_str()); + else + source.Reply(_("Your nick has been logged out.")); + + ircdproto->SendAccountLogout(u2, u2->Account()); + u2->RemoveMode(source.owner, UMODE_REGISTERED); + ircdproto->SendUnregisteredNick(u2); + + u2->Logout(); + + /* Send out an event */ + FOREACH_MOD(I_OnNickLogout, OnNickLogout(u2)); + } + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Without a parameter, reverses the effect of the \002IDENTIFY\002 \n" + "command, i.e. make you not recognized as the real owner of the nick\n" + "anymore. Note, however, that you won't be asked to reidentify\n" + "yourself.\n" + " \n" + "With a parameter, does the same for the given nick. If you \n" + "specify REVALIDATE as well, Services will ask the given nick\n" + "to re-identify. This use limited to \002Services Operators\002.")); + + return true; + } +}; + +class NSLogout : public Module +{ + CommandNSLogout commandnslogout; + + public: + NSLogout(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandnslogout(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandnslogout); + } +}; + +MODULE_INIT(NSLogout) diff --git a/modules/commands/ns_recover.cpp b/modules/commands/ns_recover.cpp new file mode 100644 index 000000000..5e45aab9a --- /dev/null +++ b/modules/commands/ns_recover.cpp @@ -0,0 +1,132 @@ +/* NickServ core 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 "module.h" + +class CommandNSRecover : public Command +{ + public: + CommandNSRecover(Module *creator) : Command(creator, "nickserv/recover", 1, 2) + { + this->SetFlag(CFLAG_ALLOW_UNREGISTERED); + this->SetDesc(_("Kill another user who has taken your nick")); + this->SetSyntax(_("\037nickname\037 [\037password\037]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + + const Anope::string &nick = params[0]; + const Anope::string &pass = params.size() > 1 ? params[1] : ""; + + NickAlias *na; + User *u2; + if (!(u2 = finduser(nick))) + source.Reply(NICK_X_NOT_IN_USE, nick.c_str()); + else if (!(na = findnick(u2->nick))) + source.Reply(NICK_X_NOT_REGISTERED, nick.c_str()); + else if (na->nc->HasFlag(NI_SUSPENDED)) + source.Reply(NICK_X_SUSPENDED, na->nick.c_str()); + else if (nick.equals_ci(u->nick)) + source.Reply(_("You can't recover yourself!")); + else if (!pass.empty()) + { + EventReturn MOD_RESULT; + FOREACH_RESULT(I_OnCheckAuthentication, OnCheckAuthentication(this, &source, params, na->nc->display, pass)); + if (MOD_RESULT == EVENT_STOP) + return; + + if (MOD_RESULT == EVENT_ALLOW) + { + u2->SendMessage(source.owner, FORCENICKCHANGE_NOW); + u2->Collide(na); + + /* Convert Config->NSReleaseTimeout seconds to string format */ + Anope::string relstr = duration(Config->NSReleaseTimeout); + + source.Reply(NICK_RECOVERED, Config->UseStrictPrivMsgString.c_str(), Config->NickServ.c_str(), nick.c_str(), relstr.c_str()); + } + else + { + source.Reply(ACCESS_DENIED); + Log(LOG_COMMAND, u, this) << "with invalid password for " << nick; + bad_password(u); + } + } + else + { + if (u->Account() == na->nc || (!na->nc->HasFlag(NI_SECURE) && is_on_access(u, na->nc)) || + (!u->fingerprint.empty() && na->nc->FindCert(u->fingerprint))) + { + u2->SendMessage(source.owner, FORCENICKCHANGE_NOW); + u2->Collide(na); + + /* Convert Config->NSReleaseTimeout seconds to string format */ + Anope::string relstr = duration(Config->NSReleaseTimeout); + + source.Reply(NICK_RECOVERED, Config->UseStrictPrivMsgString.c_str(), Config->NickServ.c_str(), nick.c_str(), relstr.c_str()); + } + else + source.Reply(ACCESS_DENIED); + } + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + /* Convert Config->NSReleaseTimeout seconds to string format */ + Anope::string relstr = duration(Config->NSReleaseTimeout); + + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Allows you to recover your nickname if someone else has\n" + "taken it; this does the same thing that %s does\n" + "automatically if someone tries to use a kill-protected\n" + "nick.\n" + " \n" + "When you give this command, %s will bring a fake\n" + "user online with the same nickname as the user you're\n" + "trying to recover your nick from. This causes the IRC\n" + "servers to disconnect the other user. This fake user will\n" + "remain online for %s to ensure that the other\n" + "user does not immediately reconnect; after that time, you\n" + "can reclaim your nick. Alternatively, use the \002RELEASE\002\n" + "command (\002%s%s HELP RELEASE\002) to get the nick\n" + "back sooner.\n" + " \n" + "In order to use the \002RECOVER\002 command for a nick, your\n" + "current address as shown in /WHOIS must be on that nick's\n" + "access list, you must be identified and in the group of\n" + "that nick, or you must supply the correct password for\n" + "the nickname."), Config->NickServ.c_str(), Config->NickServ.c_str(), relstr.c_str(), Config->UseStrictPrivMsgString.c_str(), Config->NickServ.c_str()); + + return true; + } +}; + +class NSRecover : public Module +{ + CommandNSRecover commandnsrecover; + + public: + NSRecover(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandnsrecover(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandnsrecover); + } +}; + +MODULE_INIT(NSRecover) diff --git a/modules/commands/ns_register.cpp b/modules/commands/ns_register.cpp new file mode 100644 index 000000000..552551994 --- /dev/null +++ b/modules/commands/ns_register.cpp @@ -0,0 +1,355 @@ +/* NickServ core 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 "module.h" + +static bool SendRegmail(User *u, NickAlias *na, BotInfo *bi); + +class CommandNSConfirm : public Command +{ + public: + CommandNSConfirm(Module *creator) : Command(creator, "nickserv/confirm", 1, 2) + { + this->SetFlag(CFLAG_ALLOW_UNREGISTERED); + this->SetDesc(_("Confirm an auth code")); + this->SetSyntax(_("\037passcode\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + const Anope::string &passcode = params[0]; + + if (u->Account() && u->HasPriv("nickserv/confirm")) + { + NickAlias *na = findnick(passcode); + if (na == NULL) + source.Reply(NICK_X_NOT_REGISTERED, passcode.c_str()); + else if (na->nc->HasFlag(NI_UNCONFIRMED) == false) + source.Reply(_("Nick \002%s\002 is already confirmed."), na->nick.c_str()); + else + { + na->nc->UnsetFlag(NI_UNCONFIRMED); + Log(LOG_ADMIN, u, this) << "to confirm nick " << na->nick << " (" << na->nc->display << ")"; + source.Reply(_("Nick \002%s\002 has been confirmed."), na->nick.c_str()); + } + } + else if (u->Account()) + { + Anope::string code; + if (u->Account()->GetExtRegular<Anope::string>("ns_register_passcode", code) && code == passcode) + { + u->Account()->Shrink("ns_register_passcode"); + Log(LOG_COMMAND, u, this) << "to confirm their email"; + source.Reply(_("Your email address of \002%s\002 has been confirmed."), u->Account()->email.c_str()); + u->Account()->UnsetFlag(NI_UNCONFIRMED); + ircdproto->SendAccountLogin(u, u->Account()); + NickAlias *na = findnick(u->nick); + if (na && na->nc == u->Account()) + u->SetMode(findbot(Config->NickServ), UMODE_REGISTERED); + } + else + source.Reply(_("Invalid passcode.")); + } + else + source.Reply(_("Invalid passcode.")); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + User *u = source.u; + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("This command is used by several commands as a way to confirm\n" + "changes made to your account.\n" + " \n" + "This is most commonly used to confirm your email address once\n" + "you register or change it.\n" + " \n" + "This is also used after the RESETPASS command has been used to\n" + "force identify you to your nick so you may change your password.")); + if (u->Account() && u->HasPriv("nickserv/confirm")) + source.Reply(_("Additionally, Services Operators with the \037nickserv/confirm\037 permission can\n" + "replace \037passcode\037 with a users nick to force validate them.")); + return true; + } + + void OnSyntaxError(CommandSource &source, const Anope::string &subcommand) + { + source.Reply(NICK_CONFIRM_INVALID); + } +}; + +class CommandNSRegister : public Command +{ + public: + CommandNSRegister(Module *creator) : Command(creator, "nickserv/register", 1, 2) + { + this->SetFlag(CFLAG_ALLOW_UNREGISTERED); + this->SetDesc(_("Register a nickname")); + if (Config->NSForceEmail) + this->SetSyntax(_("\037password\037 \037email\037")); + else + this->SetSyntax(_("\037password\037 \037[email]\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + NickAlias *na; + size_t prefixlen = Config->NSGuestNickPrefix.length(); + size_t nicklen = u->nick.length(); + Anope::string pass = params[0]; + Anope::string email = params.size() > 1 ? params[1] : ""; + + if (readonly) + { + source.Reply(_("Sorry, nickname registration is temporarily disabled.")); + return; + } + + if (!u->HasMode(UMODE_OPER) && Config->NickRegDelay && Anope::CurTime - u->my_signon < Config->NickRegDelay) + { + source.Reply(_("You must have been using this nick for at least %d seconds to register."), Config->NickRegDelay); + return; + } + + /* Prevent "Guest" nicks from being registered. -TheShadow */ + + /* Guest nick can now have a series of between 1 and 7 digits. + * --lara + */ + if (nicklen <= prefixlen + 7 && nicklen >= prefixlen + 1 && !u->nick.find_ci(Config->NSGuestNickPrefix) && u->nick.substr(prefixlen).find_first_not_of("1234567890") == Anope::string::npos) + { + source.Reply(NICK_CANNOT_BE_REGISTERED, u->nick.c_str()); + return; + } + + if (Config->RestrictOperNicks) + for (unsigned i = 0; i < Config->Opers.size(); ++i) + { + Oper *o = Config->Opers[i]; + + if (!u->HasMode(UMODE_OPER) && u->nick.find_ci(o->name) != Anope::string::npos) + { + source.Reply(NICK_CANNOT_BE_REGISTERED, u->nick.c_str()); + return; + } + } + + if (Config->NSForceEmail && email.empty()) + this->OnSyntaxError(source, ""); + else if (Anope::CurTime < u->lastnickreg + Config->NSRegDelay) + source.Reply(_("Please wait %d seconds before using the REGISTER command again."), (u->lastnickreg + Config->NSRegDelay) - Anope::CurTime); + else if ((na = findnick(u->nick))) + source.Reply(NICK_ALREADY_REGISTERED, u->nick.c_str()); + else if (pass.equals_ci(u->nick) || (Config->StrictPasswords && pass.length() < 5)) + source.Reply(MORE_OBSCURE_PASSWORD); + else if (pass.length() > Config->PassLen) + source.Reply(PASSWORD_TOO_LONG); + else if (!email.empty() && !MailValidate(email)) + source.Reply(MAIL_X_INVALID, email.c_str()); + else + { + na = new NickAlias(u->nick, new NickCore(u->nick)); + enc_encrypt(pass, na->nc->pass); + if (!email.empty()) + na->nc->email = email; + + Anope::string last_usermask = u->GetIdent() + "@" + u->GetDisplayedHost(); + na->last_usermask = last_usermask; + na->last_realname = u->realname; + if (Config->NSAddAccessOnReg) + na->nc->AddAccess(create_mask(u)); + + u->Login(na->nc); + + Log(LOG_COMMAND, u, this) << "to register " << na->nick << " (email: " << (!na->nc->email.empty() ? na->nc->email : "none") << ")"; + + FOREACH_MOD(I_OnNickRegister, OnNickRegister(na)); + + if (Config->NSAddAccessOnReg) + source.Reply(_("Nickname \002%s\002 registered under your account: %s"), u->nick.c_str(), na->nc->GetAccess(0).c_str()); + else + source.Reply(_("Nickname \002%s\002 registered."), u->nick.c_str()); + + Anope::string tmp_pass; + if (enc_decrypt(na->nc->pass, tmp_pass) == 1) + source.Reply(_("Your password is \002%s\002 - remember this for later use."), tmp_pass.c_str()); + + if (Config->NSEmailReg) + { + na->nc->SetFlag(NI_UNCONFIRMED); + if (SendRegmail(u, na, source.owner)) + { + source.Reply(_("A passcode has been sent to %s, please type %s%s confirm <passcode> to confirm your email address."), email.c_str(), Config->UseStrictPrivMsgString.c_str(), Config->NickServ.c_str()); + source.Reply(_("If you do not confirm your email address within %s your account will expire."), duration(Config->NSUnconfirmedExpire).c_str()); + } + } + else + ircdproto->SendAccountLogin(u, u->Account()); + ircdproto->SetAutoIdentificationToken(u); + + u->lastnickreg = Anope::CurTime; + } + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply("\n"); + source.Reply(_("Registers your nickname in the %s database. Once\n" + "your nick is registered, you can use the \002SET\002 and \002ACCESS\002\n" + "commands to configure your nick's settings as you like\n" + "them. Make sure you remember the password you use when\n" + "registering - you'll need it to make changes to your nick\n" + "later. (Note that \002case matters!\002 \037ANOPE\037, \037Anope\037, and \n" + "\037anope\037 are all different passwords!)\n" + " \n" + "Guidelines on choosing passwords:\n" + " \n" + "Passwords should not be easily guessable. For example,\n" + "using your real name as a password is a bad idea. Using\n" + "your nickname as a password is a much worse idea ;) and,\n" + "in fact, %s will not allow it. Also, short\n" + "passwords are vulnerable to trial-and-error searches, so\n" + "you should choose a password at least 5 characters long.\n" + "Finally, the space character cannot be used in passwords.\n"), + Config->NickServ.c_str(), Config->NickServ.c_str()); + + if (!Config->NSForceEmail) + source.Reply(_(" \n" + "The parameter \037email\037 is optional and will set the email\n" + "for your nick immediately.\n" + "Your privacy is respected; this e-mail won't be given to\n" + "any third-party person.\n" + " \n")); + + source.Reply(_("This command also creates a new group for your nickname,\n" + "that will allow you to register other nicks later sharing\n" + "the same configuration, the same set of memos and the\n" + "same channel privileges.")); + return true; + } +}; + +class CommandNSResend : public Command +{ + public: + CommandNSResend(Module *creator) : Command(creator, "nickserv/resend", 0, 0) + { + this->SetSyntax(""); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + if (!Config->NSEmailReg) + return; + + User *u = source.u; + NickAlias *na = findnick(u->nick); + + if (na == NULL) + source.Reply(NICK_NOT_REGISTERED); + else if (na->nc != u->Account() || u->Account()->HasFlag(NI_UNCONFIRMED) == false) + source.Reply(_("Your account is already confirmed.")); + else + { + if (Anope::CurTime < u->Account()->lastmail + Config->NSResendDelay) + source.Reply(_("Cannot send mail now; please retry a little later.")); + else if (!SendRegmail(u, na, source.owner)) + { + na->nc->lastmail = Anope::CurTime; + source.Reply(_("Your passcode has been re-sent to %s."), na->nc->email.c_str()); + Log(LOG_COMMAND, u, this) << "to resend registration verification code"; + } + else + Log() << "Unable to resend registration verification code for " << u->nick; + } + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + if (!Config->NSEmailReg) + return false; + + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("This command will re-send the auth code (also called passcode)\n" + "to the e-mail address of the user whom is performing it.")); + return true; + } + + void OnServHelp(CommandSource &source) + { + if (Config->NSEmailReg) + source.Reply(" %-14s %s", this->name.c_str(),_("Resend the registration passcode")); + } +}; + +class NSRegister : public Module +{ + CommandNSRegister commandnsregister; + CommandNSConfirm commandnsconfirm; + CommandNSResend commandnsrsend; + + public: + NSRegister(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandnsregister(this), commandnsconfirm(this), commandnsrsend(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandnsregister); + ModuleManager::RegisterService(&commandnsconfirm); + ModuleManager::RegisterService(&commandnsrsend); + } +}; + +static bool SendRegmail(User *u, NickAlias *na, BotInfo *bi) +{ + Anope::string code; + if (na->nc->GetExtRegular<Anope::string>("ns_register_passcode", code) == false) + { + int chars[] = { + ' ', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', + 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', + 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', + 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', + 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' + }; + int idx, min = 1, max = 62; + for (idx = 0; idx < 9; ++idx) + code += chars[1 + static_cast<int>((static_cast<float>(max - min)) * getrandom16() / 65536.0) + min]; + na->nc->Extend("ns_register_passcode", new ExtensibleItemRegular<Anope::string>(code)); + } + + Anope::string subject = Anope::printf(_("Nickname Registration (%s)"), na->nick.c_str()); + Anope::string message = Anope::printf(_("Hi,\n" + " \n" + "You have requested to register the nickname %s on %s.\n" + "Please type \" %s%s confirm %s \" to complete registration.\n" + " \n" + "If you don't know why this mail was sent to you, please ignore it silently.\n" + " \n" + "%s administrators."), na->nick.c_str(), Config->NetworkName.c_str(), Config->UseStrictPrivMsgString.c_str(), Config->NickServ.c_str(), code.c_str(), Config->NetworkName.c_str()); + + return Mail(u, na->nc, bi, subject, message); +} + +MODULE_INIT(NSRegister) diff --git a/modules/commands/ns_release.cpp b/modules/commands/ns_release.cpp new file mode 100644 index 000000000..20a3e295c --- /dev/null +++ b/modules/commands/ns_release.cpp @@ -0,0 +1,110 @@ +/* NickServ core 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 "module.h" + +class CommandNSRelease : public Command +{ + public: + CommandNSRelease(Module *creator) : Command(creator, "nickserv/release", 1, 2) + { + this->SetFlag(CFLAG_ALLOW_UNREGISTERED); + this->SetDesc(_("Regain custody of your nick after RECOVER")); + this->SetSyntax(_("\037nickname\037 [\037password\037]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + const Anope::string &nick = params[0]; + Anope::string pass = params.size() > 1 ? params[1] : ""; + NickAlias *na; + + if (!(na = findnick(nick))) + source.Reply(NICK_X_NOT_REGISTERED, nick.c_str()); + else if (na->nc->HasFlag(NI_SUSPENDED)) + source.Reply(NICK_X_SUSPENDED, na->nick.c_str()); + else if (!na->HasFlag(NS_HELD)) + source.Reply(_("Nick \002%s\002 isn't being held."), nick.c_str()); + else if (!pass.empty()) + { + EventReturn MOD_RESULT; + FOREACH_RESULT(I_OnCheckAuthentication, OnCheckAuthentication(this, &source, params, na->nc->display, pass)); + if (MOD_RESULT == EVENT_STOP) + return; + + if (MOD_RESULT == EVENT_ALLOW) + { + Log(LOG_COMMAND, u, this) << "released " << na->nick; + na->Release(); + source.Reply(_("Services' hold on your nick has been released.")); + } + else + { + source.Reply(ACCESS_DENIED); + Log(LOG_COMMAND, u, this) << "invalid password for " << nick; + bad_password(u); + } + } + else + { + if (u->Account() == na->nc || (!na->nc->HasFlag(NI_SECURE) && is_on_access(u, na->nc)) || + (!u->fingerprint.empty() && na->nc->FindCert(u->fingerprint))) + { + na->Release(); + source.Reply(_("Services' hold on your nick has been released.")); + } + else + source.Reply(ACCESS_DENIED); + } + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + /* Convert Config->NSReleaseTimeout seconds to string format */ + Anope::string relstr = duration(Config->NSReleaseTimeout); + + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Instructs %s to remove any hold on your nickname\n" + "caused by automatic kill protection or use of the \002RECOVER\002\n" + "command. This holds lasts for %s;\n" + "This command gets rid of them sooner.\n" + " \n" + "In order to use the \002RELEASE\002 command for a nick, your\n" + "current address as shown in /WHOIS must be on that nick's\n" + "access list, you must be identified and in the group of\n" + "that nick, or you must supply the correct password for\n" + "the nickname."), Config->NickServ.c_str(), relstr.c_str()); + + + return true; + } +}; + +class NSRelease : public Module +{ + CommandNSRelease commandnsrelease; + + public: + NSRelease(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandnsrelease(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandnsrelease); + } +}; + +MODULE_INIT(NSRelease) diff --git a/modules/commands/ns_resetpass.cpp b/modules/commands/ns_resetpass.cpp new file mode 100644 index 000000000..b0d40226f --- /dev/null +++ b/modules/commands/ns_resetpass.cpp @@ -0,0 +1,152 @@ +/* NickServ core 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 "module.h" + +static bool SendResetEmail(User *u, NickAlias *na, BotInfo *bi); + +class CommandNSResetPass : public Command +{ + public: + CommandNSResetPass(Module *creator) : Command(creator, "nickserv/resetpass", 1, 1) + { + this->SetFlag(CFLAG_ALLOW_UNREGISTERED); + this->SetDesc(_("Helps you reset lost passwords")); + this->SetSyntax(_("\037nickname\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + NickAlias *na; + + if (Config->RestrictMail && (!u->Account() || !u->HasCommand("nickserv/nickserv/resetpass"))) + source.Reply(ACCESS_DENIED); + else if (!(na = findnick(params[0]))) + source.Reply(NICK_X_NOT_REGISTERED, params[0].c_str()); + else + { + if (SendResetEmail(u, na, source.owner)) + { + Log(LOG_COMMAND, u, this) << "for " << na->nick << " (group: " << na->nc->display << ")"; + source.Reply(_("Password reset email for \002%s\002 has been sent."), na->nick.c_str()); + } + } + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Sends a code key to the nickname with instructions on how to\n" + "reset their password.")); + return true; + } +}; + +class NSResetPass : public Module +{ + CommandNSResetPass commandnsresetpass; + + public: + NSResetPass(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandnsresetpass(this) + { + this->SetAuthor("Anope"); + + if (!Config->UseMail) + throw ModuleException("Not using mail."); + + ModuleManager::RegisterService(&commandnsresetpass); + + ModuleManager::Attach(I_OnPreCommand, this); + } + + EventReturn OnPreCommand(CommandSource &source, Command *command, std::vector<Anope::string> ¶ms) + { + if (command->name == "nickserv/confirm" && params.size() > 1) + { + User *u = source.u; + NickAlias *na = findnick(params[0]); + + time_t t; + Anope::string c; + if (na && na->nc->GetExtRegular("ns_resetpass_code", c) && na->nc->GetExtRegular("ns_resetpass_time", t)) + { + const Anope::string &passcode = params[1]; + if (t < Anope::CurTime - 3600) + { + na->nc->Shrink("ns_resetpass_code"); + na->nc->Shrink("ns_resetpass_time"); + source.Reply(_("Your password reset request has expired.")); + } + else if (passcode.equals_cs(c)) + { + na->nc->Shrink("ns_resetpass_code"); + na->nc->Shrink("ns_resetpass_time"); + + Log(LOG_COMMAND, u, &commandnsresetpass) << "confirmed RESETPASS to forcefully identify to " << na->nick; + + na->nc->UnsetFlag(NI_UNCONFIRMED); + u->Identify(na); + + source.Reply(_("You are now identified for your nick. Change your passwor now.")); + + } + else + return EVENT_CONTINUE; + + return EVENT_STOP; + } + } + + return EVENT_CONTINUE; + } +}; + +static bool SendResetEmail(User *u, NickAlias *na, BotInfo *bi) +{ + int min = 1, max = 62; + int chars[] = { + ' ', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', + 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', + 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', + 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', + 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' + }; + + Anope::string passcode; + int idx; + for (idx = 0; idx < 20; ++idx) + passcode += chars[1 + static_cast<int>((static_cast<float>(max - min)) * getrandom16() / 65536.0) + min]; + + Anope::string subject = Anope::printf(translate(na->nc, _("Reset password request for %s")), na->nick.c_str()); + Anope::string message = Anope::printf(translate(na->nc, _( + "Hi,\n" + " \n" + "You have requested to have the password for %s reset.\n" + "To reset your password, type %s%s CONFIRM %s %s\n" + " \n" + "If you don't know why this mail was sent to you, please ignore it silently.\n" + " \n" + "%s administrators.")), na->nick.c_str(), Config->UseStrictPrivMsgString.c_str(), Config->NickServ.c_str(), na->nick.c_str(), passcode.c_str(), Config->NetworkName.c_str()); + + na->nc->Extend("ns_resetpass_code", new ExtensibleItemRegular<Anope::string>(passcode)); + na->nc->Extend("ns_resetpass_time", new ExtensibleItemRegular<time_t>(Anope::CurTime)); + + return Mail(u, na->nc, bi, subject, message); +} + +MODULE_INIT(NSResetPass) diff --git a/modules/commands/ns_saset.cpp b/modules/commands/ns_saset.cpp new file mode 100644 index 000000000..a4aa2f422 --- /dev/null +++ b/modules/commands/ns_saset.cpp @@ -0,0 +1,173 @@ +/* NickServ core 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 "module.h" + +class CommandNSSASet : public Command +{ + public: + CommandNSSASet(Module *creator) : Command(creator, "nickserv/saset", 2, 4) + { + this->SetDesc(_("Set SET-options on another nickname")); + this->SetSyntax(_("\037option\037 \037nickname\037 \037parameters\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + this->OnSyntaxError(source, ""); + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(_("Sets various nickname options. \037option\037 can be one of:")); + Anope::string this_name = source.command; + for (BotInfo::command_map::iterator it = source.owner->commands.begin(), it_end = source.owner->commands.end(); it != it_end; ++it) + { + const Anope::string &c_name = it->first; + CommandInfo &info = it->second; + + if (c_name.find_ci(this_name + " ") == 0) + { + service_reference<Command> command(info.name); + if (command) + { + source.command = c_name; + command->OnServHelp(source); + } + } + } + source.Reply(_("Type \002%s%s HELP SASET \037option\037\002 for more information\n" + "on a specific option. The options will be set on the given\n" + "\037nickname\037."), Config->UseStrictPrivMsgString.c_str(), Config->NickServ.c_str()); + return true; + } +}; + +class CommandNSSASetDisplay : public Command +{ + public: + CommandNSSASetDisplay(Module *creator) : Command(creator, "nickserv/saset/display", 2, 2) + { + this->SetDesc(_("Set the display of the group in Services")); + this->SetSyntax(_("\037nickname\037 \037new-display\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + NickAlias *setter_na = findnick(params[0]); + if (setter_na == NULL) + { + source.Reply(NICK_X_NOT_REGISTERED, params[0].c_str()); + return; + } + NickCore *nc = setter_na->nc; + + NickAlias *na = findnick(params[1]); + if (!na || na->nc != nc) + { + source.Reply(_("The new display for \002%s\002 MUST be a nickname of the nickname group!"), nc->display.c_str()); + return; + } + + change_core_display(nc, params[1]); + source.Reply(NICK_SET_DISPLAY_CHANGED, nc->display.c_str()); + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(_("Changes the display used to refer to the nickname group in \n" + "Services. The new display MUST be a nick of the group.")); + return true; + } +}; + +class CommandNSSASetPassword : public Command +{ + public: + CommandNSSASetPassword(Module *creator) : Command(creator, "nickserv/saset/password", 2, 2) + { + this->SetDesc(_("Set the nickname password")); + this->SetSyntax(_("\037nickname\037 \037new-password\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + NickAlias *setter_na = findnick(params[0]); + if (setter_na == NULL) + { + source.Reply(NICK_X_NOT_REGISTERED, params[0].c_str()); + return; + } + NickCore *nc = setter_na->nc; + + size_t len = params[1].length(); + + if (Config->NSSecureAdmins && u->Account() != nc && nc->IsServicesOper()) + { + source.Reply(ACCESS_DENIED); + return; + } + else if (nc->display.equals_ci(params[1]) || (Config->StrictPasswords && len < 5)) + { + source.Reply(MORE_OBSCURE_PASSWORD); + return; + } + else if (len > Config->PassLen) + { + source.Reply(PASSWORD_TOO_LONG); + return; + } + + enc_encrypt(params[1], nc->pass); + Anope::string tmp_pass; + if (enc_decrypt(nc->pass, tmp_pass) == 1) + source.Reply(_("Password for \002%s\002 changed to \002%s\002."), nc->display.c_str(), tmp_pass.c_str()); + else + source.Reply(_("Password for \002%s\002 changed."), nc->display.c_str()); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Changes the password used to identify as the nick's owner.")); + return true; + } +}; + +class NSSASet : public Module +{ + CommandNSSASet commandnssaset; + CommandNSSASetDisplay commandnssasetdisplay; + CommandNSSASetPassword commandnssasetpassword; + + public: + NSSASet(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandnssaset(this), commandnssasetdisplay(this), commandnssasetpassword(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandnssaset); + ModuleManager::RegisterService(&commandnssasetdisplay); + ModuleManager::RegisterService(&commandnssasetpassword); + } +}; + +MODULE_INIT(NSSASet) diff --git a/modules/commands/ns_saset_noexpire.cpp b/modules/commands/ns_saset_noexpire.cpp new file mode 100644 index 000000000..1bff9b461 --- /dev/null +++ b/modules/commands/ns_saset_noexpire.cpp @@ -0,0 +1,76 @@ +/* NickServ core 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 "module.h" + +class CommandNSSASetNoexpire : public Command +{ + public: + CommandNSSASetNoexpire(Module *creator) : Command(creator, "nickserv/saset/noexpire", 1, 2) + { + this->SetDesc(_("Prevent the nickname from expiring")); + this->SetSyntax(_("\037nickname\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + NickAlias *na = findnick(params[0]); + if (na == NULL) + { + source.Reply(NICK_X_NOT_REGISTERED, params[0].c_str()); + return; + } + + Anope::string param = params.size() > 1 ? params[1] : ""; + + if (param.equals_ci("ON")) + { + na->SetFlag(NS_NO_EXPIRE); + source.Reply(_("Nick %s \002will not\002 expire."), na->nick.c_str()); + } + else if (param.equals_ci("OFF")) + { + na->UnsetFlag(NS_NO_EXPIRE); + source.Reply(_("Nick %s \002will\002 expire."), na->nick.c_str()); + } + else + this->OnSyntaxError(source, "NOEXPIRE"); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Sets whether the given nickname will expire. Setting this\n" + "to \002ON\002 prevents the nickname from expiring.")); + return true; + } +}; + +class NSSASetNoexpire : public Module +{ + CommandNSSASetNoexpire commandnssasetnoexpire; + + public: + NSSASetNoexpire(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandnssasetnoexpire(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandnssasetnoexpire); + } +}; + +MODULE_INIT(NSSASetNoexpire) diff --git a/modules/commands/ns_sendpass.cpp b/modules/commands/ns_sendpass.cpp new file mode 100644 index 000000000..ca185c68b --- /dev/null +++ b/modules/commands/ns_sendpass.cpp @@ -0,0 +1,108 @@ +/* NickServ core 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 "module.h" + +static bool SendPassMail(User *u, NickAlias *na, BotInfo *bi, const Anope::string &pass); + +class CommandNSSendPass : public Command +{ + public: + CommandNSSendPass(Module *creator) : Command(creator, "nickserv/sendpass", 1, 1) + { + this->SetFlag(CFLAG_ALLOW_UNREGISTERED); + this->SetDesc(_("Forgot your password? Try this")); + this->SetSyntax(_("\037nickname\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + const Anope::string &nick = params[0]; + NickAlias *na; + + if (Config->RestrictMail && (!u->Account() || !u->HasCommand("nickserv/sendpass"))) + source.Reply(ACCESS_DENIED); + else if (!(na = findnick(nick))) + source.Reply(NICK_X_NOT_REGISTERED, nick.c_str()); + else + { + Anope::string tmp_pass; + if (enc_decrypt(na->nc->pass, tmp_pass) == 1) + { + if (SendPassMail(u, na, source.owner, tmp_pass)) + { + Log(Config->RestrictMail ? LOG_ADMIN : LOG_COMMAND, u, this) << "for " << na->nick; + source.Reply(_("Password of \002%s\002 has been sent."), nick.c_str()); + } + } + else + source.Reply(_("SENDPASS command unavailable because encryption is in use.")); + } + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Send the password of the given nickname to the e-mail address\n" + "set in the nickname record. This command is really useful\n" + "to deal with lost passwords.\n" + " \n" + "May be limited to \002IRC operators\002 on certain networks.")); + return true; + } +}; + +class NSSendPass : public Module +{ + CommandNSSendPass commandnssendpass; + + public: + NSSendPass(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandnssendpass(this) + { + this->SetAuthor("Anope"); + + if (!Config->UseMail) + throw ModuleException("Not using mail."); + + Anope::string tmp_pass = "plain:tmp"; + if (enc_decrypt(tmp_pass, tmp_pass) == -1) + throw ModuleException("Incompatible with the encryption module being used"); + + ModuleManager::RegisterService(&commandnssendpass); + } +}; + +static bool SendPassMail(User *u, NickAlias *na, BotInfo *bi, const Anope::string &pass) +{ + char subject[BUFSIZE], message[BUFSIZE]; + + snprintf(subject, sizeof(subject), translate(na->nc, _("Nickname password (%s)")), na->nick.c_str()); + snprintf(message, sizeof(message), translate(na->nc, _( + "Hi,\n" + " \n" + "You have requested to receive the password of nickname %s by e-mail.\n" + "The password is %s. For security purposes, you should change it as soon as you receive this mail.\n" + " \n" + "If you don't know why this mail was sent to you, please ignore it silently.\n" + " \n" + "%s administrators.")), na->nick.c_str(), pass.c_str(), Config->NetworkName.c_str()); + + return Mail(u, na->nc, bi, subject, message); +} + +MODULE_INIT(NSSendPass) diff --git a/modules/commands/ns_set.cpp b/modules/commands/ns_set.cpp new file mode 100644 index 000000000..ad7acb5a4 --- /dev/null +++ b/modules/commands/ns_set.cpp @@ -0,0 +1,158 @@ +/* NickServ core 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 "module.h" + +class CommandNSSet : public Command +{ + public: + CommandNSSet(Module *creator) : Command(creator, "nickserv/set", 1, 3) + { + this->SetDesc(_("Set options, including kill protection")); + this->SetSyntax(_("\037option\037 \037parameters\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + this->OnSyntaxError(source, ""); + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Sets various nickname options. \037option\037 can be one of:")); + Anope::string this_name = source.command; + for (BotInfo::command_map::iterator it = source.owner->commands.begin(), it_end = source.owner->commands.end(); it != it_end; ++it) + { + const Anope::string &c_name = it->first; + CommandInfo &info = it->second; + + if (c_name.find_ci(this_name + " ") == 0) + { + service_reference<Command> command(info.name); + if (command) + { + source.command = c_name; + command->OnServHelp(source); + } + } + } + source.Reply(_("Type \002%s%s HELP %s \037option\037\002 for more information\n" + "on a specific option."), Config->UseStrictPrivMsgString.c_str(), source.owner->nick.c_str(), source.command.c_str()); + return true; + } +}; + +class CommandNSSetDisplay : public Command +{ + public: + CommandNSSetDisplay(Module *creator) : Command(creator, "nickserv/set/display", 1) + { + this->SetDesc(_("Set the display of your group in Services")); + this->SetSyntax(_("\037new-display\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + NickAlias *na = findnick(params[1]); + + if (!na || na->nc != u->Account()) + { + source.Reply(_("The new display MUST be a nickname of your nickname group!")); + return; + } + + change_core_display(u->Account(), params[1]); + source.Reply(NICK_SET_DISPLAY_CHANGED, u->Account()->display.c_str()); + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Changes the display used to refer to your nickname group in \n" + "Services. The new display MUST be a nick of your group.")); + return true; + } +}; + +class CommandNSSetPassword : public Command +{ + public: + CommandNSSetPassword(Module *creator) : Command(creator, "nickserv/set/password", 1) + { + this->SetDesc(_("Set your nickname password")); + this->SetSyntax(_("\037new-password\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + + const Anope::string ¶m = params[1]; + unsigned len = param.length(); + + if (u->Account()->display.equals_ci(param) || (Config->StrictPasswords && len < 5)) + { + source.Reply(MORE_OBSCURE_PASSWORD); + return; + } + else if (len > Config->PassLen) + { + source.Reply(PASSWORD_TOO_LONG); + return; + } + + enc_encrypt(param, u->Account()->pass); + Anope::string tmp_pass; + if (enc_decrypt(u->Account()->pass, tmp_pass) == 1) + source.Reply(_("Password for \002%s\002 changed to \002%s\002."), u->Account()->display.c_str(), tmp_pass.c_str()); + else + source.Reply(_("Password for \002%s\002 changed."), u->Account()->display.c_str()); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Changes the password used to identify you as the nick's\n" + "owner.")); + return true; + } +}; + +class NSSet : public Module +{ + CommandNSSet commandnsset; + CommandNSSetDisplay commandnssetdisplay; + CommandNSSetPassword commandnssetpassword; + + public: + NSSet(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandnsset(this), commandnssetdisplay(this), commandnssetpassword(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandnsset); + ModuleManager::RegisterService(&commandnssetdisplay); + ModuleManager::RegisterService(&commandnssetpassword); + } +}; + +MODULE_INIT(NSSet) diff --git a/modules/commands/ns_set_autoop.cpp b/modules/commands/ns_set_autoop.cpp new file mode 100644 index 000000000..ff178141f --- /dev/null +++ b/modules/commands/ns_set_autoop.cpp @@ -0,0 +1,107 @@ +/* NickServ core 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 "module.h" + +class CommandNSSetAutoOp : public Command +{ + public: + CommandNSSetAutoOp(Module *creator, const Anope::string &sname = "nickserv/set/autoop", size_t min = 1) : Command(creator, sname, min, min + 1) + { + this->SetDesc(_("Should services op you automatically.")); + this->SetSyntax(_("{ON | OFF}")); + } + + void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) + { + NickAlias *na = findnick(user); + if (na == NULL) + { + source.Reply(NICK_X_NOT_REGISTERED, user.c_str()); + return; + } + NickCore *nc = na->nc; + + if (param.equals_ci("ON")) + { + nc->SetFlag(NI_AUTOOP); + source.Reply(_("Services will now autoop %s in channels."), nc->display.c_str()); + } + else if (param.equals_ci("OFF")) + { + nc->UnsetFlag(NI_AUTOOP); + source.Reply(_("Services will no longer autoop %s in channels."), nc->display.c_str()); + } + else + this->OnSyntaxError(source, "AUTOOP"); + + return; + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + this->Run(source, source.u->Account()->display, params[0]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Sets whether you will be opped automatically. Set to ON to \n" + "allow ChanServ to op you automatically when entering channels.")); + return true; + } +}; + +class CommandNSSASetAutoOp : public CommandNSSetAutoOp +{ + public: + CommandNSSASetAutoOp(Module *creator) : CommandNSSetAutoOp(creator, "nickserv/saset/autoop", 2) + { + this->ClearSyntax(); + this->SetSyntax(_("\037nickname\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + this->Run(source, params[0], params[1]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Sets whether the given nickname will be opped automatically.\n" + "Set to \002ON\002 to allow ChanServ to op the given nickname \n" + "omatically when joining channels.")); + return true; + } +}; + +class NSSetAutoOp : public Module +{ + CommandNSSetAutoOp commandnssetautoop; + CommandNSSASetAutoOp commandnssasetautoop; + + public: + NSSetAutoOp(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandnssetautoop(this), commandnssasetautoop(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandnssetautoop); + ModuleManager::RegisterService(&commandnssasetautoop); + } +}; + +MODULE_INIT(NSSetAutoOp) diff --git a/modules/commands/ns_set_email.cpp b/modules/commands/ns_set_email.cpp new file mode 100644 index 000000000..f0914eb51 --- /dev/null +++ b/modules/commands/ns_set_email.cpp @@ -0,0 +1,186 @@ +/* NickServ core 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 "module.h" + +static bool SendConfirmMail(User *u, BotInfo *bi) +{ + int chars[] = { + ' ', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', + 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', + 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', + 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', + 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' + }; + int idx, min = 1, max = 62; + Anope::string code; + for (idx = 0; idx < 9; ++idx) + code += chars[1 + static_cast<int>((static_cast<float>(max - min)) * getrandom16() / 65536.0) + min]; + u->Account()->Extend("ns_set_email_passcode", new ExtensibleItemRegular<Anope::string>(code)); + + Anope::string subject = _("Email confirmation"); + Anope::string message = Anope::printf(_("Hi,\n" + " \n" + "You have requested to change your email address to %s.\n" + "Please type \" %s%s confirm %s \" to confirm this change.\n" + " \n" + "If you don't know why this mail was sent to you, please ignore it silently.\n" + " \n" + "%s administrators."), u->Account()->email.c_str(), Config->UseStrictPrivMsgString.c_str(), Config->NickServ.c_str(), code.c_str(), Config->NetworkName.c_str()); + + return Mail(u, u->Account(), bi, subject, message); +} + +class CommandNSSetEmail : public Command +{ + public: + CommandNSSetEmail(Module *creator, const Anope::string &cname = "nickserv/set/email", size_t min = 0) : Command(creator, cname, min, min + 1) + { + this->SetDesc(_("Associate an E-mail address with your nickname")); + this->SetSyntax(_("\037address\037")); + } + + void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) + { + User *u = source.u; + NickAlias *na = findnick(user); + if (!na) + { + source.Reply(NICK_X_NOT_REGISTERED, user.c_str()); + return; + } + NickCore *nc = na->nc; + + if (param.empty() && Config->NSForceEmail) + { + source.Reply(_("You cannot unset the e-mail on this network.")); + return; + } + else if (Config->NSSecureAdmins && u->Account() != nc && nc->IsServicesOper()) + { + source.Reply(ACCESS_DENIED); + return; + } + else if (!param.empty() && !MailValidate(param)) + { + source.Reply(MAIL_X_INVALID, param.c_str()); + return; + } + + if (!param.empty() && Config->NSConfirmEmailChanges && !u->IsServicesOper()) + { + u->Account()->Extend("ns_set_email", new ExtensibleItemRegular<Anope::string>(param)); + Anope::string old = u->Account()->email; + u->Account()->email = param; + if (SendConfirmMail(u, source.owner)) + source.Reply(_("A confirmation email has been sent to \002%s\002. Follow the instructions in it to change your email address."), param.c_str()); + u->Account()->email = old; + } + else + { + if (!param.empty()) + { + nc->email = param; + source.Reply(_("E-mail address for \002%s\002 changed to \002%s\002."), nc->display.c_str(), param.c_str()); + } + else + { + nc->email.clear(); + source.Reply(_("E-mail address for \002%s\002 unset."), nc->display.c_str()); + } + } + + return; + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + this->Run(source, source.u->Account()->display, params.size() ? params[0] : ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Associates the given E-mail address with your nickname.\n" + "This address will be displayed whenever someone requests\n" + "information on the nickname with the \002INFO\002 command.")); + return true; + } +}; + +class CommandNSSASetEmail : public CommandNSSetEmail +{ + public: + CommandNSSASetEmail(Module *creator) : CommandNSSetEmail(creator, "nickserv/saset/email", 2) + { + this->ClearSyntax(); + this->SetSyntax(_("\037nickname\037 \037address\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + this->Run(source, params[0], params.size() > 1 ? params[1] : ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Associates the given E-mail address with the nickname.")); + return true; + } +}; + +class NSSetEmail : public Module +{ + CommandNSSetEmail commandnssetemail; + CommandNSSASetEmail commandnssasetemail; + + public: + NSSetEmail(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandnssetemail(this), commandnssasetemail(this) + { + this->SetAuthor("Anope"); + + ModuleManager::Attach(I_OnPreCommand, this); + + ModuleManager::RegisterService(&commandnssetemail); + ModuleManager::RegisterService(&commandnssasetemail); + } + + EventReturn OnPreCommand(CommandSource &source, Command *command, std::vector<Anope::string> ¶ms) + { + User *u = source.u; + if (command->name == "nickserv/confirm" && !params.empty() && u->IsIdentified()) + { + Anope::string new_email, passcode; + if (u->Account()->GetExtRegular("ns_set_email", new_email) && u->Account()->GetExtRegular("ns_set_email_passcode", passcode)) + { + if (params[0] == passcode) + { + u->Account()->email = new_email; + Log(LOG_COMMAND, u, command) << "to confirm their email address change to " << u->Account()->email; + source.Reply(_("Your email address has been changed to \002%s\002."), u->Account()->email.c_str()); + u->Account()->Shrink("ns_set_email"); + u->Account()->Shrink("ns_set_email_passcode"); + return EVENT_STOP; + } + } + } + + return EVENT_CONTINUE; + } +}; + +MODULE_INIT(NSSetEmail) diff --git a/modules/commands/ns_set_greet.cpp b/modules/commands/ns_set_greet.cpp new file mode 100644 index 000000000..03bcd3834 --- /dev/null +++ b/modules/commands/ns_set_greet.cpp @@ -0,0 +1,108 @@ +/* NickServ core 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 "module.h" + +class CommandNSSetGreet : public Command +{ + public: + CommandNSSetGreet(Module *creator, const Anope::string &sname = "nickserv/set/greet", size_t min = 0) : Command(creator, sname, min, min + 1) + { + this->SetDesc(_("Associate a greet message with your nickname")); + this->SetSyntax(_("\037message\037")); + } + + void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) + { + NickAlias *na = findnick(user); + if (!na) + { + source.Reply(NICK_X_NOT_REGISTERED, user.c_str()); + return; + } + NickCore *nc = na->nc; + + if (!param.empty()) + { + nc->greet = param; + source.Reply(_("Greet message for \002%s\002 changed to \002%s\002."), nc->display.c_str(), nc->greet.c_str()); + } + else + { + nc->greet.clear(); + source.Reply(_("Greet message for \002%s\002 unset."), nc->display.c_str()); + } + + return; + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + this->Run(source, source.u->Account()->display, params.size() > 0 ? params[0] : ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Makes the given message the greet of your nickname, that\n" + "will be displayed when joining a channel that has GREET\n" + "option enabled, provided that you have the necessary \n" + "access on it.")); + return true; + } +}; + +class CommandNSSASetGreet : public CommandNSSetGreet +{ + public: + CommandNSSASetGreet(Module *creator) : CommandNSSetGreet(creator, "nickserv/saset/greet", 1) + { + this->ClearSyntax(); + this->SetSyntax(_("\037nickname\037 \037message\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + this->Run(source, params[0], params.size() > 1 ? params[1] : ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Makes the given message the greet of the nickname, that\n" + "will be displayed when joining a channel that has GREET\n" + "option enabled, provided that the user has the necessary \n" + "access on it.")); + return true; + } +}; + +class NSSetGreet : public Module +{ + CommandNSSetGreet commandnssetgreet; + CommandNSSASetGreet commandnssasetgreet; + + public: + NSSetGreet(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandnssetgreet(this), commandnssasetgreet(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandnssetgreet); + ModuleManager::RegisterService(&commandnssasetgreet); + } +}; + +MODULE_INIT(NSSetGreet) diff --git a/modules/commands/ns_set_hide.cpp b/modules/commands/ns_set_hide.cpp new file mode 100644 index 000000000..e0fb0e85e --- /dev/null +++ b/modules/commands/ns_set_hide.cpp @@ -0,0 +1,149 @@ +/* NickServ core 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 "module.h" + +class CommandNSSetHide : public Command +{ + public: + CommandNSSetHide(Module *creator, const Anope::string &sname = "nickserv/set/hide", size_t min = 2) : Command(creator, sname, min, min + 1) + { + this->SetDesc(_("Hide certain pieces of nickname information")); + this->SetSyntax(_("{EMAIL | STATUS | USERMASK | QUIT} {ON | OFF}")); + } + + void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m, const Anope::string &arg) + { + NickAlias *na = findnick(user); + if (!na) + { + source.Reply(NICK_X_NOT_REGISTERED, user.c_str()); + return; + } + NickCore *nc = na->nc; + + Anope::string onmsg, offmsg; + NickCoreFlag flag; + + if (param.equals_ci("EMAIL")) + { + flag = NI_HIDE_EMAIL; + onmsg = _("The E-mail address of \002%s\002 will now be hidden from %s INFO displays."); + offmsg = _("The E-mail address of \002%s\002 will now be shown in %s INFO displays."); + } + else if (param.equals_ci("USERMASK")) + { + flag = NI_HIDE_MASK; + onmsg = _("The last seen user@host mask of \002%s\002 will now be hidden from %s INFO displays."); + offmsg = _("The last seen user@host mask of \002%s\002 will now be shown in %s INFO displays."); + } + else if (param.equals_ci("STATUS")) + { + flag = NI_HIDE_STATUS; + onmsg = _("The services access status of \002%s\002 will now be hidden from %s INFO displays."); + offmsg = _("The services access status of \002%s\002 will now be shown in %s INFO displays."); + } + else if (param.equals_ci("QUIT")) + { + flag = NI_HIDE_QUIT; + onmsg = _("The last quit message of \002%s\002 will now be hidden from %s INFO displays."); + offmsg = _("The last quit message of \002%s\002 will now be shown in %s INFO displays."); + } + else + { + this->OnSyntaxError(source, "HIDE"); + return; + } + + if (arg.equals_ci("ON")) + { + nc->SetFlag(flag); + source.Reply(onmsg.c_str(), nc->display.c_str(), Config->NickServ.c_str()); + } + else if (arg.equals_ci("OFF")) + { + nc->UnsetFlag(flag); + source.Reply(offmsg.c_str(), nc->display.c_str(), Config->NickServ.c_str()); + } + else + this->OnSyntaxError(source, "HIDE"); + + return; + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + this->Run(source, source.u->Account()->display, params[0], params[1]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Allows you to prevent certain pieces of information from\n" + "being displayed when someone does a %s \002INFO\002 on your\n" + "nick. You can hide your E-mail address (\002EMAIL\002), last seen\n" + "user@host mask (\002USERMASK\002), your services access status\n" + "(\002STATUS\002) and last quit message (\002QUIT\002).\n" + "The second parameter specifies whether the information should\n" + "be displayed (\002OFF\002) or hidden (\002ON\002)."), Config->NickServ.c_str()); + return true; + } +}; + +class CommandNSSASetHide : public CommandNSSetHide +{ + public: + CommandNSSASetHide(Module *creator) : CommandNSSetHide(creator, "nickserv/saset/hide", 3) + { + this->SetSyntax("\037nickname\037 {EMAIL | STATUS | USERMASK | QUIT} {ON | OFF}"); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + this->ClearSyntax(); + this->Run(source, params[0], params[1], params[2]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Allows you to prevent certain pieces of information from\n" + "being displayed when someone does a %s \002INFO\002 on the\n" + "nick. You can hide the E-mail address (\002EMAIL\002), last seen\n" + "user@host mask (\002USERMASK\002), the services access status\n" + "(\002STATUS\002) and last quit message (\002QUIT\002).\n" + "The second parameter specifies whether the information should\n" + "be displayed (\002OFF\002) or hidden (\002ON\002)."), Config->NickServ.c_str()); + return true; + } +}; + +class NSSetHide : public Module +{ + CommandNSSetHide commandnssethide; + CommandNSSASetHide commandnssasethide; + + public: + NSSetHide(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandnssethide(this), commandnssasethide(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandnssethide); + ModuleManager::RegisterService(&commandnssasethide); + } +}; + +MODULE_INIT(NSSetHide) diff --git a/modules/commands/ns_set_kill.cpp b/modules/commands/ns_set_kill.cpp new file mode 100644 index 000000000..f57a93893 --- /dev/null +++ b/modules/commands/ns_set_kill.cpp @@ -0,0 +1,149 @@ +/* NickServ core 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 "module.h" + +class CommandNSSetKill : public Command +{ + public: + CommandNSSetKill(Module *creator, const Anope::string &sname = "nickserv/set/kill", size_t min = 1) : Command(creator, sname, min, min + 1) + { + this->SetDesc(_("Turn protection on or off")); + this->SetSyntax(_("{ON | QUICK | IMMED | OFF}")); + } + + void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) + { + NickAlias *na = findnick(user); + if (!na) + { + source.Reply(NICK_X_NOT_REGISTERED, user.c_str()); + return; + } + NickCore *nc = na->nc; + + if (param.equals_ci("ON")) + { + nc->SetFlag(NI_KILLPROTECT); + nc->UnsetFlag(NI_KILL_QUICK); + nc->UnsetFlag(NI_KILL_IMMED); + source.Reply(_("Protection is now \002on\002 for \002%s\002."), nc->display.c_str()); + } + else if (param.equals_ci("QUICK")) + { + nc->SetFlag(NI_KILLPROTECT); + nc->SetFlag(NI_KILL_QUICK); + nc->UnsetFlag(NI_KILL_IMMED); + source.Reply(_("Protection is now \002on\002 for \002%s\002, with a reduced delay."), nc->display.c_str()); + } + else if (param.equals_ci("IMMED")) + { + if (Config->NSAllowKillImmed) + { + nc->SetFlag(NI_KILLPROTECT); + nc->SetFlag(NI_KILL_IMMED); + nc->UnsetFlag(NI_KILL_QUICK); + source.Reply(_("Protection is now \002on\002 for \002%s\002, with no delay."), nc->display.c_str()); + } + else + source.Reply(_("The \002IMMED\002 option is not available on this network.")); + } + else if (param.equals_ci("OFF")) + { + nc->UnsetFlag(NI_KILLPROTECT); + nc->UnsetFlag(NI_KILL_QUICK); + nc->UnsetFlag(NI_KILL_IMMED); + source.Reply(_("Protection is now \002off\002 for \002%s\002."), nc->display.c_str()); + } + else + this->OnSyntaxError(source, "KILL"); + + return; + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + this->Run(source, source.u->Account()->display, params[0]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Turns the automatic protection option for your nick\n" + "on or off. With protection on, if another user\n" + "tries to take your nick, they will be given one minute to\n" + "change to another nick, after which %s will forcibly change\n" + "their nick.\n" + " \n" + "If you select \002QUICK\002, the user will be given only 20 seconds\n" + "to change nicks instead of the usual 60. If you select\n" + "\002IMMED\002, user's nick will be changed immediately \037without\037 being\n" + "warned first or given a chance to change their nick; please\n" + "do not use this option unless necessary. Also, your\n" + "network's administrators may have disabled this option."), Config->NickServ.c_str()); + return true; + } +}; + +class CommandNSSASetKill : public CommandNSSetKill +{ + public: + CommandNSSASetKill(Module *creator) : CommandNSSetKill(creator, "nickserv/saset/kill", 2) + { + this->SetSyntax(_("\037nickname\037 {ON | QUICK | IMMED | OFF}")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + this->ClearSyntax(); + this->Run(source, params[0], params[1]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Turns the automatic protection option for the nick\n" + "on or off. With protection on, if another user\n" + "tries to take the nick, they will be given one minute to\n" + "change to another nick, after which %s will forcibly change\n" + "their nick.\n" + " \n" + "If you select \002QUICK\002, the user will be given only 20 seconds\n" + "to change nicks instead of the usual 60. If you select\n" + "\002IMMED\002, the user's nick will be changed immediately \037without\037 being\n" + "warned first or given a chance to change their nick; please\n" + "do not use this option unless necessary. Also, your\n" + "network's administrators may have disabled this option."), Config->NickServ.c_str()); + return true; + } +}; + +class NSSetKill : public Module +{ + CommandNSSetKill commandnssetkill; + CommandNSSASetKill commandnssasetkill; + + public: + NSSetKill(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandnssetkill(this), commandnssasetkill(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandnssetkill); + ModuleManager::RegisterService(&commandnssasetkill); + } +}; + +MODULE_INIT(NSSetKill) diff --git a/modules/commands/ns_set_language.cpp b/modules/commands/ns_set_language.cpp new file mode 100644 index 000000000..3429ad688 --- /dev/null +++ b/modules/commands/ns_set_language.cpp @@ -0,0 +1,121 @@ +/* NickServ core 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 "module.h" + +class CommandNSSetLanguage : public Command +{ + public: + CommandNSSetLanguage(Module *creator, const Anope::string &sname = "nickserv/set/language", size_t min = 1) : Command(creator, sname, min, min + 1) + { + this->SetDesc(_("Set the language Services will use when messaging you")); + this->SetSyntax(_("\037language\037")); + } + + void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) + { + NickAlias *na = findnick(user); + if (!na) + { + source.Reply(NICK_X_NOT_REGISTERED, user.c_str()); + return; + } + NickCore *nc = na->nc; + + for (unsigned j = 0; j < languages.size(); ++j) + { + if (param == "en" || languages[j] == param) + break; + else if (j + 1 == languages.size()) + { + this->OnSyntaxError(source, ""); + return; + } + } + + nc->language = param != "en" ? param : ""; + source.Reply(_("Language changed to \002English\002.")); + + return; + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶m) + { + this->Run(source, source.u->Account()->display, param[0]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Changes the language Services uses when sending messages to\n" + "you (for example, when responding to a command you send).\n" + "\037language\037 should be chosen from the following list of\n" + "supported languages:")); + + source.Reply(" en (English)"); + for (unsigned j = 0; j < languages.size(); ++j) + { + const Anope::string &langname = anope_gettext(languages[j].c_str(), _("English")); + if (langname == "English") + continue; + source.Reply(" %s (%s)", languages[j].c_str(), langname.c_str()); + } + + return true; + } +}; + +class CommandNSSASetLanguage : public CommandNSSetLanguage +{ + public: + CommandNSSASetLanguage(Module *creator) : CommandNSSetLanguage(creator, "nickserv/saset/language", 2) + { + this->ClearSyntax(); + this->SetSyntax(_("\037nickname\037 \037language\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + this->Run(source, params[0], params[1]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Changes the language Services uses when sending messages to\n" + "you (for example, when responding to a command you send).\n" + "\037language\037 should be chosen from the following list of\n" + "supported languages:")); + return true; + } +}; + +class NSSetLanguage : public Module +{ + CommandNSSetLanguage commandnssetlanguage; + CommandNSSASetLanguage commandnssasetlanguage; + + public: + NSSetLanguage(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandnssetlanguage(this), commandnssasetlanguage(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandnssetlanguage); + ModuleManager::RegisterService(&commandnssasetlanguage); + } +}; + +MODULE_INIT(NSSetLanguage) diff --git a/modules/commands/ns_set_message.cpp b/modules/commands/ns_set_message.cpp new file mode 100644 index 000000000..677002c78 --- /dev/null +++ b/modules/commands/ns_set_message.cpp @@ -0,0 +1,114 @@ +/* NickServ core 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 "module.h" + +class CommandNSSetMessage : public Command +{ + public: + CommandNSSetMessage(Module *creator, const Anope::string &sname = "nickserv/set/message", size_t min = 1) : Command(creator, sname, min, min + 1) + { + this->SetDesc(_("Change the communication method of Services")); + this->SetSyntax(_("{ON | OFF}")); + } + + void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) + { + NickAlias *na = findnick(user); + if (!na) + { + source.Reply(NICK_X_NOT_REGISTERED, user.c_str()); + return; + } + NickCore *nc = na->nc; + + if (!Config->UsePrivmsg) + { + source.Reply(_("Option \002%s\02 cannot be set on this network."), "MSG"); + return; + } + + if (param.equals_ci("ON")) + { + nc->SetFlag(NI_MSG); + source.Reply(_("Services will now reply to \002%s\002 with \002messages\002."), nc->display.c_str()); + } + else if (param.equals_ci("OFF")) + { + nc->UnsetFlag(NI_MSG); + source.Reply(_("Services will now reply to \002%s\002 with \002notices\002."), nc->display.c_str()); + } + else + this->OnSyntaxError(source, "MSG"); + + return; + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + this->Run(source, source.u->Account()->display, params[0]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Allows you to choose the way Services are communicating with \n" + "you. With \002MSG\002 set, Services will use messages, else they'll \n" + "use notices.")); + return true; + } +}; + +class CommandNSSASetMessage : public CommandNSSetMessage +{ + public: + CommandNSSASetMessage(Module *creator) : CommandNSSetMessage(creator, "nickserv/saset/message", 2) + { + this->ClearSyntax(); + this->SetSyntax(_("\037nickname\037 {ON | OFF}")); + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Allows you to choose the way Services are communicating with \n" + "the given user. With \002MSG\002 set, Services will use messages,\n" + "else they'll use notices.")); + return true; + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + this->Run(source, params[0], params[1]); + } +}; + +class NSSetMessage : public Module +{ + CommandNSSetMessage commandnssetmessage; + CommandNSSASetMessage commandnssasetmessage; + + public: + NSSetMessage(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandnssetmessage(this), commandnssasetmessage(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandnssetmessage); + ModuleManager::RegisterService(&commandnssasetmessage); + } +}; + +MODULE_INIT(NSSetMessage) diff --git a/modules/commands/ns_set_misc.cpp b/modules/commands/ns_set_misc.cpp new file mode 100644 index 000000000..f0ee269cc --- /dev/null +++ b/modules/commands/ns_set_misc.cpp @@ -0,0 +1,126 @@ +/* + * + * (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 "module.h" + +class CommandNSSetMisc : public Command +{ + public: + CommandNSSetMisc(Module *creator, const Anope::string &cname = "nickserv/set/misc", size_t min = 1) : Command(creator, cname, min, min + 1) + { + this->SetSyntax(_("\037parameter\037")); + } + + void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) + { + NickAlias *na = findnick(user); + if (!na) + { + source.Reply(NICK_X_NOT_REGISTERED, user.c_str()); + return; + } + NickCore *nc = na->nc; + + nc->Shrink("ns_set_misc:" + source.command.replace_all_cs(" ", "_")); + if (!param.empty()) + { + nc->Extend("ns_set_misc:" + source.command.replace_all_cs(" ", "_"), new ExtensibleItemRegular<Anope::string>(param)); + source.Reply(CHAN_SETTING_CHANGED, source.command.c_str(), nc->display.c_str(), param.c_str()); + } + else + source.Reply(CHAN_SETTING_UNSET, source.command.c_str(), nc->display.c_str()); + + return; + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + this->Run(source, source.u->Account()->display, params[0]); + } +}; + +class CommandNSSASetMisc : public CommandNSSetMisc +{ + public: + CommandNSSASetMisc(Module *creator) : CommandNSSetMisc(creator, "nickserv/saset/misc", 2) + { + this->ClearSyntax(); + this->SetSyntax(_("\037nickname\037 \037parameter\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + this->Run(source, params[0], params[1]); + } +}; + +class NSSetMisc : public Module +{ + CommandNSSetMisc commandnssetmisc; + CommandNSSASetMisc commandnssasetmisc; + + public: + NSSetMisc(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, SUPPORTED), + commandnssetmisc(this), commandnssasetmisc(this) + { + this->SetAuthor("Anope"); + + Implementation i[] = { I_OnNickInfo, I_OnDatabaseWriteMetadata, I_OnDatabaseReadMetadata }; + ModuleManager::Attach(i, this, sizeof(i) / sizeof(Implementation)); + + ModuleManager::RegisterService(&this->commandnssetmisc); + ModuleManager::RegisterService(&this->commandnssasetmisc); + } + + void OnNickInfo(CommandSource &source, NickAlias *na, bool ShowHidden) + { + std::deque<Anope::string> list; + na->nc->GetExtList(list); + + for (unsigned i = 0; i < list.size(); ++i) + { + if (list[i].find("ns_set_misc:") != 0) + continue; + + Anope::string value; + if (na->nc->GetExtRegular(list[i], value)) + source.Reply(" %s: %s", list[i].substr(12).replace_all_cs("_", " ").c_str(), value.c_str()); + } + } + + void OnDatabaseWriteMetadata(void (*WriteMetadata)(const Anope::string &, const Anope::string &), NickCore *nc) + { + std::deque<Anope::string> list; + nc->GetExtList(list); + + for (unsigned i = 0; i < list.size(); ++i) + { + if (list[i].find("ns_set_misc:") != 0) + continue; + + Anope::string value; + if (nc->GetExtRegular(list[i], value)) + WriteMetadata(list[i], ":" + value); + } + } + + EventReturn OnDatabaseReadMetadata(NickCore *nc, const Anope::string &key, const std::vector<Anope::string> ¶ms) + { + if (key.find("ns_set_misc:") == 0) + nc->Extend(key, new ExtensibleItemRegular<Anope::string>(params[0])); + + return EVENT_CONTINUE; + } +}; + +MODULE_INIT(NSSetMisc) diff --git a/modules/commands/ns_set_private.cpp b/modules/commands/ns_set_private.cpp new file mode 100644 index 000000000..63fec57d6 --- /dev/null +++ b/modules/commands/ns_set_private.cpp @@ -0,0 +1,114 @@ +/* NickServ core 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 "module.h" + +class CommandNSSetPrivate : public Command +{ + public: + CommandNSSetPrivate(Module *creator, const Anope::string &sname = "nickserv/set/private", size_t min = 1) : Command(creator, sname, min, min + 1) + { + this->SetDesc(Anope::printf(_("Prevent the nickname from appearing in a \002%s%s LIST\002"), Config->UseStrictPrivMsgString.c_str(), Config->NickServ.c_str())); + this->SetSyntax(_("{ON | OFF}")); + } + + void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) + { + NickAlias *na = findnick(user); + if (!na) + { + source.Reply(NICK_X_NOT_REGISTERED, user.c_str()); + return; + } + NickCore *nc = na->nc; + + if (param.equals_ci("ON")) + { + nc->SetFlag(NI_PRIVATE); + source.Reply(_("Private option is now \002on\002 for \002%s\002."), nc->display.c_str()); + } + else if (param.equals_ci("OFF")) + { + nc->UnsetFlag(NI_PRIVATE); + source.Reply(_("Private option is now \002off\002 for \002%s\002."), nc->display.c_str()); + } + else + this->OnSyntaxError(source, "PRIVATE"); + + return; + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + this->Run(source, source.u->Account()->display, params[0]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Turns %s's privacy option on or off for your nick.\n" + "With \002PRIVATE\002 set, your nickname will not appear in\n" + "nickname lists generated with %s's \002LIST\002 command.\n" + "(However, anyone who knows your nickname can still get\n" + "information on it using the \002INFO\002 command.)"), + Config->NickServ.c_str(), Config->NickServ.c_str()); + return true; + } +}; + +class CommandNSSASetPrivate : public CommandNSSetPrivate +{ + public: + CommandNSSASetPrivate(Module *creator) : CommandNSSetPrivate(creator, "nickserv/saset/private", 2) + { + this->ClearSyntax(); + this->SetSyntax(_("\037nickname\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + this->Run(source, params[0], params[1]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Turns %s's privacy option on or off for the nick.\n" + "With \002PRIVATE\002 set, the nickname will not appear in\n" + "nickname lists generated with %s's \002LIST\002 command.\n" + "(However, anyone who knows the nickname can still get\n" + "information on it using the \002INFO\002 command.)"), + Config->NickServ.c_str(), Config->NickServ.c_str()); + return true; + } +}; + +class NSSetPrivate : public Module +{ + CommandNSSetPrivate commandnssetprivate; + CommandNSSASetPrivate commandnssasetprivate; + + public: + NSSetPrivate(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandnssetprivate(this), commandnssasetprivate(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandnssetprivate); + ModuleManager::RegisterService(&commandnssasetprivate); + } +}; + +MODULE_INIT(NSSetPrivate) diff --git a/modules/commands/ns_set_secure.cpp b/modules/commands/ns_set_secure.cpp new file mode 100644 index 000000000..31314a5a5 --- /dev/null +++ b/modules/commands/ns_set_secure.cpp @@ -0,0 +1,114 @@ +/* NickServ core 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 "module.h" + +class CommandNSSetSecure : public Command +{ + public: + CommandNSSetSecure(Module *creator, const Anope::string &sname = "nickserv/set/secure", size_t min = 1) : Command(creator, sname, min, min + 1) + { + this->SetDesc(_("Turn nickname security on or off")); + this->SetSyntax(_("{ON | OFF}")); + } + + void Run(CommandSource &source, const Anope::string &user, const Anope::string ¶m) + { + NickAlias *na = findnick(user); + if (!na) + { + source.Reply(NICK_X_NOT_REGISTERED, user.c_str()); + return; + } + NickCore *nc = na->nc; + + if (param.equals_ci("ON")) + { + nc->SetFlag(NI_SECURE); + source.Reply(_("Secure option is now \002on\002 for \002%s\002."), nc->display.c_str()); + } + else if (param.equals_ci("OFF")) + { + nc->UnsetFlag(NI_SECURE); + source.Reply(_("Secure option is now \002off\002 for \002%s\002."), nc->display.c_str()); + } + else + this->OnSyntaxError(source, "SECURE"); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + this->Run(source, source.u->Account()->display, params[0]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Turns %s's security features on or off for your\n" + "nick. With \002SECURE\002 set, you must enter your password\n" + "before you will be recognized as the owner of the nick,\n" + "regardless of whether your address is on the access\n" + "list. However, if you are on the access list, %s\n" + "will not auto-kill you regardless of the setting of the\n" + "\002KILL\002 option."), Config->NickServ.c_str(), Config->NickServ.c_str()); + return true; + } +}; + +class CommandNSSASetSecure : public CommandNSSetSecure +{ + public: + CommandNSSASetSecure(Module *creator) : CommandNSSetSecure(creator, "nickserv/saset/secure", 2) + { + this->ClearSyntax(); + this->SetSyntax(_("\037nickname\037 {ON | OFF}")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + this->Run(source, params[0], params[1]); + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Turns %s's security features on or off for your\n" + "nick. With \002SECURE\002 set, you must enter your password\n" + "before you will be recognized as the owner of the nick,\n" + "regardless of whether your address is on the access\n" + "list. However, if you are on the access list, %s\n" + "will not auto-kill you regardless of the setting of the\n" + "\002KILL\002 option."), Config->NickServ.c_str(), Config->NickServ.c_str()); + return true; + } +}; + +class NSSetSecure : public Module +{ + CommandNSSetSecure commandnssetsecure; + CommandNSSASetSecure commandnssasetsecure; + + public: + NSSetSecure(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandnssetsecure(this), commandnssasetsecure(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandnssetsecure); + ModuleManager::RegisterService(&commandnssasetsecure); + } +}; + +MODULE_INIT(NSSetSecure) diff --git a/modules/commands/ns_status.cpp b/modules/commands/ns_status.cpp new file mode 100644 index 000000000..9d8b58fcb --- /dev/null +++ b/modules/commands/ns_status.cpp @@ -0,0 +1,94 @@ +/* NickServ core 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 "module.h" + +class CommandNSStatus : public Command +{ + public: + CommandNSStatus(Module *creator) : Command(creator, "nickserv/status", 0, 16) + { + this->SetFlag(CFLAG_ALLOW_UNREGISTERED); + this->SetDesc(_("Returns the owner status of the given nickname")); + this->SetSyntax(_("\037nickname\037...")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + const Anope::string &nick = !params.empty() ? params[0] : u->nick; + NickAlias *na = findnick(nick); + spacesepstream sep(nick); + Anope::string nickbuf; + + while (sep.GetToken(nickbuf)) + { + User *u2 = finduser(nickbuf); + if (!u2) /* Nick is not online */ + source.Reply(_("STATUS %s %d %s"), nickbuf.c_str(), 0, ""); + else if (u2->IsIdentified() && na && na->nc == u2->Account()) /* Nick is identified */ + source.Reply(_("STATUS %s %d %s"), nickbuf.c_str(), 3, u2->Account()->display.c_str()); + else if (u2->IsRecognized()) /* Nick is recognised, but NOT identified */ + source.Reply(_("STATUS %s %d %s"), nickbuf.c_str(), 2, u2->Account() ? u2->Account()->display.c_str() : ""); + else if (!na) /* Nick is online, but NOT a registered */ + source.Reply(_("STATUS %s %d %s"), nickbuf.c_str(), 0, ""); + else + /* Nick is not identified for the nick, but they could be logged into an account, + * so we tell the user about it + */ + source.Reply(_("STATUS %s %d %s"), nickbuf.c_str(), 1, u2->Account() ? u2->Account()->display.c_str() : ""); + } + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Returns whether the user using the given nickname is\n" + "recognized as the owner of the nickname. The response has\n" + "this format:\n" + " \n" + " \037nickname\037 \037status-code\037 \037account\037\n" + " \n" + "where \037nickname\037 is the nickname sent with the command,\n" + "\037status-code\037 is one of the following, and \037account\037\n" + "is the account they are logged in as.\n" + " \n" + " 0 - no such user online \002or\002 nickname not registered\n" + " 1 - user not recognized as nickname's owner\n" + " 2 - user recognized as owner via access list only\n" + " 3 - user recognized as owner via password identification\n" + " \n" + "Up to sixteen nicknames may be sent with each command; the\n" + "rest will be ignored. If no nickname is given, your status\n" + "will be returned.")); + return true; + } +}; + +class NSStatus : public Module +{ + CommandNSStatus commandnsstatus; + + public: + NSStatus(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandnsstatus(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandnsstatus); + } +}; + +MODULE_INIT(NSStatus) diff --git a/modules/commands/ns_suspend.cpp b/modules/commands/ns_suspend.cpp new file mode 100644 index 000000000..50cf039ce --- /dev/null +++ b/modules/commands/ns_suspend.cpp @@ -0,0 +1,160 @@ +/* NickServ core 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 "module.h" + +class CommandNSSuspend : public Command +{ + public: + CommandNSSuspend(Module *creator) : Command(creator, "nickserv/suspend", 2, 2) + { + this->SetDesc(_("Suspend a given nick")); + this->SetSyntax(_("\037nickname\037 \037reason\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + + const Anope::string &nick = params[0]; + const Anope::string &reason = params[1]; + + if (readonly) + { + source.Reply(READ_ONLY_MODE); + return; + } + + NickAlias *na = findnick(nick); + if (!na) + { + source.Reply(NICK_X_NOT_REGISTERED, nick.c_str()); + return; + } + + if (Config->NSSecureAdmins && na->nc->IsServicesOper()) + { + source.Reply(ACCESS_DENIED); + return; + } + + na->nc->SetFlag(NI_SUSPENDED); + na->nc->SetFlag(NI_SECURE); + na->nc->UnsetFlag(NI_KILLPROTECT); + na->nc->UnsetFlag(NI_KILL_QUICK); + na->nc->UnsetFlag(NI_KILL_IMMED); + + for (std::list<NickAlias *>::iterator it = na->nc->aliases.begin(), it_end = na->nc->aliases.end(); it != it_end; ++it) + { + NickAlias *na2 = *it; + + if (na2->nc == na->nc) + { + na2->last_quit = reason; + + User *u2 = finduser(na2->nick); + if (u2) + { + u2->Logout(); + u2->Collide(na2); + } + } + } + + Log(LOG_ADMIN, u, this) << "for " << nick << " (" << (!reason.empty() ? reason : "No reason") << ")"; + source.Reply(_("Nick %s is now suspended."), nick.c_str()); + + FOREACH_MOD(I_OnNickSuspended, OnNickSuspend(na)); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Suspends a registered nickname, which prevents from being used\n" + "while keeping all the data for that nick.")); + return true; + } +}; + +class CommandNSUnSuspend : public Command +{ + public: + CommandNSUnSuspend(Module *creator) : Command(creator, "nickserv/unsuspend", 1, 1) + { + this->SetDesc(_("Unsuspend a given nick")); + this->SetSyntax(_("\037nickname\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + const Anope::string &nick = params[0]; + + if (readonly) + { + source.Reply(READ_ONLY_MODE); + return; + } + + NickAlias *na = findnick(nick); + if (!na) + { + source.Reply(NICK_X_NOT_REGISTERED, nick.c_str()); + return; + } + + if (Config->NSSecureAdmins && na->nc->IsServicesOper()) + { + source.Reply(ACCESS_DENIED); + return; + } + + na->nc->UnsetFlag(NI_SUSPENDED); + + Log(LOG_ADMIN, u, this) << "for " << na->nick; + source.Reply(_("Nick %s is now released."), nick.c_str()); + + FOREACH_MOD(I_OnNickUnsuspended, OnNickUnsuspended(na)); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Unsuspends a nickname which allows it to be used again.")); + return true; + } +}; + +class NSSuspend : public Module +{ + CommandNSSuspend commandnssuspend; + CommandNSUnSuspend commandnsunsuspend; + + public: + NSSuspend(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandnssuspend(this), commandnsunsuspend(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandnssuspend); + ModuleManager::RegisterService(&commandnsunsuspend); + } +}; + +MODULE_INIT(NSSuspend) diff --git a/modules/commands/ns_update.cpp b/modules/commands/ns_update.cpp new file mode 100644 index 000000000..d870e3532 --- /dev/null +++ b/modules/commands/ns_update.cpp @@ -0,0 +1,70 @@ +/* NickServ core 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 "module.h" + +class CommandNSUpdate : public Command +{ + public: + CommandNSUpdate(Module *creator) : Command(creator, "nickserv/update", 0, 0) + { + this->SetDesc(_("Updates your current status, i.e. it checks for new memos")); + this->SetSyntax(""); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + NickAlias *na = findnick(u->nick); + + if (!na) + { + source.Reply(NICK_NOT_REGISTERED); + return; + } + + na->last_realname = u->realname; + na->last_seen = Anope::CurTime; + + FOREACH_MOD(I_OnNickUpdate, OnNickUpdate(u)); + + source.Reply(_("Status updated (memos, vhost, chmodes, flags)."), Config->NickServ.c_str()); + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Updates your current status, i.e. it checks for new memos,\n" + "sets needed channel modes and updates your vhost and\n" + "your userflags (lastseentime, etc).")); + return true; + } +}; + +class NSUpdate : public Module +{ + CommandNSUpdate commandnsupdate; + + public: + NSUpdate(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandnsupdate(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandnsupdate); + } +}; + +MODULE_INIT(NSUpdate) diff --git a/modules/commands/os_akill.cpp b/modules/commands/os_akill.cpp new file mode 100644 index 000000000..05602a746 --- /dev/null +++ b/modules/commands/os_akill.cpp @@ -0,0 +1,464 @@ +/* OperServ core 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 "module.h" + +static service_reference<XLineManager> akills("xlinemanager/sgline"); + +class AkillDelCallback : public NumberList +{ + CommandSource &source; + unsigned Deleted; + public: + AkillDelCallback(CommandSource &_source, const Anope::string &numlist) : NumberList(numlist, true), source(_source), Deleted(0) + { + } + + ~AkillDelCallback() + { + if (!Deleted) + source.Reply(_("No matching entries on the AKILL list.")); + else if (Deleted == 1) + source.Reply(_("Deleted 1 entry from the AKILL list.")); + else + source.Reply(_("Deleted %d entries from the AKILL list."), Deleted); + } + + void HandleNumber(unsigned Number) + { + if (!Number) + return; + + XLine *x = akills->GetEntry(Number - 1); + + if (!x) + return; + + ++Deleted; + DoDel(source, x); + } + + static void DoDel(CommandSource &source, XLine *x) + { + akills->DelXLine(x); + } +}; + +class AkillListCallback : public NumberList +{ + protected: + CommandSource &source; + bool SentHeader; + public: + AkillListCallback(CommandSource &_source, const Anope::string &numlist) : NumberList(numlist, false), source(_source), SentHeader(false) + { + } + + ~AkillListCallback() + { + if (!SentHeader) + source.Reply(_("No matching entries on the AKILL list.")); + else + source.Reply(END_OF_ANY_LIST, "Akill"); + } + + void HandleNumber(unsigned Number) + { + if (!Number) + return; + + XLine *x = akills->GetEntry(Number - 1); + + if (!x) + return; + + if (!SentHeader) + { + SentHeader = true; + source.Reply(_("Current AKILL list:\n" + " Num Mask Reason")); + } + + DoList(source, x, Number); + } + + static void DoList(CommandSource &source, XLine *x, unsigned Number) + { + source.Reply(OPER_LIST_FORMAT, Number + 1, x->Mask.c_str(), x->Reason.c_str()); + } +}; + +class AkillViewCallback : public AkillListCallback +{ + public: + AkillViewCallback(CommandSource &_source, const Anope::string &numlist) : AkillListCallback(_source, numlist) + { + } + + void HandleNumber(unsigned Number) + { + if (!Number) + return; + + XLine *x = akills->GetEntry(Number - 1); + + if (!x) + return; + + if (!SentHeader) + { + SentHeader = true; + source.Reply(_("Current AKILL list:")); + } + + DoList(source, x, Number); + } + + static void DoList(CommandSource &source, XLine *x, unsigned Number) + { + source.Reply(OPER_VIEW_FORMAT, Number + 1, x->Mask.c_str(), x->By.c_str(), do_strftime(x->Created).c_str(), expire_left(source.u->Account(), x->Expires).c_str(), x->Reason.c_str()); + } +}; + +class CommandOSAKill : public Command +{ + private: + void DoAdd(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + unsigned last_param = 2; + Anope::string expiry, mask; + time_t expires; + + mask = params.size() > 1 ? params[1] : ""; + if (!mask.empty() && mask[0] == '+') + { + expiry = mask; + mask = params.size() > 2 ? params[2] : ""; + last_param = 3; + } + + expires = !expiry.empty() ? dotime(expiry) : Config->AutokillExpiry; + /* If the expiry given does not contain a final letter, it's in days, + * said the doc. Ah well. + */ + if (!expiry.empty() && isdigit(expiry[expiry.length() - 1])) + expires *= 86400; + /* Do not allow less than a minute expiry time */ + if (expires && expires < 60) + { + source.Reply(BAD_EXPIRY_TIME); + return; + } + else if (expires > 0) + expires += Anope::CurTime; + + if (params.size() <= last_param) + { + this->OnSyntaxError(source, "ADD"); + return; + } + + Anope::string reason = params[last_param]; + if (last_param == 2 && params.size() > 3) + reason += " " + params[3]; + if (!mask.empty() && !reason.empty()) + { + std::pair<int, XLine *> canAdd = akills->CanAdd(mask, expires); + if (mask.find('!') != Anope::string::npos) + source.Reply(_("\002Reminder\002: AKILL masks cannot contain nicknames; make sure you have \002not\002 included a nick portion in your mask.")); + else if (mask.find('@') == Anope::string::npos) + source.Reply(BAD_USERHOST_MASK); + else if (mask.find_first_not_of("~@.*?") == Anope::string::npos) + source.Reply(USERHOST_MASK_TOO_WIDE, mask.c_str()); + else if (canAdd.first == 1) + source.Reply(_("\002%s\002 already exists on the AKILL list."), canAdd.second->Mask.c_str()); + else if (canAdd.first == 2) + source.Reply(_("Expiry time of \002%s\002 changed."), canAdd.second->Mask.c_str()); + else if (canAdd.first == 3) + source.Reply(_("\002%s\002 is already covered by %s."), mask.c_str(), canAdd.second->Mask.c_str()); + else + { + User *user = finduser(mask); + if (user) + mask = "*@" + user->host; + unsigned int affected = 0; + for (Anope::insensitive_map<User *>::iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it) + if (Anope::Match(it->second->GetIdent() + "@" + it->second->host, mask)) + ++affected; + float percent = static_cast<float>(affected) / static_cast<float>(UserListByNick.size()) * 100.0; + + if (percent > 95) + { + source.Reply(USERHOST_MASK_TOO_WIDE, mask.c_str()); + Log(LOG_ADMIN, u, this) << "tried to akill " << percent << "% of the network (" << affected << " users)"; + return; + } + + XLine *x = akills->Add(mask, u->nick, expires, reason); + + EventReturn MOD_RESULT; + FOREACH_RESULT(I_OnAddXLine, OnAddXLine(u, x, akills)); + if (MOD_RESULT == EVENT_STOP) + { + delete x; + return; + } + + source.Reply(_("\002%s\002 added to the AKILL list."), mask.c_str()); + + Log(LOG_ADMIN, u, this) << "on " << mask << " (" << reason << ") expires in " << (expires ? duration(expires - Anope::CurTime) : "never") << " [affects " << affected << " user(s) (" << percent << "%)]"; + + if (readonly) + source.Reply(READ_ONLY_MODE); + } + } + else + this->OnSyntaxError(source, "ADD"); + + return; + } + + void DoDel(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + const Anope::string &mask = params.size() > 1 ? params[1] : ""; + + if (mask.empty()) + { + this->OnSyntaxError(source, "DEL"); + return; + } + + if (akills->GetList().empty()) + { + source.Reply(_("AKILL list is empty.")); + return; + } + + if (isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos) + { + AkillDelCallback list(source, mask); + list.Process(); + } + else + { + XLine *x = akills->HasEntry(mask); + + if (!x) + { + source.Reply(_("\002%s\002 not found on the AKILL list."), mask.c_str()); + return; + } + + FOREACH_MOD(I_OnDelXLine, OnDelXLine(u, x, akills)); + + AkillDelCallback::DoDel(source, x); + source.Reply(_("\002%s\002 deleted from the AKILL list."), mask.c_str()); + } + + if (readonly) + source.Reply(READ_ONLY_MODE); + + return; + } + + void DoList(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + if (akills->GetList().empty()) + { + source.Reply(_("AKILL list is empty.")); + return; + } + + const Anope::string &mask = params.size() > 1 ? params[1] : ""; + + if (!mask.empty() && isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos) + { + AkillListCallback list(source, mask); + list.Process(); + } + else + { + bool SentHeader = false; + + for (unsigned i = 0, end = akills->GetCount(); i < end; ++i) + { + XLine *x = akills->GetEntry(i); + + if (mask.empty() || mask.equals_ci(x->Mask) || Anope::Match(x->Mask, mask)) + { + if (!SentHeader) + { + SentHeader = true; + source.Reply(_("Current AKILL list:\n" + " Num Mask Reason")); + } + + AkillListCallback::DoList(source, x, i); + } + } + + if (!SentHeader) + source.Reply(_("No matching entries on the AKILL list.")); + else + source.Reply(END_OF_ANY_LIST, "Akill"); + } + + return; + } + + void DoView(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + if (akills->GetList().empty()) + { + source.Reply(_("AKILL list is empty.")); + return; + } + + const Anope::string &mask = params.size() > 1 ? params[1] : ""; + + if (!mask.empty() && isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos) + { + AkillViewCallback list(source, mask); + list.Process(); + } + else + { + bool SentHeader = false; + + for (unsigned i = 0, end = akills->GetCount(); i < end; ++i) + { + XLine *x = akills->GetEntry(i); + + if (mask.empty() || mask.equals_ci(x->Mask) || Anope::Match(x->Mask, mask)) + { + if (!SentHeader) + { + SentHeader = true; + source.Reply(_("Current AKILL list:")); + } + + AkillViewCallback::DoList(source, x, i); + } + } + + if (!SentHeader) + source.Reply(_("No matching entries on the AKILL list.")); + } + + return; + } + + void DoClear(CommandSource &source) + { + User *u = source.u; + FOREACH_MOD(I_OnDelXLine, OnDelXLine(u, NULL, akills)); + akills->Clear(); + source.Reply(_("The AKILL list has been cleared.")); + + return; + } + public: + CommandOSAKill(Module *creator) : Command(creator, "operserv/akill", 1, 4) + { + this->SetDesc(_("Manipulate the AKILL list")); + this->SetSyntax(_("ADD [+\037expiry\037] \037mask\037 \037reason\037")); + this->SetSyntax(_("DEL {\037mask\037 | \037entry-num\037 | \037list\037}")); + this->SetSyntax(_("LIST [\037mask\037 | \037list\037]")); + this->SetSyntax(_("VIEW [\037mask\037 | \037list\037]")); + this->SetSyntax(_("CLEAR")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &cmd = params[0]; + + if (!akills) + return; + + if (cmd.equals_ci("ADD")) + return this->DoAdd(source, params); + else if (cmd.equals_ci("DEL")) + return this->DoDel(source, params); + else if (cmd.equals_ci("LIST")) + return this->DoList(source, params); + else if (cmd.equals_ci("VIEW")) + return this->DoView(source, params); + else if (cmd.equals_ci("CLEAR")) + return this->DoClear(source); + else + this->OnSyntaxError(source, ""); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Allows Services operators to manipulate the AKILL list. If\n" + "a user matching an AKILL mask attempts to connect, Services\n" + "will issue a KILL for that user and, on supported server\n" + "types, will instruct all servers to add a ban (K-line) for\n" + "the mask which the user matched.\n" + " \n" + "\002AKILL ADD\002 adds the given nick or user@host/ip mask to the AKILL\n" + "list for the given reason (which \002must\002 be given).\n" + "\037expiry\037 is specified as an integer followed by one of \037d\037 \n" + "(days), \037h\037 (hours), or \037m\037 (minutes). Combinations (such as \n" + "\0371h30m\037) are not permitted. If a unit specifier is not \n" + "included, the default is days (so \037+30\037 by itself means 30 \n" + "days). To add an AKILL which does not expire, use \037+0\037. If the\n" + "usermask to be added starts with a \037+\037, an expiry time must\n" + "be given, even if it is the same as the default. The\n" + "current AKILL default expiry time can be found with the\n" + "\002STATS AKILL\002 command.\n" + " \n" + "The \002AKILL DEL\002 command removes the given mask from the\n" + "AKILL list if it is present. If a list of entry numbers is \n" + "given, those entries are deleted. (See the example for LIST \n" + "below.)\n" + " \n" + "The \002AKILL LIST\002 command displays the AKILL list. \n" + "If a wildcard mask is given, only those entries matching the\n" + "mask are displayed. If a list of entry numbers is given,\n" + "only those entries are shown; for example:\n" + " \002AKILL LIST 2-5,7-9\002\n" + " Lists AKILL entries numbered 2 through 5 and 7 \n" + " through 9.\n" + " \n" + "\002AKILL VIEW\002 is a more verbose version of \002AKILL LIST\002, and \n" + "will show who added an AKILL, the date it was added, and when \n" + "it expires, as well as the user@host/ip mask and reason.\n" + " \n" + "\002AKILL CLEAR\002 clears all entries of the AKILL list.")); + return true; + } +}; + +class OSAKill : public Module +{ + CommandOSAKill commandosakill; + + public: + OSAKill(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandosakill(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandosakill); + } +}; + +MODULE_INIT(OSAKill) diff --git a/modules/commands/os_chankill.cpp b/modules/commands/os_chankill.cpp new file mode 100644 index 000000000..9ce895c59 --- /dev/null +++ b/modules/commands/os_chankill.cpp @@ -0,0 +1,120 @@ +/* OperServ core 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 "module.h" + +static service_reference<XLineManager> akills("xlinemanager/sgline"); + +class CommandOSChanKill : public Command +{ + public: + CommandOSChanKill(Module *creator) : Command(creator, "operserv/chankill", 2, 3) + { + this->SetDesc(_("AKILL all users on a specific channel")); + this->SetSyntax(_("[+\037expiry\037] \037channel\037 \037reason\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + if (!akills) + return; + + User *u = source.u; + Anope::string expiry, channel; + time_t expires; + unsigned last_param = 1; + Channel *c; + + channel = params[0]; + if (!channel.empty() && channel[0] == '+') + { + expiry = channel; + channel = params[1]; + last_param = 2; + } + + expires = !expiry.empty() ? dotime(expiry) : Config->ChankillExpiry; + if (!expiry.empty() && isdigit(expiry[expiry.length() - 1])) + expires *= 86400; + if (expires && expires < 60) + { + source.Reply(BAD_EXPIRY_TIME); + return; + } + else if (expires > 0) + expires += Anope::CurTime; + + if (params.size() <= last_param) + { + this->OnSyntaxError(source, ""); + return; + } + + Anope::string reason = params[last_param]; + if (params.size() > last_param + 1) + reason += params[last_param + 1]; + if (!reason.empty()) + { + Anope::string realreason; + if (Config->AddAkiller) + realreason = "[" + u->nick + "] " + reason; + else + realreason = reason; + + if ((c = findchan(channel))) + { + for (CUserList::iterator it = c->users.begin(), it_end = c->users.end(); it != it_end; ) + { + UserContainer *uc = *it++; + + if (uc->user->HasMode(UMODE_OPER)) + continue; + + akills->Add("*@" + uc->user->host, u->nick, expires, realreason); + akills->Check(uc->user); + } + + Log(LOG_ADMIN, u, this) << "(" << realreason << ")"; + } + else + source.Reply(CHAN_X_NOT_IN_USE, channel.c_str()); + } + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Puts an AKILL for every nick on the specified channel. It\n" + "uses the entire and complete real ident@host for every nick,\n" + "then enforces the AKILL.")); + return true; + } +}; + +class OSChanKill : public Module +{ + CommandOSChanKill commandoschankill; + + public: + OSChanKill(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandoschankill(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandoschankill); + } +}; + +MODULE_INIT(OSChanKill) diff --git a/modules/commands/os_config.cpp b/modules/commands/os_config.cpp new file mode 100644 index 000000000..1d1fead33 --- /dev/null +++ b/modules/commands/os_config.cpp @@ -0,0 +1,217 @@ +/* OperServ core 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 "module.h" + +class CommandOSConfig : public Command +{ + void ChangeHash(ConfigDataHash &hash, const Anope::string &block, const Anope::string &iname, const Anope::string &value) + { + ConfigDataHash::iterator it = hash.find(block); + + KeyValList &list = it->second; + for (unsigned i = 0; i < list.size(); ++i) + { + const Anope::string &item = list[i].first; + if (item == iname) + { + list[i] = std::make_pair(item, value); + break; + } + } + } + + public: + CommandOSConfig(Module *creator) : Command(creator, "operserv/config", 1, 4) + { + this->SetDesc(_("View and change configuration file settings")); + this->SetSyntax(_("{\037MODIFY\037|\037VIEW\037} [\037block name\037 \037item name\037 \037item value\037]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &what = params[0]; + + if (what.equals_ci("MODIFY") && params.size() > 3) + { + ConfigItems configitems(Config); + + for (unsigned i = 0; !configitems.Values[i].tag.empty(); ++i) + { + ConfigItems::Item *v = &configitems.Values[i]; + if (v->tag.equals_cs(params[1]) && v->value.equals_cs(params[2])) + { + try + { + ValueItem vi(params[3]); + if (!v->validation_function(Config, v->tag, v->value, vi)) + throw ConfigException("Parameter failed to validate."); + + int dt = v->datatype; + + if (dt & DT_NORELOAD) + throw ConfigException("This item can not be changed while Anope is running."); + bool allow_wild = dt & DT_ALLOW_WILD; + dt &= ~(DT_ALLOW_NEWLINE | DT_ALLOW_WILD); + + /* Yay for *massive* copypaste from config.cpp */ + switch (dt) + { + case DT_NOSPACES: + { + ValueContainerString *vcs = debug_cast<ValueContainerString *>(v->val); + Config->ValidateNoSpaces(vi.GetValue(), v->tag, v->value); + vcs->Set(vi.GetValue()); + break; + } + case DT_HOSTNAME: + { + ValueContainerString *vcs = debug_cast<ValueContainerString *>(v->val); + Config->ValidateHostname(vi.GetValue(), v->tag, v->value); + vcs->Set(vi.GetValue()); + break; + } + case DT_IPADDRESS: + { + ValueContainerString *vcs = debug_cast<ValueContainerString *>(v->val); + Config->ValidateIP(vi.GetValue(), v->tag, v->value, allow_wild); + vcs->Set(vi.GetValue()); + break; + } + case DT_STRING: + { + ValueContainerString *vcs = debug_cast<ValueContainerString *>(v->val); + vcs->Set(vi.GetValue()); + break; + } + case DT_INTEGER: + { + int val = vi.GetInteger(); + ValueContainerInt *vci = debug_cast<ValueContainerInt *>(v->val); + vci->Set(&val, sizeof(int)); + break; + } + case DT_UINTEGER: + { + unsigned val = vi.GetInteger(); + ValueContainerUInt *vci = debug_cast<ValueContainerUInt *>(v->val); + vci->Set(&val, sizeof(unsigned)); + break; + } + case DT_LUINTEGER: + { + unsigned long val = vi.GetInteger(); + ValueContainerLUInt *vci = debug_cast<ValueContainerLUInt *>(v->val); + vci->Set(&val, sizeof(unsigned long)); + break; + } + case DT_TIME: + { + time_t time = dotime(vi.GetValue()); + ValueContainerTime *vci = debug_cast<ValueContainerTime *>(v->val); + vci->Set(&time, sizeof(time_t)); + break; + } + case DT_BOOLEAN: + { + bool val = vi.GetBool(); + ValueContainerBool *vcb = debug_cast<ValueContainerBool *>(v->val); + vcb->Set(&val, sizeof(bool)); + break; + } + default: + break; + } + } + catch (const ConfigException &ex) + { + source.Reply(_("Error changing configuration value: ") + ex.GetReason()); + return; + } + + ChangeHash(Config->config_data, params[1], params[2], params[3]); + + Log(LOG_ADMIN, source.u, this) << "to change the configuration value of " << params[1] << ":" << params[2] << " to " << params[3]; + source.Reply(_("Value of %s:%s changed to %s"), params[1].c_str(), params[2].c_str(), params[3].c_str()); + return; + } + } + + source.Reply("There is no configuration value named %s:%s", params[1].c_str(), params[2].c_str()); + } + else if (what.equals_ci("VIEW")) + { + /* Blocks we should show */ + const Anope::string show_blocks[] = { "botserv", "chanserv", "defcon", "global", "memoserv", "nickserv", "networkinfo", "operserv", "options", "" }; + + Log(LOG_ADMIN, source.u, this) << "VIEW"; + + for (ConfigDataHash::const_iterator it = Config->config_data.begin(), it_end = Config->config_data.end(); it != it_end; ++it) + { + const Anope::string &bname = it->first; + const KeyValList &list = it->second; + + bool ok = false; + for (unsigned i = 0; !show_blocks[i].empty(); ++i) + if (bname == show_blocks[i]) + ok = true; + if (ok == false) + continue; + + source.Reply(_("%s settings:"), bname.c_str()); + for (unsigned i = 0; i < list.size(); ++i) + { + const Anope::string &first = list[i].first, second = list[i].second; + + source.Reply(_(" Name: %-15s Value: %s"), first.c_str(), second.c_str()); + } + } + + source.Reply(_("End of configuration.")); + } + else + this->OnSyntaxError(source, what); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Allows you to change and view configuration settings.\n" + "Settings changed by this command are temporary and will not be reflected\n" + "back into the configuration file, and will be lost if Anope is shut down,\n" + "restarted, or the RELOAD command is used.\n" + " \n" + "Example:\n" + " \002MODIFY nickserv forcemail no\002")); + return true; + } +}; + +class OSConfig : public Module +{ + CommandOSConfig commandosconfig; + + public: + OSConfig(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandosconfig(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandosconfig); + } +}; + +MODULE_INIT(OSConfig) diff --git a/modules/commands/os_defcon.cpp b/modules/commands/os_defcon.cpp new file mode 100644 index 000000000..ea513e1dc --- /dev/null +++ b/modules/commands/os_defcon.cpp @@ -0,0 +1,663 @@ +/* OperServ core 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 "module.h" +#include "global.h" +#include "os_session.h" + +enum DefconLevel +{ + DEFCON_NO_NEW_CHANNELS, + DEFCON_NO_NEW_NICKS, + DEFCON_NO_MLOCK_CHANGE, + DEFCON_FORCE_CHAN_MODES, + DEFCON_REDUCE_SESSION, + DEFCON_NO_NEW_CLIENTS, + DEFCON_OPER_ONLY, + DEFCON_SILENT_OPER_ONLY, + DEFCON_AKILL_NEW_CLIENTS, + DEFCON_NO_NEW_MEMOS +}; + +bool DefConModesSet = false; + +struct DefconConfig +{ + std::vector<std::bitset<32> > DefCon; + Flags<ChannelModeName, CMODE_END * 2> DefConModesOn; + Flags<ChannelModeName, CMODE_END * 2> DefConModesOff; + std::map<ChannelModeName, Anope::string> DefConModesOnParams; + + int defaultlevel, sessionlimit; + Anope::string chanmodes, message, offmessage, akillreason; + std::vector<Anope::string> defcons; + time_t akillexpire, timeout; + bool globalondefcon; + + DefconConfig() + { + this->DefCon.resize(6); + this->defcons.resize(5); + } + + bool Check(DefconLevel level) + { + return this->Check(this->defaultlevel, level); + } + + bool Check(int dlevel, DefconLevel level) + { + return this->DefCon[dlevel].test(level); + } + + void Add(int dlevel, DefconLevel level) + { + this->DefCon[dlevel][level] = true; + } + + void Del(int dlevel, DefconLevel level) + { + this->DefCon[dlevel][level] = false; + } + + bool SetDefConParam(ChannelModeName Name, const Anope::string &buf) + { + return DefConModesOnParams.insert(std::make_pair(Name, buf)).second; + } + + void UnsetDefConParam(ChannelModeName Name) + { + DefConModesOnParams.erase(Name); + } + + bool GetDefConParam(ChannelModeName Name, Anope::string &buf) + { + std::map<ChannelModeName, Anope::string>::iterator it = DefConModesOnParams.find(Name); + + buf.clear(); + + if (it != DefConModesOnParams.end()) + { + buf = it->second; + return true; + } + + return false; + } +}; + +static DefconConfig DConfig; + +/**************************************************************************/ + +void defcon_sendlvls(CommandSource &source); +void runDefCon(); +static Anope::string defconReverseModes(const Anope::string &modes); + +class DefConTimeout : public CallBack +{ + int level; + + public: + DefConTimeout(Module *mod, int newlevel) : CallBack(mod, DConfig.timeout), level(newlevel) { } + + void Tick(time_t) + { + if (DConfig.defaultlevel != level) + { + DConfig.defaultlevel = level; + FOREACH_MOD(I_OnDefconLevel, OnDefconLevel(level)); + Log(findbot(Config->OperServ), "operserv/defcon") << "Defcon level timeout, returning to level " << level; + + if (DConfig.globalondefcon) + { + if (!DConfig.offmessage.empty()) + global->SendGlobal(findbot(Config->Global), "", DConfig.offmessage); + else + global->SendGlobal(findbot(Config->Global), "", Anope::printf(translate(_("The Defcon Level is now at Level: \002%d\002")), DConfig.defaultlevel)); + + if (!DConfig.message.empty()) + global->SendGlobal(findbot(Config->Global), "", DConfig.message); + } + + runDefCon(); + } + } +}; +static DefConTimeout *timeout; + +class CommandOSDefcon : public Command +{ + void SendLevels(CommandSource &source) + { + if (DConfig.Check(DEFCON_NO_NEW_CHANNELS)) + source.Reply(_("* No new channel registrations")); + if (DConfig.Check(DEFCON_NO_NEW_NICKS)) + source.Reply(_("* No new nick registrations")); + if (DConfig.Check(DEFCON_NO_MLOCK_CHANGE)) + source.Reply(_("* No MLOCK changes")); + if (DConfig.Check(DEFCON_FORCE_CHAN_MODES) && !DConfig.chanmodes.empty()) + source.Reply(_("* Force Chan Modes (%s) to be set on all channels"), DConfig.chanmodes.c_str()); + if (DConfig.Check(DEFCON_REDUCE_SESSION)) + source.Reply(_("* Use the reduced session limit of %d"), DConfig.sessionlimit); + if (DConfig.Check(DEFCON_NO_NEW_CLIENTS)) + source.Reply(_("* Kill any NEW clients connecting")); + if (DConfig.Check(DEFCON_OPER_ONLY)) + source.Reply(_("* Ignore any non-opers with message")); + if (DConfig.Check(DEFCON_SILENT_OPER_ONLY)) + source.Reply(_("* Silently ignore non-opers")); + if (DConfig.Check(DEFCON_AKILL_NEW_CLIENTS)) + source.Reply(_("* AKILL any new clients connecting")); + if (DConfig.Check(DEFCON_NO_NEW_MEMOS)) + source.Reply(_("* No new memos sent")); + } + + public: + CommandOSDefcon(Module *creator) : Command(creator, "operserv/defcon", 1, 1) + { + this->SetDesc(_("Manipulate the DefCon system")); + this->SetSyntax(_("[\0021\002|\0022\002|\0023\002|\0024\002|\0025\002]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + const Anope::string &lvl = params[0]; + + if (lvl.empty()) + { + source.Reply(_("Services are now at DEFCON \002%d\002"), DConfig.defaultlevel); + this->SendLevels(source); + return; + } + + int newLevel = 0; + try + { + newLevel = convertTo<int>(lvl); + } + catch (const ConvertException &) { } + + if (newLevel < 1 || newLevel > 5) + { + this->OnSyntaxError(source, ""); + return; + } + + DConfig.defaultlevel = newLevel; + + FOREACH_MOD(I_OnDefconLevel, OnDefconLevel(newLevel)); + + if (timeout) + { + delete timeout; + timeout = NULL; + } + + if (DConfig.timeout) + timeout = new DefConTimeout(this->module, 5); + + source.Reply(_("Services are now at DEFCON \002%d\002"), DConfig.defaultlevel); + this->SendLevels(source); + Log(LOG_ADMIN, u, this) << "to change defcon level to " << newLevel; + + /* Global notice the user what is happening. Also any Message that + the Admin would like to add. Set in config file. */ + if (DConfig.globalondefcon) + { + if (DConfig.defaultlevel == 5 && !DConfig.offmessage.empty()) + global->SendGlobal(findbot(Config->Global), "", DConfig.offmessage); + else if (DConfig.defaultlevel != 5) + { + global->SendGlobal(findbot(Config->Global), "", Anope::printf(_("The Defcon level is now at: \002%d\002"), DConfig.defaultlevel)); + if (!DConfig.message.empty()) + global->SendGlobal(findbot(Config->Global), "", DConfig.message); + } + } + + /* Run any defcon functions, e.g. FORCE CHAN MODE */ + runDefCon(); + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("The defcon system can be used to implement a pre-defined\n" + "set of restrictions to services useful during an attempted\n" + "attack on the network.")); + return true; + } +}; + +class OSDefcon : public Module +{ + service_reference<SessionService> session_service; + service_reference<XLineManager> akills; + CommandOSDefcon commandosdefcon; + + void ParseModeString() + { + int add = -1; /* 1 if adding, 0 if deleting, -1 if neither */ + unsigned char mode; + ChannelMode *cm; + ChannelModeParam *cmp; + Anope::string modes, param; + + spacesepstream ss(DConfig.chanmodes); + + DConfig.DefConModesOn.ClearFlags(); + DConfig.DefConModesOff.ClearFlags(); + ss.GetToken(modes); + + /* Loop while there are modes to set */ + for (unsigned i = 0, end = modes.length(); i < end; ++i) + { + mode = modes[i]; + + switch (mode) + { + case '+': + add = 1; + continue; + case '-': + add = 0; + continue; + default: + if (add < 0) + continue; + } + + if ((cm = ModeManager::FindChannelModeByChar(mode))) + { + if (cm->Type == MODE_STATUS || cm->Type == MODE_LIST || !cm->CanSet(NULL)) + { + Log() << "DefConChanModes mode character '" << mode << "' cannot be locked"; + continue; + } + else if (add) + { + DConfig.DefConModesOn.SetFlag(cm->Name); + DConfig.DefConModesOff.UnsetFlag(cm->Name); + + if (cm->Type == MODE_PARAM) + { + cmp = debug_cast<ChannelModeParam *>(cm); + + if (!ss.GetToken(param)) + { + Log() << "DefConChanModes mode character '" << mode << "' has no parameter while one is expected"; + continue; + } + + if (!cmp->IsValid(param)) + continue; + + DConfig.SetDefConParam(cmp->Name, param); + } + } + else if (DConfig.DefConModesOn.HasFlag(cm->Name)) + { + DConfig.DefConModesOn.UnsetFlag(cm->Name); + + if (cm->Type == MODE_PARAM) + DConfig.UnsetDefConParam(cm->Name); + } + } + } + + /* We can't mlock +L if +l is not mlocked as well. */ + if ((cm = ModeManager::FindChannelModeByName(CMODE_REDIRECT)) && DConfig.DefConModesOn.HasFlag(cm->Name) && !DConfig.DefConModesOn.HasFlag(CMODE_LIMIT)) + { + DConfig.DefConModesOn.UnsetFlag(CMODE_REDIRECT); + + Log() << "DefConChanModes must lock mode +l as well to lock mode +L"; + } + + /* Some ircd we can't set NOKNOCK without INVITE */ + /* So check if we need there is a NOKNOCK MODE and that we need INVITEONLY */ + if (ircd->knock_needs_i && (cm = ModeManager::FindChannelModeByName(CMODE_NOKNOCK)) && DConfig.DefConModesOn.HasFlag(cm->Name) && !DConfig.DefConModesOn.HasFlag(CMODE_INVITE)) + { + DConfig.DefConModesOn.UnsetFlag(CMODE_NOKNOCK); + Log() << "DefConChanModes must lock mode +i as well to lock mode +K"; + } + } + + public: + OSDefcon(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, SUPPORTED), session_service("session"), akills("xlinemanager/sgline"), commandosdefcon(this) + { + this->SetAuthor("Anope"); + + Implementation i[] = { I_OnReload, I_OnChannelModeSet, I_OnChannelModeUnset, I_OnPreCommand, I_OnUserConnect, I_OnChannelModeAdd, I_OnChannelCreate }; + ModuleManager::Attach(i, this, sizeof(i) / sizeof(Implementation)); + + ModuleManager::RegisterService(&commandosdefcon); + + try + { + this->OnReload(); + } + catch (const ConfigException &ex) + { + throw ModuleException(ex.GetReason()); + } + } + + void OnReload() + { + ConfigReader config; + DefconConfig dconfig; + + dconfig.defaultlevel = config.ReadInteger("defcon", "defaultlevel", 0, 0); + dconfig.defcons[4] = config.ReadValue("defcon", "level4", 0); + dconfig.defcons[3] = config.ReadValue("defcon", "level3", 0); + dconfig.defcons[2] = config.ReadValue("defcon", "level2", 0); + dconfig.defcons[1] = config.ReadValue("defcon", "level1", 0); + dconfig.sessionlimit = config.ReadInteger("defcon", "sessionlimit", 0, 0); + dconfig.akillreason = config.ReadValue("defcon", "akillreason", 0); + dconfig.akillexpire = dotime(config.ReadValue("defcon", "akillexpire", 0)); + dconfig.chanmodes = config.ReadValue("defcon", "chanmodes", 0); + dconfig.timeout = dotime(config.ReadValue("defcon", "timeout", 0)); + dconfig.globalondefcon = config.ReadFlag("defcon", "globalondefcon", 0); + dconfig.message = config.ReadValue("defcon", "message", 0); + dconfig.offmessage = config.ReadValue("defcon", "offmessage", 0); + + if (dconfig.defaultlevel < 1 || dconfig.defaultlevel > 5) + throw ConfigException("The value for <defcon:defaultlevel> must be between 1 and 5"); + else if (dconfig.akillexpire <= 0) + throw ConfigException("The value for <defcon:akillexpire> must be greater than zero!"); + + for (unsigned level = 1; level < 5; ++level) + { + spacesepstream operations(dconfig.defcons[level]); + Anope::string operation; + while (operations.GetToken(operation)) + { + if (operation.equals_ci("nonewchannels")) + dconfig.Add(level, DEFCON_NO_NEW_CHANNELS); + else if (operation.equals_ci("nonewnicks")) + dconfig.Add(level, DEFCON_NO_NEW_NICKS); + else if (operation.equals_ci("nomlockchanges")) + dconfig.Add(level, DEFCON_NO_MLOCK_CHANGE); + else if (operation.equals_ci("forcechanmodes")) + dconfig.Add(level, DEFCON_FORCE_CHAN_MODES); + else if (operation.equals_ci("reducedsessions")) + dconfig.Add(level, DEFCON_REDUCE_SESSION); + else if (operation.equals_ci("nonewclients")) + dconfig.Add(level, DEFCON_NO_NEW_CLIENTS); + else if (operation.equals_ci("operonly")) + dconfig.Add(level, DEFCON_OPER_ONLY); + else if (operation.equals_ci("silentoperonly")) + dconfig.Add(level, DEFCON_SILENT_OPER_ONLY); + else if (operation.equals_ci("akillnewclients")) + dconfig.Add(level, DEFCON_AKILL_NEW_CLIENTS); + else if (operation.equals_ci("nonewmemos")) + dconfig.Add(level, DEFCON_NO_NEW_MEMOS); + } + + if (dconfig.Check(level, DEFCON_REDUCE_SESSION) && dconfig.sessionlimit <= 0) + throw ConfigException("The value for <defcon:sessionlimit> must be greater than zero!"); + else if (dconfig.Check(level, DEFCON_AKILL_NEW_CLIENTS) && dconfig.akillreason.empty()) + throw ConfigException("The value for <defcon:akillreason> must not be empty!"); + else if (dconfig.Check(level, DEFCON_FORCE_CHAN_MODES) && dconfig.chanmodes.empty()) + throw ConfigException("The value for <defcon:chanmodes> must not be empty!"); + } + + DConfig = dconfig; + this->ParseModeString(); + } + + EventReturn OnUserConnect(User *u, bool &exempt) + { + if (!exempt && u->server->IsSynced() && DConfig.Check(DEFCON_AKILL_NEW_CLIENTS) && !u->server->IsULined()) + { + if (DConfig.Check(DEFCON_AKILL_NEW_CLIENTS) && akills) + { + Log(findbot(Config->OperServ), "operserv/defcon") << "DEFCON: adding akill for *@" << u->host; + XLine *x = akills->Add("*@" + u->host, Config->OperServ, Anope::CurTime + DConfig.akillexpire, DConfig.akillreason); + if (x) + x->By = Config->OperServ; + } + + if (DConfig.Check(DEFCON_NO_NEW_CLIENTS) || DConfig.Check(DEFCON_AKILL_NEW_CLIENTS)) + u->Kill(Config->OperServ, DConfig.akillreason); + + return EVENT_STOP; + } + + return EVENT_CONTINUE; + } + + EventReturn OnChannelModeSet(Channel *c, ChannelModeName Name, const Anope::string ¶m) + { + ChannelMode *cm = ModeManager::FindChannelModeByName(Name); + + if (DConfig.Check(DEFCON_FORCE_CHAN_MODES) && cm && DConfig.DefConModesOff.HasFlag(Name)) + { + c->RemoveMode(findbot(Config->OperServ), Name, param); + + return EVENT_STOP; + } + + return EVENT_CONTINUE; + } + + EventReturn OnChannelModeUnset(Channel *c, ChannelModeName Name, const Anope::string &) + { + ChannelMode *cm = ModeManager::FindChannelModeByName(Name); + + if (DConfig.Check(DEFCON_FORCE_CHAN_MODES) && cm && DConfig.DefConModesOn.HasFlag(Name)) + { + Anope::string param; + + if (DConfig.GetDefConParam(Name, param)) + c->SetMode(findbot(Config->OperServ), Name, param); + else + c->SetMode(findbot(Config->OperServ), Name); + + return EVENT_STOP; + + } + + return EVENT_CONTINUE; + } + + EventReturn OnPreCommand(CommandSource &source, Command *command, std::vector<Anope::string> ¶ms) + { + if (command->name == "nickserv/register" || command->name == "nickserv/group") + { + if (DConfig.Check(DEFCON_NO_NEW_NICKS)) + { + source.Reply(_("Services are in Defcon mode, Please try again later.")); + return EVENT_STOP; + } + } + else if (command->name == "chanserv/set/mlock") + { + if (DConfig.Check(DEFCON_NO_MLOCK_CHANGE)) + { + source.Reply(_("Services are in Defcon mode, Please try again later.")); + return EVENT_STOP; + } + } + else if (command->name == "chanserv/register") + { + if (DConfig.Check(DEFCON_NO_NEW_CHANNELS)) + { + source.Reply(_("Services are in Defcon mode, Please try again later.")); + return EVENT_STOP; + } + } + else if (command->name == "memoserv/send") + { + if (DConfig.Check(DEFCON_NO_NEW_MEMOS)) + { + source.Reply(_("Services are in Defcon mode, Please try again later.")); + return EVENT_STOP; + } + } + + return EVENT_CONTINUE; + } + + void OnUserConnect(dynamic_reference<User> &u, bool &exempt) + { + if (exempt || !u || !u->server->IsSynced() || u->server->IsULined()) + return; + + if (DConfig.Check(DEFCON_AKILL_NEW_CLIENTS) && akills) + { + Log(findbot(Config->OperServ), "operserv/defcon") << "DEFCON: adding akill for *@" << u->host; + XLine *x = akills->Add("*@" + u->host, Config->OperServ, Anope::CurTime + DConfig.akillexpire, DConfig.akillreason); + if (x) + x->By = Config->OperServ; + } + if (DConfig.Check(DEFCON_NO_NEW_CLIENTS) || DConfig.Check(DEFCON_AKILL_NEW_CLIENTS)) + { + u->Kill(Config->OperServ, DConfig.akillreason); + return; + } + + if (!DConfig.sessionlimit) + return; + + if (DConfig.Check(DEFCON_AKILL_NEW_CLIENTS) && akills) + { + Log(findbot(Config->OperServ), "operserv/defcon") << "DEFCON: adding akill for *@" << u->host; + XLine *x = akills->Add("*@" + u->host, Config->OperServ, Anope::CurTime + DConfig.akillexpire, !DConfig.akillreason.empty() ? DConfig.akillreason : "DEFCON AKILL"); + if (x) + x->By = Config->OperServ; + } + + if (DConfig.Check(DEFCON_NO_NEW_CLIENTS) || DConfig.Check(DEFCON_AKILL_NEW_CLIENTS)) + { + u->Kill(Config->OperServ, DConfig.akillreason); + return; + } + + Session *session = session_service->FindSession(u->host); + Exception *exception = session_service->FindException(u); + + if (DConfig.Check(DEFCON_REDUCE_SESSION) && !exception) + { + if (session && session->count > DConfig.sessionlimit) + { + if (!Config->SessionLimitExceeded.empty()) + ircdproto->SendMessage(findbot(Config->OperServ), u->nick, Config->SessionLimitExceeded.c_str(), u->host.c_str()); + if (!Config->SessionLimitDetailsLoc.empty()) + ircdproto->SendMessage(findbot(Config->OperServ), u->nick, "%s", Config->SessionLimitDetailsLoc.c_str()); + + u->Kill(Config->OperServ, "Defcon session limit exceeded"); + ++session->hits; + if (akills && Config->MaxSessionKill && session->hits >= Config->MaxSessionKill) + { + akills->Add("*@" + u->host, Config->OperServ, Anope::CurTime + Config->SessionAutoKillExpiry, "Defcon session limit exceeded"); + ircdproto->SendGlobops(findbot(Config->OperServ), "[DEFCON] Added a temporary AKILL for \2*@%s\2 due to excessive connections", u->host.c_str()); + } + } + } + } + + void OnChannelModeAdd(ChannelMode *cm) + { + if (DConfig.chanmodes.find(cm->ModeChar) != Anope::string::npos) + this->ParseModeString(); + } + + void OnChannelCreate(Channel *c) + { + if (DConfig.Check(DEFCON_FORCE_CHAN_MODES)) + c->SetModes(findbot(Config->OperServ), false, "%s", DConfig.chanmodes.c_str()); + } +}; + +/** + * Send a message to the oper about which precautions are "active" for this level + **/ +void defcon_sendlvls(CommandSource &source) +{ + if (DConfig.Check(DEFCON_NO_NEW_CHANNELS)) + source.Reply(_("* No new channel registrations")); + if (DConfig.Check(DEFCON_NO_NEW_NICKS)) + source.Reply(_("* No new nick registrations")); + if (DConfig.Check(DEFCON_NO_MLOCK_CHANGE)) + source.Reply(_("* No MLOCK changes")); + if (DConfig.Check(DEFCON_FORCE_CHAN_MODES) && !DConfig.chanmodes.empty()) + source.Reply(_("* Force Chan Modes (%s) to be set on all channels"), DConfig.chanmodes.c_str()); + if (DConfig.Check(DEFCON_REDUCE_SESSION)) + source.Reply(_("* Use the reduced session limit of %d"), DConfig.sessionlimit); + if (DConfig.Check(DEFCON_NO_NEW_CLIENTS)) + source.Reply(_("* Kill any NEW clients connecting")); + if (DConfig.Check(DEFCON_OPER_ONLY)) + source.Reply(_("* Ignore any non-opers with message")); + if (DConfig.Check(DEFCON_SILENT_OPER_ONLY)) + source.Reply(_("* Silently ignore non-opers")); + if (DConfig.Check(DEFCON_AKILL_NEW_CLIENTS)) + source.Reply(_("* AKILL any new clients connecting")); + if (DConfig.Check(DEFCON_NO_NEW_MEMOS)) + source.Reply(_("* No new memos sent")); +} + +void runDefCon() +{ + if (DConfig.Check(DEFCON_FORCE_CHAN_MODES)) + { + if (!DConfig.chanmodes.empty() && !DefConModesSet) + { + if (DConfig.chanmodes[0] == '+' || DConfig.chanmodes[0] == '-') + { + Log(findbot(Config->OperServ), "operserv/defcon") << "DEFCON: setting " << DConfig.chanmodes << " on all channels"; + DefConModesSet = true; + for (channel_map::const_iterator it = ChannelList.begin(), it_end = ChannelList.end(); it != it_end; ++it) + it->second->SetModes(findbot(Config->OperServ), false, "%s", DConfig.chanmodes.c_str()); + } + } + } + else + { + if (!DConfig.chanmodes.empty() && DefConModesSet) + { + if (DConfig.chanmodes[0] == '+' || DConfig.chanmodes[0] == '-') + { + DefConModesSet = false; + Anope::string newmodes = defconReverseModes(DConfig.chanmodes); + if (!newmodes.empty()) + { + Log(findbot(Config->OperServ), "operserv/defcon") << "DEFCON: setting " << newmodes << " on all channels"; + for (channel_map::const_iterator it = ChannelList.begin(), it_end = ChannelList.end(); it != it_end; ++it) + it->second->SetModes(findbot(Config->OperServ), false, "%s", newmodes.c_str()); + } + } + } + } +} + +static Anope::string defconReverseModes(const Anope::string &modes) +{ + if (modes.empty()) + return ""; + Anope::string newmodes; + for (unsigned i = 0, end = modes.length(); i < end; ++i) + { + if (modes[i] == '+') + newmodes += '-'; + else if (modes[i] == '-') + newmodes += '+'; + else + newmodes += modes[i]; + } + return newmodes; +} + +MODULE_INIT(OSDefcon) diff --git a/modules/commands/os_forbid.cpp b/modules/commands/os_forbid.cpp new file mode 100644 index 000000000..d4b4c2b41 --- /dev/null +++ b/modules/commands/os_forbid.cpp @@ -0,0 +1,342 @@ +/* OperServ core 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 "module.h" +#include "os_forbid.h" + +class MyForbidService : public ForbidService +{ + std::vector<ForbidData *> forbidData; + + public: + MyForbidService(Module *m) : ForbidService(m) { } + + void AddForbid(ForbidData *d) + { + this->forbidData.push_back(d); + } + + void RemoveForbid(ForbidData *d) + { + std::vector<ForbidData *>::iterator it = std::find(this->forbidData.begin(), this->forbidData.end(), d); + if (it != this->forbidData.end()) + this->forbidData.erase(it); + delete d; + } + + ForbidData *FindForbid(const Anope::string &mask, ForbidType type) + { + const std::vector<ForbidData *> &forbids = this->GetForbids(); + for (unsigned i = forbids.size(); i > 0; --i) + { + ForbidData *d = this->forbidData[i - 1]; + + if ((type == FT_NONE || type == d->type) && Anope::Match(mask, d->mask)) + return d; + } + return NULL; + } + + const std::vector<ForbidData *> &GetForbids() + { + for (unsigned i = this->forbidData.size(); i > 0; --i) + { + ForbidData *d = this->forbidData[i - 1]; + + if (d->expires && Anope::CurTime >= d->expires) + { + Anope::string type = "none"; + if (d->type == FT_NICK) + type = "nick"; + else if (d->type == FT_CHAN) + type = "chan"; + else if (d->type == FT_EMAIL) + type = "email"; + + Log(LOG_NORMAL, Config->OperServ + "/forbid") << "Expiring forbid for " << d->mask << " type " << type; + this->forbidData.erase(this->forbidData.begin() + i - 1); + delete d; + } + } + + return this->forbidData; + } +}; + +class CommandOSForbid : public Command +{ + service_reference<ForbidService> fs; + public: + CommandOSForbid(Module *creator) : Command(creator, "operserv/forbid", 1, 5), fs("forbid") + { + this->SetDesc(_("Forbid usage of nicknames, channels, and emails")); + this->SetSyntax(_("ADD {NICK|CHAN|EMAIL} [+\037expiry\037] \037entry\037\002 [\037reason\037]")); + this->SetSyntax(_("DEL {NICK|CHAN|EMAIL} \037entry\037")); + this->SetSyntax(_("LIST (NICK|CHAN|EMAIL)")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + if (!this->fs) + return; + + const Anope::string &command = params[0]; + const Anope::string &subcommand = params.size() > 1 ? params[1] : ""; + + ForbidType type = FT_NONE; + if (subcommand.equals_ci("NICK")) + type = FT_NICK; + else if (subcommand.equals_ci("CHAN")) + type = FT_CHAN; + else if (subcommand.equals_ci("EMAIL")) + type = FT_EMAIL; + + if (command.equals_ci("ADD") && params.size() > 2 && type != FT_NONE) + { + const Anope::string &expiry = params[2][0] == '+' ? params[2] : ""; + const Anope::string &entry = params.size() > 3 && !expiry.empty() ? params[3] : params[2]; + Anope::string reason = !expiry.empty() && params.size() > 4 ? params[4] : (params.size() > 3 ? params[3] : ""); + + time_t expiryt = 0; + if (!expiry.empty()) + { + expiryt = dotime(expiry); + if (expiryt) + expiryt += Anope::CurTime; + } + + ForbidData *d = this->fs->FindForbid(entry, type); + bool created = false; + if (d == NULL) + { + d = new ForbidData(); + created = true; + } + + d->mask = entry; + d->creator = source.u->nick; + d->reason = reason; + d->created = Anope::CurTime; + d->expires = expiryt; + d->type = type; + if (created) + this->fs->AddForbid(d); + + Log(LOG_ADMIN, source.u, this) << "to add a forbid on " << entry << " of type " << subcommand; + source.Reply(_("Added a%s forbid on %s to expire on %s"), type == FT_CHAN ? "n" : "", entry.c_str(), d->expires ? do_strftime(d->expires).c_str() : "never"); + } + else if (command.equals_ci("DEL") && params.size() > 2 && type != FT_NONE) + { + const Anope::string &entry = params[2]; + + ForbidData *d = this->fs->FindForbid(entry, type); + if (d != NULL) + { + Log(LOG_ADMIN, source.u, this) << "to remove forbid " << d->mask << " of type " << subcommand; + source.Reply(_("%s deleted from the %s forbid list."), d->mask.c_str(), subcommand.c_str()); + } + else + source.Reply(_("Forbid on %s was not found."), entry.c_str()); + } + else if (command.equals_ci("LIST")) + { + const std::vector<ForbidData *> &forbids = this->fs->GetForbids(); + if (forbids.empty()) + source.Reply(_("Forbid list is empty.")); + else + { + source.Reply(_("Forbid list:")); + source.Reply(_("Mask Type Reason")); + + for (unsigned i = 0; i < forbids.size(); ++i) + { + ForbidData *d = forbids[i]; + + Anope::string ftype; + if (d->type == FT_NICK) + ftype = "NICK"; + else if (d->type == FT_CHAN) + ftype = "CHAN"; + else if (d->type == FT_EMAIL) + ftype = "EMAIL"; + else + continue; + + source.Reply("%-10s %-5s %s", d->mask.c_str(), ftype.c_str(), d->reason.c_str()); + source.Reply(_("By %s, expires on %s"), d->creator.c_str(), d->expires ? do_strftime(d->expires).c_str() : "never"); + } + + source.Reply(_("End of forbid list.")); + } + } + else + this->OnSyntaxError(source, command); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Forbid allows you to forbid usage of certain nicknames, channels,\n" + "and email addresses. Wildcards are accepted for all entries.")); + return true; + } +}; + +class OSForbid : public Module +{ + MyForbidService forbidService; + CommandOSForbid commandosforbid; + + public: + OSForbid(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + forbidService(this), commandosforbid(this) + { + this->SetAuthor("Anope"); + + Implementation i[] = { I_OnUserConnect, I_OnUserNickChange, I_OnJoinChannel, I_OnPreCommand, I_OnDatabaseWrite, I_OnDatabaseRead }; + ModuleManager::Attach(i, this, sizeof(i) / sizeof(Implementation)); + + ModuleManager::RegisterService(&this->forbidService); + + ModuleManager::RegisterService(&commandosforbid); + } + + void OnUserConnect(dynamic_reference<User> &u, bool &exempt) + { + if (!u || exempt) + return; + + this->OnUserNickChange(u, ""); + } + + void OnUserNickChange(User *u, const Anope::string &) + { + if (u->HasMode(UMODE_OPER)) + return; + + ForbidData *d = this->forbidService.FindForbid(u->nick, FT_NICK); + if (d != NULL) + { + BotInfo *bi = findbot(Config->OperServ); + if (bi) + { + if (d->reason.empty()) + u->SendMessage(bi, _("This nickname has been forbidden.")); + else + u->SendMessage(bi, _("This nickname has been forbidden: %s"), d->reason.c_str()); + } + u->Collide(NULL); + } + } + + void OnJoinChannel(User *u, Channel *c) + { + if (u->HasMode(UMODE_OPER)) + return; + + BotInfo *bi = findbot(Config->OperServ); + ForbidData *d = this->forbidService.FindForbid(c->name, FT_CHAN); + if (bi != NULL && d != NULL) + { + if (!c->HasFlag(CH_INHABIT)) + { + /* Join ChanServ and set a timer for this channel to part ChanServ later */ + new ChanServTimer(c); + + /* Set +si to prevent rejoin */ + c->SetMode(NULL, CMODE_NOEXTERNAL); + c->SetMode(NULL, CMODE_TOPIC); + c->SetMode(NULL, CMODE_SECRET); + c->SetMode(NULL, CMODE_INVITE); + } + + if (d->reason.empty()) + c->Kick(bi, u, _("This channel has been forbidden.")); + else + c->Kick(bi, u, _("This channel has been forbidden: %s"), d->reason.c_str()); + } + } + + EventReturn OnPreCommand(CommandSource &source, Command *command, std::vector<Anope::string> ¶ms) + { + if (source.u->HasMode(UMODE_OPER)) + return EVENT_CONTINUE; + else if (command->name == "nickserv/register" && params.size() > 1) + { + ForbidData *d = this->forbidService.FindForbid(params[1], FT_EMAIL); + if (d != NULL) + { + source.Reply("Your email address is not allowed, choose a different one."); + return EVENT_STOP; + } + } + else if (command->name == "nickserv/set/email" && params.size() > 0) + { + ForbidData *d = this->forbidService.FindForbid(params[0], FT_EMAIL); + if (d != NULL) + { + source.Reply("Your email address is not allowed, choose a different one."); + return EVENT_STOP; + } + } + + return EVENT_CONTINUE; + } + + void OnDatabaseWrite(void (*Write)(const Anope::string &)) + { + std::vector<ForbidData *> forbids = this->forbidService.GetForbids(); + for (unsigned i = 0; i < forbids.size(); ++i) + { + ForbidData *f = forbids[i]; + Anope::string ftype; + if (f->type == FT_NICK) + ftype = "NICK"; + else if (f->type == FT_CHAN) + ftype = "CHAN"; + else if (f->type == FT_EMAIL) + ftype = "EMAIL"; + Write("FORBID " + f->mask + " " + f->creator + " " + stringify(f->created) + " " + stringify(f->expires) + " " + ftype + " " + f->reason); + } + } + + EventReturn OnDatabaseRead(const std::vector<Anope::string> ¶ms) + { + if (params.size() > 5 && params[0] == "FORBID") + { + ForbidData *f = new ForbidData(); + f->mask = params[1]; + f->creator = params[2]; + f->created = convertTo<time_t>(params[3]); + f->expires = convertTo<time_t>(params[4]); + if (params[5] == "NICK") + f->type = FT_NICK; + else if (params[5] == "CHAN") + f->type = FT_CHAN; + else if (params[5] == "EMAIL") + f->type = FT_EMAIL; + else + f->type = FT_NONE; + f->reason = params.size() > 6 ? params[6] : ""; + + this->forbidService.AddForbid(f); + } + + return EVENT_CONTINUE; + } +}; + +MODULE_INIT(OSForbid) diff --git a/modules/commands/os_forbid.h b/modules/commands/os_forbid.h new file mode 100644 index 000000000..b91aa3be8 --- /dev/null +++ b/modules/commands/os_forbid.h @@ -0,0 +1,37 @@ +#ifndef OS_FORBID_H +#define OS_FORBID_H + +enum ForbidType +{ + FT_NONE, + FT_NICK, + FT_CHAN, + FT_EMAIL +}; + +struct ForbidData +{ + Anope::string mask; + Anope::string creator; + Anope::string reason; + time_t created; + time_t expires; + ForbidType type; +}; + +class ForbidService : public Service +{ + public: + ForbidService(Module *m) : Service(m, "forbid") { } + + virtual void AddForbid(ForbidData *d) = 0; + + virtual void RemoveForbid(ForbidData *d) = 0; + + virtual ForbidData *FindForbid(const Anope::string &mask, ForbidType type) = 0; + + virtual const std::vector<ForbidData *> &GetForbids() = 0; +}; + +#endif + diff --git a/modules/commands/os_ignore.cpp b/modules/commands/os_ignore.cpp new file mode 100644 index 000000000..9f50ec967 --- /dev/null +++ b/modules/commands/os_ignore.cpp @@ -0,0 +1,350 @@ +/* OperServ core 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 "module.h" +#include "os_ignore.h" + +class OSIgnoreService : public IgnoreService +{ + public: + OSIgnoreService(Module *o, const Anope::string &n) : IgnoreService(o, n) { } + + void AddIgnore(const Anope::string &mask, const Anope::string &creator, const Anope::string &reason, time_t delta = Anope::CurTime) + { + /* If it s an existing user, we ignore the hostmask. */ + Anope::string realmask = mask; + size_t user, host; + + User *u = finduser(mask); + if (u) + realmask = "*!*@" + u->host; + /* Determine whether we get a nick or a mask. */ + else if ((host = mask.find('@')) != Anope::string::npos) + { + /* Check whether we have a nick too.. */ + if ((user = mask.find('!')) != Anope::string::npos) + { + /* this should never happen */ + if (user > host) + return; + } + else + /* We have user@host. Add nick wildcard. */ + realmask = "*!" + mask; + } + /* We only got a nick.. */ + else + realmask = mask + "!*@*"; + + /* Check if we already got an identical entry. */ + IgnoreData *ign = this->Find(realmask); + if (ign != NULL) + { + if (!delta) + ign->time = 0; + else + ign->time = Anope::CurTime + delta; + } + /* Create new entry.. */ + else + { + IgnoreData newign; + newign.mask = realmask; + newign.creator = creator; + newign.reason = reason; + newign.time = delta ? Anope::CurTime + delta : 0; + this->ignores.push_back(newign); + } + } + + bool DelIgnore(const Anope::string &mask) + { + for (std::list<IgnoreData>::iterator it = this->ignores.begin(), it_end = this->ignores.end(); it != it_end; ++it) + { + IgnoreData &idn = *it; + if (idn.mask.equals_ci(mask)) + { + this->ignores.erase(it); + return true; + } + } + + return false; + } + + IgnoreData *Find(const Anope::string &mask) + { + User *u = finduser(mask); + std::list<IgnoreData>::iterator ign = this->ignores.begin(), ign_end = this->ignores.end(); + + if (u) + { + for (; ign != ign_end; ++ign) + { + Entry ignore_mask(CMODE_BEGIN, ign->mask); + if (ignore_mask.Matches(u)) + break; + } + } + else + { + size_t user, host; + Anope::string tmp; + /* We didn't get a user.. generate a valid mask. */ + if ((host = mask.find('@')) != Anope::string::npos) + { + if ((user = mask.find('!')) != Anope::string::npos) + { + /* this should never happen */ + if (user > host) + return NULL; + tmp = mask; + } + else + /* We have user@host. Add nick wildcard. */ + tmp = "*!" + mask; + } + /* We only got a nick.. */ + else + tmp = mask + "!*@*"; + + for (; ign != ign_end; ++ign) + if (Anope::Match(tmp, ign->mask)) + break; + } + + /* Check whether the entry has timed out */ + if (ign != ign_end)// && (*ign)->time && (*ign)->time <= Anope::CurTime) + { + IgnoreData &id = *ign; + + if (id.time && id.time <= Anope::CurTime) + { + Log(LOG_DEBUG) << "Expiring ignore entry " << id.mask; + this->ignores.erase(ign); + } + else + return &id; + } + + return NULL; + } +}; + +class CommandOSIgnore : public Command +{ + private: + void DoAdd(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + service_reference<IgnoreService> ignore_service("ignore"); + if (!ignore_service) + return; + + const Anope::string &time = params.size() > 1 ? params[1] : ""; + const Anope::string &nick = params.size() > 2 ? params[2] : ""; + const Anope::string &reason = params.size() > 3 ? params[3] : ""; + + if (time.empty() || nick.empty()) + { + this->OnSyntaxError(source, "ADD"); + return; + } + else + { + time_t t = dotime(time); + + if (t <= -1) + { + source.Reply(_("You have to enter a valid number as time.")); + return; + } + + ignore_service->AddIgnore(nick, source.u->nick, reason, t); + if (!t) + source.Reply(_("\002%s\002 will now permanently be ignored."), nick.c_str()); + else + source.Reply(_("\002%s\002 will now be ignored for \002%s\002."), nick.c_str(), time.c_str()); + } + + return; + } + + void DoList(CommandSource &source) + { + service_reference<IgnoreService> ignore_service("ignore"); + if (!ignore_service) + return; + + const std::list<IgnoreData> &ignores = ignore_service->GetIgnores(); + if (ignores.empty()) + source.Reply(_("Ignore list is empty.")); + else + { + source.Reply(_("Services ignore list:\n" + " Mask Creator Reason Expires")); + for (std::list<IgnoreData>::const_iterator ign = ignores.begin(), ign_end = ignores.end(); ign != ign_end; ++ign) + { + const IgnoreData &ignore = *ign; + + source.Reply(" %-11s %-11s %-11s %s", ignore.mask.c_str(), ignore.creator.c_str(), ignore.reason.c_str(), do_strftime(ignore.time).c_str()); + } + } + + return; + } + + void DoDel(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + service_reference<IgnoreService> ignore_service("ignore"); + if (!ignore_service) + return; + + const Anope::string nick = params.size() > 1 ? params[1] : ""; + if (nick.empty()) + this->OnSyntaxError(source, "DEL"); + else if (ignore_service->DelIgnore(nick)) + source.Reply(_("\002%s\002 will no longer be ignored."), nick.c_str()); + else + source.Reply(_("Nick \002%s\002 not found on ignore list."), nick.c_str()); + + return; + } + + void DoClear(CommandSource &source) + { + service_reference<IgnoreService> ignore_service("ignore"); + if (!ignore_service) + return; + + ignore_service->ClearIgnores(); + source.Reply(_("Ignore list has been cleared.")); + + return; + } + + public: + CommandOSIgnore(Module *creator) : Command(creator, "operserv/ignore", 1, 4) + { + this->SetDesc(_("Modify the Services ignore list")); + this->SetSyntax(_("ADD \037time\037 \037nick\037 \037reason\037")); + this->SetSyntax(_("DEL \037nick\037")); + this->SetSyntax(_("LIST")); + this->SetSyntax(_("CLEAR")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &cmd = params[0]; + + if (cmd.equals_ci("ADD")) + return this->DoAdd(source, params); + else if (cmd.equals_ci("LIST")) + return this->DoList(source); + else if (cmd.equals_ci("DEL")) + return this->DoDel(source, params); + else if (cmd.equals_ci("CLEAR")) + return this->DoClear(source); + else + this->OnSyntaxError(source, ""); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Allows Services Operators to make Services ignore a nick or mask\n" + "for a certain time or until the next restart. The default\n" + "time format is seconds. You can specify it by using units.\n" + "Valid units are: \037s\037 for seconds, \037m\037 for minutes, \n" + "\037h\037 for hours and \037d\037 for days. \n" + "Combinations of these units are not permitted.\n" + "To make Services permanently ignore the user, type 0 as time.\n" + "When adding a \037mask\037, it should be in the format user@host\n" + "or nick!user@host, everything else will be considered a nick.\n" + "Wildcards are permitted.\n" + " \n" + "Ignores will not be enforced on IRC Operators.")); + return true; + } +}; + +class OSIgnore : public Module +{ + OSIgnoreService osignoreservice; + CommandOSIgnore commandosignore; + + public: + OSIgnore(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + osignoreservice(this, "ignore"), commandosignore(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandosignore); + + Implementation i[] = { I_OnDatabaseRead, I_OnDatabaseWrite, I_OnBotPrivmsg }; + ModuleManager::Attach(i, this, sizeof(i) / sizeof(Implementation)); + + ModuleManager::RegisterService(&this->osignoreservice); + } + + EventReturn OnDatabaseRead(const std::vector<Anope::string> ¶ms) + { + if (params.size() >= 4 && params[0].equals_ci("OS") && params[1].equals_ci("IGNORE")) + { + service_reference<IgnoreService> ignore_service("ignore"); + if (ignore_service) + { + const Anope::string &mask = params[2]; + time_t time = params[3].is_pos_number_only() ? convertTo<time_t>(params[3]) : 0; + const Anope::string &creator = params.size() > 4 ? params[4] : ""; + const Anope::string &reason = params.size() > 5 ? params[5] : ""; + ignore_service->AddIgnore(mask, creator, reason, time - Anope::CurTime); + + return EVENT_STOP; + } + } + + return EVENT_CONTINUE; + } + + void OnDatabaseWrite(void (*Write)(const Anope::string &)) + { + for (std::list<IgnoreData>::iterator ign = this->osignoreservice.GetIgnores().begin(), ign_end = this->osignoreservice.GetIgnores().end(); ign != ign_end; ) + { + if (ign->time && ign->time <= Anope::CurTime) + { + Log(LOG_DEBUG) << "[os_ignore] Expiring ignore entry " << ign->mask; + ign = this->osignoreservice.GetIgnores().erase(ign); + } + else + { + std::stringstream buf; + buf << "OS IGNORE " << ign->mask << " " << ign->time << " " << ign->creator << " :" << ign->reason; + Write(buf.str()); + ++ign; + } + } + } + + EventReturn OnBotPrivmsg(User *u, BotInfo *bi, Anope::string &message) + { + if (this->osignoreservice.Find(u->nick)) + return EVENT_STOP; + + return EVENT_CONTINUE; + } +}; + +MODULE_INIT(OSIgnore) diff --git a/modules/commands/os_ignore.h b/modules/commands/os_ignore.h new file mode 100644 index 000000000..056161a79 --- /dev/null +++ b/modules/commands/os_ignore.h @@ -0,0 +1,39 @@ +/* OperServ ignore interface + * + * (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. + */ + + +struct IgnoreData +{ + Anope::string mask; + Anope::string creator; + Anope::string reason; + time_t time; /* When do we stop ignoring them? */ +}; + +class IgnoreService : public Service +{ + protected: + std::list<IgnoreData> ignores; + + IgnoreService(Module *c, const Anope::string &n) : Service(c, n) { } + + public: + virtual void AddIgnore(const Anope::string &mask, const Anope::string &creator, const Anope::string &reason, time_t delta = Anope::CurTime) = 0; + + virtual bool DelIgnore(const Anope::string &mask) = 0; + + inline void ClearIgnores() { this->ignores.clear(); } + + virtual IgnoreData *Find(const Anope::string &mask) = 0; + + inline std::list<IgnoreData> &GetIgnores() { return this->ignores; } +}; + diff --git a/modules/commands/os_jupe.cpp b/modules/commands/os_jupe.cpp new file mode 100644 index 000000000..a689b33e2 --- /dev/null +++ b/modules/commands/os_jupe.cpp @@ -0,0 +1,79 @@ +/* OperServ core 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 "module.h" + +class CommandOSJupe : public Command +{ + public: + CommandOSJupe(Module *creator) : Command(creator, "operserv/jupe", 1, 2) + { + this->SetDesc(_("\"Jupiter\" a server")); + this->SetSyntax(_("\037server\037 [\037reason\037]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + const Anope::string &jserver = params[0]; + const Anope::string &reason = params.size() > 1 ? params[1] : ""; + Server *server = Server::Find(jserver); + + if (!isValidHost(jserver, 3)) + source.Reply(_("Please use a valid server name when juping")); + else if (server && (server == Me || server == Me->GetLinks().front())) + source.Reply(_("You can not jupe your services server or your uplink server.")); + else + { + Anope::string rbuf = "Juped by " + u->nick + (!reason.empty() ? ": " + reason : ""); + if (server) + ircdproto->SendSquit(jserver, rbuf); + Server *juped_server = new Server(Me, jserver, 1, rbuf, ircd->ts6 ? ts6_sid_retrieve() : "", SERVER_JUPED); + ircdproto->SendServer(juped_server); + + Log(LOG_ADMIN, u, this) << "on " << jserver << " (" << rbuf << ")"; + } + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Tells Services to jupiter a server -- that is, to create\n" + "a fake \"server\" connected to Services which prevents\n" + "the real server of that name from connecting. The jupe\n" + "may be removed using a standard \002SQUIT\002. If a reason is\n" + "given, it is placed in the server information field;\n" + "otherwise, the server information field will contain the\n" + "text \"Juped by <nick>\", showing the nickname of the\n" + "person who jupitered the server.")); + return true; + } +}; + +class OSJupe : public Module +{ + CommandOSJupe commandosjupe; + + public: + OSJupe(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandosjupe(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandosjupe); + } +}; + +MODULE_INIT(OSJupe) diff --git a/modules/commands/os_kick.cpp b/modules/commands/os_kick.cpp new file mode 100644 index 000000000..341ea1ee6 --- /dev/null +++ b/modules/commands/os_kick.cpp @@ -0,0 +1,83 @@ +/* OperServ core 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 "module.h" + +class CommandOSKick : public Command +{ + public: + CommandOSKick(Module *creator) : Command(creator, "operserv/kick", 3, 3) + { + this->SetDesc(_("Kick a user from a channel")); + this->SetSyntax(_("\037channel\037 \037user\037 \037reason\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + const Anope::string &chan = params[0]; + const Anope::string &nick = params[1]; + const Anope::string &s = params[2]; + Channel *c; + User *u2; + + if (!(c = findchan(chan))) + { + source.Reply(CHAN_X_NOT_IN_USE, chan.c_str()); + return; + } + else if (c->bouncy_modes) + { + source.Reply(_("Services is unable to change modes. Are your servers' U:lines configured correctly?")); + return; + } + else if (!(u2 = finduser(nick))) + { + source.Reply(NICK_X_NOT_IN_USE, nick.c_str()); + return; + } + + c->Kick(source.owner, u2, "%s (%s)", u->nick.c_str(), s.c_str()); + Log(LOG_ADMIN, u, this) << "on " << u2->nick << " in " << c->name; + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Allows staff to kick a user from any channel.\n" + "Parameters are the same as for the standard /KICK\n" + "command. The kick message will have the nickname of the\n" + "IRCop sending the KICK command prepended; for example:\n" + " \n" + "*** SpamMan has been kicked off channel #my_channel by %s (Alcan (Flood))"), source.owner->nick.c_str()); + return true; + } +}; + +class OSKick : public Module +{ + CommandOSKick commandoskick; + + public: + OSKick(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandoskick(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandoskick); + } +}; + +MODULE_INIT(OSKick) diff --git a/modules/commands/os_kill.cpp b/modules/commands/os_kill.cpp new file mode 100644 index 000000000..11a0636ef --- /dev/null +++ b/modules/commands/os_kill.cpp @@ -0,0 +1,72 @@ +/* OperServ core 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 "module.h" + +class CommandOSKill : public Command +{ + public: + CommandOSKill(Module *creator) : Command(creator, "operserv/kill", 1, 2) + { + this->SetDesc(_("Kill a user")); + this->SetSyntax(_("\037user\037 [\037reason\037]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + const Anope::string &nick = params[0]; + Anope::string reason = params.size() > 1 ? params[1] : ""; + + User *u2 = finduser(nick); + if (u2 == NULL) + source.Reply(NICK_X_NOT_IN_USE, nick.c_str()); + else if (u2->IsProtected() || u2->server == Me) + source.Reply(ACCESS_DENIED); + else + { + if (reason.empty()) + reason = "No reason specified"; + if (Config->AddAkiller) + reason = "(" + u->nick + ") " + reason; + Log(LOG_ADMIN, u, this) << "on " << u2->nick << " for " << reason; + u2->Kill(Config->OperServ, reason); + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Allows you to kill a user from the network.\n" + "Parameters are the same as for the standard /KILL\n" + "command.")); + return true; + } +}; + +class OSKill : public Module +{ + CommandOSKill commandoskill; + + public: + OSKill(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandoskill(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandoskill); + } +}; + +MODULE_INIT(OSKill) diff --git a/modules/commands/os_list.cpp b/modules/commands/os_list.cpp new file mode 100644 index 000000000..9a582f97b --- /dev/null +++ b/modules/commands/os_list.cpp @@ -0,0 +1,187 @@ +/* OperServ core 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 "module.h" + +class CommandOSChanList : public Command +{ + public: + CommandOSChanList(Module *creator) : Command(creator, "operserv/chanlist", 0, 2) + { + this->SetDesc(_("Lists all channel records")); + this->SetSyntax(_("[{\037pattern\037 | \037nick\037} [\037SECRET\037]]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &pattern = !params.empty() ? params[0] : ""; + const Anope::string &opt = params.size() > 1 ? params[1] : ""; + std::list<ChannelModeName> Modes; + User *u2; + + if (!opt.empty() && opt.equals_ci("SECRET")) + { + Modes.push_back(CMODE_SECRET); + Modes.push_back(CMODE_PRIVATE); + } + + if (!pattern.empty() && (u2 = finduser(pattern))) + { + source.Reply(_("\002%s\002 channel list:\n" + "Name Users Modes Topic"), u2->nick.c_str()); + + for (UChannelList::iterator uit = u2->chans.begin(), uit_end = u2->chans.end(); uit != uit_end; ++uit) + { + ChannelContainer *cc = *uit; + + if (!Modes.empty()) + for (std::list<ChannelModeName>::iterator it = Modes.begin(), it_end = Modes.end(); it != it_end; ++it) + if (!cc->chan->HasMode(*it)) + continue; + + source.Reply(_("%-20s %4d +%-6s %s"), cc->chan->name.c_str(), cc->chan->users.size(), cc->chan->GetModes(true, true).c_str(), !cc->chan->topic.empty() ? cc->chan->topic.c_str() : ""); + } + } + else + { + source.Reply(_("Channel list:\n" + "Name Users Modes Topic")); + + for (channel_map::const_iterator cit = ChannelList.begin(), cit_end = ChannelList.end(); cit != cit_end; ++cit) + { + Channel *c = cit->second; + + if (!pattern.empty() && !Anope::Match(c->name, pattern)) + continue; + if (!Modes.empty()) + for (std::list<ChannelModeName>::iterator it = Modes.begin(), it_end = Modes.end(); it != it_end; ++it) + if (!c->HasMode(*it)) + continue; + + source.Reply(_("%-20s %4d +%-6s %s"), c->name.c_str(), c->users.size(), c->GetModes(true, true).c_str(), !c->topic.empty() ? c->topic.c_str() : ""); + } + } + + source.Reply(_("End of channel list.")); + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Lists all channels currently in use on the IRC network, whether they\n" + "are registered or not.\n" + "If \002pattern\002 is given, lists only channels that match it. If a nickname\n" + "is given, lists only the channels the user using it is on. If SECRET is\n" + "specified, lists only channels matching \002pattern\002 that have the +s or\n" + "+p mode.")); + return true; + } +}; + +class CommandOSUserList : public Command +{ + public: + CommandOSUserList(Module *creator) : Command(creator, "operserv/userlist", 0, 2) + { + this->SetDesc(_("Lists all user records")); + this->SetSyntax(_("[{\037pattern\037 | \037channel\037} [\037INVISIBLE\037]]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &pattern = !params.empty() ? params[0] : ""; + const Anope::string &opt = params.size() > 1 ? params[1] : ""; + Channel *c; + std::list<UserModeName> Modes; + + if (!opt.empty() && opt.equals_ci("INVISIBLE")) + Modes.push_back(UMODE_INVIS); + + if (!pattern.empty() && (c = findchan(pattern))) + { + source.Reply(_("\002%s\002 users list:\n" + "Nick Mask"), pattern.c_str()); + + for (CUserList::iterator cuit = c->users.begin(), cuit_end = c->users.end(); cuit != cuit_end; ++cuit) + { + UserContainer *uc = *cuit; + + if (!Modes.empty()) + for (std::list<UserModeName>::iterator it = Modes.begin(), it_end = Modes.end(); it != it_end; ++it) + if (!uc->user->HasMode(*it)) + continue; + + source.Reply(_("%-20s %s@%s"), uc->user->nick.c_str(), uc->user->GetIdent().c_str(), uc->user->GetDisplayedHost().c_str()); + } + } + else + { + source.Reply(_("Users list:\n" + "Nick Mask")); + + for (Anope::insensitive_map<User *>::iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it) + { + User *u2 = it->second; + + if (!pattern.empty()) + { + Anope::string mask = u2->nick + "!" + u2->GetIdent() + "@" + u2->GetDisplayedHost(); + if (!Anope::Match(mask, pattern)) + continue; + if (!Modes.empty()) + for (std::list<UserModeName>::iterator mit = Modes.begin(), mit_end = Modes.end(); mit != mit_end; ++mit) + if (!u2->HasMode(*mit)) + continue; + } + source.Reply(_("%-20s %s@%s"), u2->nick.c_str(), u2->GetIdent().c_str(), u2->GetDisplayedHost().c_str()); + } + } + + source.Reply(_("End of users list.")); + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Lists all users currently online on the IRC network, whether their\n" + "nick is registered or not.\n" + " \n" + "If \002pattern\002 is given, lists only users that match it (it must be in\n" + "the format nick!user@host). If \002channel\002 is given, lists only users\n" + "that are on the given channel. If INVISIBLE is specified, only users\n" + "with the +i flag will be listed.")); + return true; + } +}; + +class OSList : public Module +{ + CommandOSChanList commandoschanlist; + CommandOSUserList commandosuserlist; + + public: + OSList(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandoschanlist(this), commandosuserlist(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandoschanlist); + ModuleManager::RegisterService(&commandosuserlist); + } +}; + +MODULE_INIT(OSList) diff --git a/modules/commands/os_login.cpp b/modules/commands/os_login.cpp new file mode 100644 index 000000000..94a82e334 --- /dev/null +++ b/modules/commands/os_login.cpp @@ -0,0 +1,97 @@ +/* OperServ core 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 "module.h" + +class CommandOSLogin : public Command +{ + public: + CommandOSLogin(Module *creator) : Command(creator, "operserv/login", 1, 1) + { + this->SetDesc(Anope::printf(_("Login to %s"), Config->OperServ.c_str())); + this->SetSyntax(_("\037password\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &password = params[0]; + + Oper *o = source.u->Account()->o; + if (o == NULL) + source.Reply(_("No oper block for your nick.")); + else if (o->password.empty()) + source.Reply(_("Your oper block doesn't require logging in.")); + else if (source.u->GetExt("os_login_password_correct")) + source.Reply(_("You are already identified.")); + else if (o->password != password) + { + source.Reply(PASSWORD_INCORRECT); + bad_password(source.u); + } + else + { + Log(LOG_ADMIN, source.u, this) << "and successfully identified to " << source.owner->nick; + source.u->Extend("os_login_password_correct"); + source.Reply(_("Password accepted.")); + } + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Logs you in to %s so you gain Services Operator privileges.\n" + "This command may be unnecessary if your oper block is\n" + "configured without a password."), source.owner->nick.c_str()); + return true; + } +}; + +class OSLogin : public Module +{ + CommandOSLogin commandoslogin; + + public: + OSLogin(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandoslogin(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandoslogin); + + ModuleManager::Attach(I_IsServicesOper, this); + } + + ~OSLogin() + { + for (Anope::insensitive_map<User *>::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it) + it->second->Shrink("os_login_password_correct"); + } + + EventReturn IsServicesOper(User *u) + { + if (!u->Account()->o->password.empty()) + { + if (u->GetExt("os_login_password_correct")) + return EVENT_ALLOW; + return EVENT_STOP; + } + + return EVENT_CONTINUE; + } +}; + +MODULE_INIT(OSLogin) diff --git a/modules/commands/os_mode.cpp b/modules/commands/os_mode.cpp new file mode 100644 index 000000000..b3e62ca25 --- /dev/null +++ b/modules/commands/os_mode.cpp @@ -0,0 +1,109 @@ +/* OperServ core 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 "module.h" + +class CommandOSMode : public Command +{ + public: + CommandOSMode(Module *creator) : Command(creator, "operserv/mode", 2, 2) + { + this->SetDesc(_("Change channel modes")); + this->SetSyntax(_("\037channel\037 \037modes\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + const Anope::string &target = params[0]; + const Anope::string &modes = params[1]; + + Channel *c = findchan(target); + if (!c) + source.Reply(CHAN_X_NOT_IN_USE, target.c_str()); + else if (c->bouncy_modes) + source.Reply(_("Services is unable to change modes. Are your servers' U:lines configured correctly?")); + else + { + c->SetModes(source.owner, false, modes.c_str()); + + Log(LOG_ADMIN, u, this) << modes << " on " << target; + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Allows Services operators to change modes for any channel.\n" + "Parameters are the same as for the standard /MODE command.")); + return true; + } +}; + +class CommandOSUMode : public Command +{ + public: + CommandOSUMode(Module *creator) : Command(creator, "operserv/umode", 2, 2) + { + this->SetDesc(_("Change channel or user modes")); + this->SetSyntax(_("\037user\037 \037modes\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + const Anope::string &target = params[0]; + const Anope::string &modes = params[1]; + + User *u2 = finduser(target); + if (!u2) + source.Reply(NICK_X_NOT_IN_USE, target.c_str()); + else + { + u2->SetModes(source.owner, "%s", modes.c_str()); + source.Reply(_("Changed usermodes of \002%s\002 to %s."), u2->nick.c_str(), modes.c_str()); + + u2->SendMessage(source.owner, _("\002%s\002 changed your usermodes to %s."), u->nick.c_str(), modes.c_str()); + + Log(LOG_ADMIN, u, this) << modes << " on " << target; + } + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Allows Services operators to change modes for any user.\n" + "Parameters are the same as for the standard /MODE command.")); + return true; + } +}; + +class OSMode : public Module +{ + CommandOSMode commandosmode; + CommandOSUMode commandosumode; + + public: + OSMode(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandosmode(this), commandosumode(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandosmode); + ModuleManager::RegisterService(&commandosumode); + } +}; + +MODULE_INIT(OSMode) diff --git a/modules/commands/os_modinfo.cpp b/modules/commands/os_modinfo.cpp new file mode 100644 index 000000000..38d9cf905 --- /dev/null +++ b/modules/commands/os_modinfo.cpp @@ -0,0 +1,245 @@ +/* OperServ core 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 "module.h" + +class CommandOSModInfo : public Command +{ + public: + CommandOSModInfo(Module *creator) : Command(creator, "operserv/modinfo", 1, 1) + { + this->SetDesc(_("Info about a loaded module")); + this->SetSyntax(_("\037modname\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &file = params[0]; + + Module *m = ModuleManager::FindModule(file); + if (m) + { + source.Reply(_("Module: \002%s\002 Version: \002%s\002 Author: \002%s\002 loaded: \002%s\002"), m->name.c_str(), !m->version.empty() ? m->version.c_str() : "?", !m->author.empty() ? m->author.c_str() : "?", do_strftime(m->created).c_str()); + + std::vector<Anope::string> services = ModuleManager::GetServiceKeys(); + for (unsigned i = 0; i < services.size(); ++i) + { + Service *s = ModuleManager::GetService(services[i]); + if (!s || s->owner != m) + continue; + source.Reply(_(" Providing service: \002%s\002"), s->name.c_str()); + + for (botinfo_map::const_iterator it = BotListByNick.begin(), it_end = BotListByNick.end(); it != it_end; ++it) + { + BotInfo *bi = it->second; + + for (BotInfo::command_map::const_iterator cit = bi->commands.begin(), cit_end = bi->commands.end(); cit != cit_end; ++cit) + { + const Anope::string &c_name = cit->first; + const CommandInfo &info = cit->second; + if (info.name != s->name) + continue; + source.Reply(_(" Command \002%s\002 on \002%s\002 is linked to \002%s\002"), c_name.c_str(), bi->nick.c_str(), s->name.c_str()); + } + } + } + } + else + source.Reply(_("No information about module \002%s\002 is available"), file.c_str()); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("This command lists information about the specified loaded module")); + return true; + } +}; + +class CommandOSModList : public Command +{ + public: + CommandOSModList(Module *creator) : Command(creator, "operserv/modlist", 0, 1) + { + this->SetDesc(_("List loaded modules")); + this->SetSyntax(_("[Core|3rd|protocol|encryption|supported]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string ¶m = !params.empty() ? params[0] : ""; + + int count = 0; + int showCore = 0; + int showThird = 1; + int showProto = 1; + int showEnc = 1; + int showSupported = 1; + int showDB = 1; + + char core[] = "Core"; + char third[] = "3rd"; + char proto[] = "Protocol"; + char enc[] = "Encryption"; + char supported[] = "Supported"; + char db[] = "Database"; + + if (!param.empty()) + { + if (param.equals_ci(core)) + { + showCore = 1; + showThird = 0; + showProto = 0; + showEnc = 0; + showSupported = 0; + showDB = 0; + } + else if (param.equals_ci(third)) + { + showCore = 0; + showThird = 1; + showSupported = 0; + showProto = 0; + showEnc = 0; + showDB = 0; + } + else if (param.equals_ci(proto)) + { + showCore = 0; + showThird = 0; + showProto = 1; + showEnc = 0; + showSupported = 0; + showDB = 0; + } + else if (param.equals_ci(supported)) + { + showCore = 0; + showThird = 0; + showProto = 0; + showSupported = 1; + showEnc = 0; + showDB = 0; + } + else if (param.equals_ci(enc)) + { + showCore = 0; + showThird = 0; + showProto = 0; + showSupported = 0; + showEnc = 1; + showDB = 0; + } + else if (param.equals_ci(db)) + { + showCore = 0; + showThird = 0; + showProto = 0; + showSupported = 0; + showEnc = 0; + showDB = 1; + } + } + + source.Reply(_("Current Module list:")); + + for (std::list<Module *>::iterator it = Modules.begin(), it_end = Modules.end(); it != it_end; ++it) + { + Module *m = *it; + + switch (m->type) + { + case CORE: + if (showCore) + { + source.Reply(_("Module: \002%s\002 [%s] [%s]"), m->name.c_str(), m->version.c_str(), core); + ++count; + } + break; + case THIRD: + if (showThird) + { + source.Reply(_("Module: \002%s\002 [%s] [%s]"), m->name.c_str(), m->version.c_str(), third); + ++count; + } + break; + case PROTOCOL: + if (showProto) + { + source.Reply(_("Module: \002%s\002 [%s] [%s]"), m->name.c_str(), m->version.c_str(), proto); + ++count; + } + break; + case SUPPORTED: + if (showSupported) + { + source.Reply(_("Module: \002%s\002 [%s] [%s]"), m->name.c_str(), m->version.c_str(), supported); + ++count; + } + break; + case ENCRYPTION: + if (showEnc) + { + source.Reply(_("Module: \002%s\002 [%s] [%s]"), m->name.c_str(), m->version.c_str(), enc); + ++count; + } + break; + case DATABASE: + if (showDB) + { + source.Reply(_("Module: \002%s\002 [%s] [%s]"), m->name.c_str(), m->version.c_str(), db); + ++count; + } + break; + default: + break; + } + } + if (!count) + source.Reply(_("No modules currently loaded")); + else + source.Reply(_("%d Modules loaded."), count); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Lists all currently loaded modules.")); + return true; + } +}; + +class OSModInfo : public Module +{ + CommandOSModInfo commandosmodinfo; + CommandOSModList commandosmodlist; + + public: + OSModInfo(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandosmodinfo(this), commandosmodlist(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandosmodinfo); + ModuleManager::RegisterService(&commandosmodlist); + } +}; + +MODULE_INIT(OSModInfo) diff --git a/modules/commands/os_module.cpp b/modules/commands/os_module.cpp new file mode 100644 index 000000000..562e130aa --- /dev/null +++ b/modules/commands/os_module.cpp @@ -0,0 +1,202 @@ +/* OperServ core 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 "module.h" + +class CommandOSModLoad : public Command +{ + public: + CommandOSModLoad(Module *creator) : Command(creator, "operserv/modload", 1, 1) + { + this->SetDesc(_("Load a module")); + this->SetSyntax(_("\037modname\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + const Anope::string &mname = params[0]; + + ModuleReturn status = ModuleManager::LoadModule(mname, u); + if (status == MOD_ERR_OK) + { + ircdproto->SendGlobops(source.owner, "%s loaded module %s", u->nick.c_str(), mname.c_str()); + source.Reply(_("Module \002%s\002 loaded"), mname.c_str()); + + /* If a user is loading this module, then the core databases have already been loaded + * so trigger the event manually + */ + Module *m = ModuleManager::FindModule(mname); + if (m) + m->OnPostLoadDatabases(); + } + else if (status == MOD_ERR_EXISTS) + source.Reply(_("Module \002%s\002 is already loaded."), mname.c_str()); + else + source.Reply(_("Unable to load module \002%s\002"), mname.c_str()); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("This command loads the module named FileName from the modules\n" + "directory.")); + return true; + } +}; + +class CommandOSModReLoad : public Command +{ + public: + CommandOSModReLoad(Module *creator) : Command(creator, "operserv/modreload", 1, 1) + { + this->SetDesc(_("Reload a module")); + this->SetSyntax(_("\037modname\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + const Anope::string &mname = params[0]; + + Module *m = ModuleManager::FindModule(mname); + if (!m) + { + source.Reply(_("Module \002%s\002 isn't loaded."), mname.c_str()); + return; + } + + if (!m->handle || m->GetPermanent()) + { + source.Reply(_("Unable to remove module \002%s\002"), m->name.c_str()); + return; + } + + /* Unrecoverable */ + bool fatal = m->type == PROTOCOL; + ModuleReturn status = ModuleManager::UnloadModule(m, u); + + if (status != MOD_ERR_OK) + { + source.Reply(_("Unable to remove module \002%s\002"), mname.c_str()); + return; + } + + status = ModuleManager::LoadModule(mname, u); + if (status == MOD_ERR_OK) + { + ircdproto->SendGlobops(source.owner, "%s reloaded module %s", u->nick.c_str(), mname.c_str()); + source.Reply(_("Module \002%s\002 reloaded"), mname.c_str()); + + /* If a user is loading this module, then the core databases have already been loaded + * so trigger the event manually + */ + m = ModuleManager::FindModule(mname); + if (m) + m->OnPostLoadDatabases(); + } + else + { + if (fatal) + throw FatalException("Unable to reload module " + mname); + else + source.Reply(_("Unable to load module \002%s\002"), mname.c_str()); + } + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("This command reloads the module named FileName.")); + return true; + } +}; + +class CommandOSModUnLoad : public Command +{ + public: + CommandOSModUnLoad(Module *creator) : Command(creator, "operserv/modunload", 1, 1) + { + this->SetDesc(_("Un-Load a module")); + this->SetSyntax(_("\037modname\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + const Anope::string &mname = params[0]; + + Module *m = ModuleManager::FindModule(mname); + if (!m) + { + source.Reply(_("Module \002%s\002 isn't loaded."), mname.c_str()); + return; + } + + if (!m->handle || m->GetPermanent() || m->type == PROTOCOL) + { + source.Reply(_("Unable to remove module \002%s\002"), m->name.c_str()); + return; + } + + Log() << "Trying to unload module [" << mname << "]"; + + ModuleReturn status = ModuleManager::UnloadModule(m, u); + + if (status == MOD_ERR_OK) + { + source.Reply(_("Module \002%s\002 unloaded"), mname.c_str()); + ircdproto->SendGlobops(source.owner, "%s unloaded module %s", u->nick.c_str(), mname.c_str()); + } + else + source.Reply(_("Unable to remove module \002%s\002"), mname.c_str()); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("This command unloads the module named FileName from the modules\n" + "directory.")); + return true; + } +}; + +class OSModule : public Module +{ + CommandOSModLoad commandosmodload; + CommandOSModReLoad commandosmodreload; + CommandOSModUnLoad commandosmodunload; + + public: + OSModule(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandosmodload(this), commandosmodreload(this), commandosmodunload(this) + { + this->SetAuthor("Anope"); + this->SetPermanent(true); + + ModuleManager::RegisterService(&commandosmodload); + ModuleManager::RegisterService(&commandosmodreload); + ModuleManager::RegisterService(&commandosmodunload); + } +}; + +MODULE_INIT(OSModule) diff --git a/modules/commands/os_news.cpp b/modules/commands/os_news.cpp new file mode 100644 index 000000000..9c9aae0a4 --- /dev/null +++ b/modules/commands/os_news.cpp @@ -0,0 +1,450 @@ +/* OperServ core functions + * + * (C) 2003-2011 Anope Team + * Contact us at info@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 "module.h" +#include "global.h" +#include "os_news.h" + +/* List of messages for each news type. This simplifies message sending. */ + +enum +{ + MSG_SYNTAX, + MSG_LIST_HEADER, + MSG_LIST_NONE, + MSG_ADDED, + MSG_DEL_NOT_FOUND, + MSG_DELETED, + MSG_DEL_NONE, + MSG_DELETED_ALL +}; + +struct NewsMessages msgarray[] = { + {NEWS_LOGON, "LOGON", + {_("LOGONNEWS {ADD|DEL|LIST} [\037text\037|\037num\037]\002"), + _("Logon news items:"), + _("There is no logon news."), + _("Added new logon news item."), + _("Logon news item #%s not found!"), + _("Logon news item #%d deleted."), + _("No logon news items to delete!"), + _("All logon news items deleted.")} + }, + {NEWS_OPER, "OPER", + {_("OPERNEWS {ADD|DEL|LIST} [\037text\037|\037num\037]\002"), + _("Oper news items:"), + _("There is no oper news."), + _("Added new oper news item."), + _("Oper news item #%s not found!"), + _("Oper news item #%d deleted."), + _("No oper news items to delete!"), + _("All oper news items deleted.")} + }, + {NEWS_RANDOM, "RANDOM", + {_("RANDOMNEWS {ADD|DEL|LIST} [\037text\037|\037num\037]\002"), + _("Random news items:"), + _("There is no random news."), + _("Added new random news item."), + _("Random news item #%s not found!"), + _("Random news item #%d deleted."), + _("No random news items to delete!"), + _("All random news items deleted.")} + } +}; + +class MyNewsService : public NewsService +{ + std::vector<NewsItem *> newsItems[3]; + public: + MyNewsService(Module *m) : NewsService(m) { } + + ~MyNewsService() + { + for (unsigned i = 0; i < 3; ++i) + for (unsigned j = 0; j < newsItems[i].size(); ++j) + delete newsItems[i][j]; + } + + void AddNewsItem(NewsItem *n) + { + this->newsItems[n->type].push_back(n); + } + + void DelNewsItem(NewsItem *n) + { + std::vector<NewsItem *> &list = this->GetNewsList(n->type); + std::vector<NewsItem *>::iterator it = std::find(list.begin(), list.end(), n); + if (it != list.end()) + list.erase(it); + delete n; + } + + std::vector<NewsItem *> &GetNewsList(NewsType t) + { + return this->newsItems[t]; + } +}; + +#define lenof(a) (sizeof(a) / sizeof(*(a))) +static const char **findmsgs(NewsType type) +{ + for (unsigned i = 0; i < lenof(msgarray); ++i) + if (msgarray[i].type == type) + return msgarray[i].msgs; + return NULL; +} + +class NewsBase : public Command +{ + service_reference<NewsService> ns; + + protected: + void DoList(CommandSource &source, NewsType type, const char **msgs) + { + std::vector<NewsItem *> &list = this->ns->GetNewsList(type); + if (list.empty()) + source.Reply(msgs[MSG_LIST_NONE]); + else + { + source.Reply(msgs[MSG_LIST_HEADER]); + for (unsigned i = 0, end = list.size(); i < end; ++i) + source.Reply(_("%5d (%s by %s)\n"" %s"), i + 1, do_strftime(list[i]->time).c_str(), !list[i]->who.empty() ? list[i]->who.c_str() : "<unknown>", list[i]->text.c_str()); + source.Reply(END_OF_ANY_LIST, "News"); + } + + return; + } + + void DoAdd(CommandSource &source, const std::vector<Anope::string> ¶ms, NewsType type, const char **msgs) + { + const Anope::string text = params.size() > 1 ? params[1] : ""; + + if (text.empty()) + this->OnSyntaxError(source, "ADD"); + else + { + if (readonly) + source.Reply(READ_ONLY_MODE); + + NewsItem *news = new NewsItem(); + news->type = type; + news->text = text; + news->time = Anope::CurTime; + news->who = source.u->nick; + + this->ns->AddNewsItem(news); + + source.Reply(msgs[MSG_ADDED]); + } + + return; + } + + void DoDel(CommandSource &source, const std::vector<Anope::string> ¶ms, NewsType type, const char **msgs) + { + const Anope::string &text = params.size() > 1 ? params[1] : ""; + + if (text.empty()) + this->OnSyntaxError(source, "DEL"); + else + { + std::vector<NewsItem *> &list = this->ns->GetNewsList(type); + if (list.empty()) + source.Reply(msgs[MSG_LIST_NONE]); + else + { + if (readonly) + source.Reply(READ_ONLY_MODE); + if (!text.equals_ci("ALL")) + { + try + { + unsigned num = convertTo<unsigned>(text); + if (num > 0 && num <= list.size()) + { + this->ns->DelNewsItem(list[num - 1]); + source.Reply(msgs[MSG_DELETED], num); + return; + } + } + catch (const ConvertException &) { } + + source.Reply(msgs[MSG_DEL_NOT_FOUND], text.c_str()); + } + else + { + for (unsigned i = list.size(); i > 0; --i) + this->ns->DelNewsItem(list[i - 1]); + source.Reply(msgs[MSG_DELETED_ALL]); + } + } + } + + return; + } + + void DoNews(CommandSource &source, const std::vector<Anope::string> ¶ms, NewsType type) + { + if (!this->ns) + return; + + const Anope::string &cmd = params[0]; + + const char **msgs = findmsgs(type); + if (!msgs) + throw CoreException("news: Invalid type to do_news()"); + + if (cmd.equals_ci("LIST")) + return this->DoList(source, type, msgs); + else if (cmd.equals_ci("ADD")) + return this->DoAdd(source, params, type, msgs); + else if (cmd.equals_ci("DEL")) + return this->DoDel(source, params, type, msgs); + else + this->OnSyntaxError(source, ""); + + return; + } + public: + NewsBase(Module *creator, const Anope::string &newstype) : Command(creator, newstype, 1, 2), ns("news") + { + this->SetSyntax(_("ADD \037text\037")); + this->SetSyntax(_("DEL {\037num\037 | ALL}")); + this->SetSyntax(_("LIST")); + } + + virtual ~NewsBase() + { + } + + virtual void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) = 0; + + virtual bool OnHelp(CommandSource &source, const Anope::string &subcommand) = 0; +}; + +class CommandOSLogonNews : public NewsBase +{ + public: + CommandOSLogonNews(Module *creator) : NewsBase(creator, "operserv/logonnews") + { + this->SetDesc(_("Define messages to be shown to users at logon")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + return this->DoNews(source, params, NEWS_LOGON); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Edits or displays the list of logon news messages. When a\n" + "user connects to the network, these messages will be sent\n" + "to them. (However, no more than \002%d\002 messages will be\n" + "sent in order to avoid flooding the user. If there are\n" + "more news messages, only the most recent will be sent.)\n" + "NewsCount can be configured in services.conf.\n" + " \n" + "LOGONNEWS may only be used by Services Operators."), Config->NewsCount); + return true; + } +}; + +class CommandOSOperNews : public NewsBase +{ + public: + CommandOSOperNews(Module *creator) : NewsBase(creator, "operserv/opernews") + { + this->SetDesc(_("Define messages to be shown to users who oper")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + return this->DoNews(source, params, NEWS_OPER); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Edits or displays the list of oper news messages. When a\n" + "user opers up (with the /OPER command), these messages will\n" + "be sent to them. (However, no more than \002%d\002 messages will\n" + "be sent in order to avoid flooding the user. If there are\n" + "more news messages, only the most recent will be sent.)\n" + "NewsCount can be configured in services.conf.\n" + " \n" + "OPERNEWS may only be used by Services Operators."), Config->NewsCount); + return true; + } +}; + +class CommandOSRandomNews : public NewsBase +{ + public: + CommandOSRandomNews(Module *creator) : NewsBase(creator, "operserv/randomnews") + { + this->SetDesc(_("Define messages to be randomly shown to users at logon")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + return this->DoNews(source, params, NEWS_RANDOM); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Edits or displays the list of random news messages. When a\n" + "user connects to the network, one (and only one) of the\n" + "random news will be randomly chosen and sent to them.\n" + " \n" + "RANDOMNEWS may only be used by Services Operators.")); + return true; + } +}; + +class OSNews : public Module +{ + MyNewsService newsservice; + + CommandOSLogonNews commandoslogonnews; + CommandOSOperNews commandosopernews; + CommandOSRandomNews commandosrandomnews; + + void DisplayNews(User *u, NewsType Type) + { + std::vector<NewsItem *> &newsList = this->newsservice.GetNewsList(Type); + if (newsList.empty()) + return; + + Anope::string msg; + if (Type == NEWS_LOGON) + msg = _("[\002Logon News\002 - %s] %s"); + else if (Type == NEWS_OPER) + msg = _("[\002Oper News\002 - %s] %s"); + else if (Type == NEWS_RANDOM) + msg = _("[\002Random News\002 - %s] %s"); + + static unsigned cur_rand_news = 0; + unsigned displayed = 0; + for (unsigned i = 0, end = newsList.size(); i < end; ++i) + { + if (Type == NEWS_RANDOM && i != cur_rand_news) + continue; + + BotInfo *gl = findbot(Config->Global); + if (!gl && !BotListByNick.empty()) + gl = BotListByNick.begin()->second; + BotInfo *os = findbot(Config->OperServ); + if (!os) + os = gl; + if (gl) + u->SendMessage(Type != NEWS_OPER ? gl : os, msg.c_str(), do_strftime(newsList[i]->time).c_str(), newsList[i]->text.c_str()); + + ++displayed; + + if (Type == NEWS_RANDOM) + { + ++cur_rand_news; + break; + } + else if (displayed >= Config->NewsCount) + break; + } + + /* Reset to head of list to get first random news value */ + if (Type == NEWS_RANDOM && cur_rand_news >= newsList.size()) + cur_rand_news = 0; + } + + public: + OSNews(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + newsservice(this), commandoslogonnews(this), commandosopernews(this), commandosrandomnews(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandoslogonnews); + ModuleManager::RegisterService(&commandosopernews); + ModuleManager::RegisterService(&commandosrandomnews); + + Implementation i[] = { I_OnUserModeSet, I_OnUserConnect, I_OnDatabaseRead, I_OnDatabaseWrite }; + ModuleManager::Attach(i, this, sizeof(i) / sizeof(Implementation)); + + ModuleManager::RegisterService(&this->newsservice); + } + + void OnUserModeSet(User *u, UserModeName Name) + { + if (Name == UMODE_OPER) + DisplayNews(u, NEWS_OPER); + } + + void OnUserConnect(dynamic_reference<User> &user, bool &) + { + if (!user || !user->server->IsSynced()) + return; + + DisplayNews(user, NEWS_LOGON); + DisplayNews(user, NEWS_RANDOM); + } + + EventReturn OnDatabaseRead(const std::vector<Anope::string> ¶ms) + { + if (params[0].equals_ci("OS") && params.size() >= 7 && params[1].equals_ci("NEWS")) + { + NewsItem *n = new NewsItem(); + // params[2] was news number + n->time = params[3].is_number_only() ? convertTo<time_t>(params[3]) : 0; + n->who = params[4]; + if (params[5].equals_ci("LOGON")) + n->type = NEWS_LOGON; + else if (params[5].equals_ci("RANDOM")) + n->type = NEWS_RANDOM; + else if (params[5].equals_ci("OPER")) + n->type = NEWS_OPER; + n->text = params[6]; + + this->newsservice.AddNewsItem(n); + + return EVENT_STOP; + } + + return EVENT_CONTINUE; + } + + void OnDatabaseWrite(void (*Write)(const Anope::string &)) + { + for (unsigned i = 0; i < 3; ++i) + { + std::vector<NewsItem *> &list = this->newsservice.GetNewsList(static_cast<NewsType>(i)); + for (std::vector<NewsItem *>::iterator it = list.begin(); it != list.end(); ++it) + { + NewsItem *n = *it; + Anope::string ntype; + if (n->type == NEWS_LOGON) + ntype = "LOGON"; + else if (n->type == NEWS_RANDOM) + ntype = "RANDOM"; + else if (n->type == NEWS_OPER) + ntype = "OPER"; + Anope::string buf = "OS NEWS 0 " + stringify(n->time) + " " + n->who + " " + ntype + " :" + n->text; + Write(buf); + } + } + } +}; + +MODULE_INIT(OSNews) diff --git a/modules/commands/os_news.h b/modules/commands/os_news.h new file mode 100644 index 000000000..c55f56d42 --- /dev/null +++ b/modules/commands/os_news.h @@ -0,0 +1,39 @@ +#ifndef OS_NEWS +#define OS_NEWS + +enum NewsType +{ + NEWS_LOGON, + NEWS_RANDOM, + NEWS_OPER +}; + +struct NewsMessages +{ + NewsType type; + Anope::string name; + const char *msgs[10]; +}; + +struct NewsItem +{ + NewsType type; + Anope::string text; + Anope::string who; + time_t time; +}; + +class NewsService : public Service +{ + public: + NewsService(Module *m) : Service(m, "news") { } + + virtual void AddNewsItem(NewsItem *n) = 0; + + virtual void DelNewsItem(NewsItem *n) = 0; + + virtual std::vector<NewsItem *> &GetNewsList(NewsType t) = 0; +}; + +#endif // OS_NEWS + diff --git a/modules/commands/os_noop.cpp b/modules/commands/os_noop.cpp new file mode 100644 index 000000000..2eec522a3 --- /dev/null +++ b/modules/commands/os_noop.cpp @@ -0,0 +1,94 @@ +/* OperServ core 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 "module.h" + +class CommandOSNOOP : public Command +{ + public: + CommandOSNOOP(Module *creator) : Command(creator, "operserv/noop", 2, 2) + { + this->SetDesc(_("Temporarily remove all O:lines of a server remotely")); + this->SetSyntax(_("SET \037server\037")); + this->SetSyntax(_("REVOKE \037server\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + const Anope::string &cmd = params[0]; + const Anope::string &server = params[1]; + + Server *s = Server::Find(server); + if (s == NULL) + source.Reply(_("Server %s does not exist."), server.c_str()); + else if (cmd.equals_ci("SET")) + { + /* Remove the O:lines */ + ircdproto->SendSVSNOOP(s, true); + + Log(LOG_ADMIN, u, this) << "SET on " << s->GetName(); + source.Reply(_("All O:lines of \002%s\002 have been removed."), s->GetName().c_str()); + + Anope::string reason = "NOOP command used by " + u->nick; + /* Kill all the IRCops of the server */ + for (Anope::insensitive_map<User *>::iterator it = UserListByNick.begin(); it != UserListByNick.end();) + { + User *u2 = it->second; + ++it; + + if (u2 && u2->HasMode(UMODE_OPER) && u2->server == s) + u2->Kill(Config->OperServ, reason); + } + } + else if (cmd.equals_ci("REVOKE")) + { + Log(LOG_ADMIN, u, this) << "REVOKE on " << s->GetName(); + ircdproto->SendSVSNOOP(s, false); + source.Reply(_("All O:lines of \002%s\002 have been reset."), s->GetName().c_str()); + } + else + this->OnSyntaxError(source, ""); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("\002NOOP SET\002 remove all O:lines of the given\n" + "\002server\002 and kill all IRCops currently on it to\n" + "prevent them from rehashing the server (because this\n" + "would just cancel the effect).\n" + "\002NOOP REVOKE\002 makes all removed O:lines available again\n" + "on the given \002server\002.\n")); + return true; + } +}; + +class OSNOOP : public Module +{ + CommandOSNOOP commandosnoop; + + public: + OSNOOP(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandosnoop(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandosnoop); + } +}; + +MODULE_INIT(OSNOOP) diff --git a/modules/commands/os_oline.cpp b/modules/commands/os_oline.cpp new file mode 100644 index 000000000..adebc52a1 --- /dev/null +++ b/modules/commands/os_oline.cpp @@ -0,0 +1,83 @@ +/* OperServ core 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 "module.h" + +class CommandOSOLine : public Command +{ + public: + CommandOSOLine(Module *creator) : Command(creator, "operserv/oline", 2, 2) + { + this->SetDesc(_("Give Operflags to a certain user")); + this->SetSyntax(_("\037nick\037 \037flags\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + const Anope::string &nick = params[0]; + const Anope::string &flag = params[1]; + User *u2 = NULL; + + /* let's check whether the user is online */ + if (!(u2 = finduser(nick))) + source.Reply(NICK_X_NOT_IN_USE, nick.c_str()); + else if (u2 && flag[0] == '+') + { + ircdproto->SendSVSO(Config->OperServ, nick, flag); + u2->SetMode(source.owner, UMODE_OPER); + u2->SendMessage(source.owner, _("You are now an IRC Operator.")); + source.Reply(_("Operflags \002%s\002 have been added for \002%s\002."), flag.c_str(), nick.c_str()); + ircdproto->SendGlobops(source.owner, "\2%s\2 used OLINE for %s", u->nick.c_str(), nick.c_str()); + } + else if (u2 && flag[0] == '-') + { + ircdproto->SendSVSO(Config->OperServ, nick, flag); + source.Reply(_("Operflags \002%s\002 have been added for \002%s\002."), flag.c_str(), nick.c_str()); + ircdproto->SendGlobops(source.owner, "\2%s\2 used OLINE for %s", u->nick.c_str(), nick.c_str()); + } + else + this->OnSyntaxError(source, ""); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Allows Services Opers to give Operflags to any user.\n" + "Flags have to be prefixed with a \"+\" or a \"-\". To\n" + "remove all flags simply type a \"-\" instead of any flags.")); + return true; + } +}; + +class OSOLine : public Module +{ + CommandOSOLine commandosoline; + + public: + OSOLine(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandosoline(this) + { + this->SetAuthor("Anope"); + + if (!ircd || !ircd->omode) + throw ModuleException("Your IRCd does not support OMODE."); + + ModuleManager::RegisterService(&commandosoline); + } +}; + +MODULE_INIT(OSOLine) diff --git a/modules/commands/os_oper.cpp b/modules/commands/os_oper.cpp new file mode 100644 index 000000000..237c0b5bc --- /dev/null +++ b/modules/commands/os_oper.cpp @@ -0,0 +1,219 @@ +/* OperServ core 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 "module.h" + +class CommandOSOper : public Command +{ + public: + CommandOSOper(Module *creator) : Command(creator, "operserv/oper", 1, 3) + { + this->SetDesc(_("View and change services operators")); + this->SetSyntax(_("ADD \037oper\037 \037type\037")); + this->SetSyntax(_("DEL [\037oper\037]")); + this->SetSyntax(_("INFO [\037type\037]")); + this->SetSyntax(_("LIST")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &subcommand = params[0]; + + if (subcommand.equals_ci("ADD") && params.size() > 2) + { + const Anope::string &oper = params[1]; + const Anope::string &type = params[2]; + + NickAlias *na = findnick(oper); + if (na == NULL) + source.Reply(NICK_X_NOT_REGISTERED, oper.c_str()); + else + { + OperType *ot = OperType::Find(type); + if (ot == NULL) + source.Reply(_("Oper type \2%s\2 has not been configured."), type.c_str()); + else + { + if (na->nc->o) + delete na->nc->o; + na->nc->o = new Oper(na->nc->display, "", "", ot); + + Log(LOG_ADMIN, source.u, this) << "ADD " << na->nick << " as type " << ot->GetName(); + source.Reply("%s (%s) added to the \2%s\2 list.", na->nick.c_str(), na->nc->display.c_str(), ot->GetName().c_str()); + } + } + } + else if (subcommand.equals_ci("DEL") && params.size() > 1) + { + const Anope::string &oper = params[1]; + + NickAlias *na = findnick(oper); + if (na == NULL) + source.Reply(NICK_X_NOT_REGISTERED, oper.c_str()); + else if (!na->nc || !na->nc->o) + source.Reply(_("Nick \2%s\2 is not a services operator."), oper.c_str()); + else + { + delete na->nc->o; + na->nc->o = NULL; + + Log(LOG_ADMIN, source.u, this) << "DEL " << na->nick; + source.Reply(_("Oper privileges removed from %s (%s)."), na->nick.c_str(), na->nc->display.c_str()); + } + } + else if (subcommand.equals_ci("LIST")) + { + source.Reply(_("Name Type")); + for (nickcore_map::const_iterator it = NickCoreList.begin(), it_end = NickCoreList.end(); it != it_end; ++it) + { + NickCore *nc = it->second; + + if (!nc->o) + continue; + + source.Reply(_("%-8s %s"), nc->o->name.c_str(), nc->o->ot->GetName().c_str()); + for (std::list<NickAlias *>::const_iterator it2 = nc->aliases.begin(), it2_end = nc->aliases.end(); it2 != it2_end; ++it2) + if (Oper::Find((*it2)->nick) != NULL) + source.Reply(_(" This oper is configured in the configuration file as %s"), (*it2)->nick.c_str()); + for (std::list<User *>::iterator uit = nc->Users.begin(); uit != nc->Users.end(); ++uit) + { + User *u = *uit; + source.Reply(_(" %s is online using this oper block."), u->nick.c_str()); + } + } + } + else if (subcommand.equals_ci("INFO") && params.size() > 1) + { + Anope::string fulltype = params[1]; + if (params.size() > 2) + fulltype += " " + params[2]; + OperType *ot = OperType::Find(fulltype); + if (ot == NULL) + source.Reply(_("Oper type \2%s\2 has not been configured."), fulltype.c_str()); + else + { + if (ot->GetCommands().empty()) + source.Reply(_("Opertype \2%s\2 has no allowed commands."), ot->GetName().c_str()); + else + { + source.Reply(_("Available commands for \2%s\2:"), ot->GetName().c_str()); + Anope::string buf; + for (std::list<Anope::string>::const_iterator it = ot->GetCommands().begin(), it_end = ot->GetCommands().end(); it != it_end; ++it) + { + buf += *it + " "; + if (buf.length() > 400) + { + source.Reply("%s", buf.c_str()); + buf.clear(); + } + } + if (!buf.empty()) + { + source.Reply("%s", buf.c_str()); + buf.clear(); + } + } + if (ot->GetPrivs().empty()) + source.Reply(_("Opertype \2%s\2 has no allowed privileges."), ot->GetName().c_str()); + else + { + source.Reply(_("Available privileges for \2%s\2:"), ot->GetName().c_str()); + Anope::string buf; + for (std::list<Anope::string>::const_iterator it = ot->GetPrivs().begin(), it_end = ot->GetPrivs().end(); it != it_end; ++it) + { + buf += *it + " "; + if (buf.length() > 400) + { + source.Reply("%s", buf.c_str()); + buf.clear(); + } + } + if (!buf.empty()) + { + source.Reply("%s", buf.c_str()); + buf.clear(); + } + } + if (!ot->modes.empty()) + source.Reply(_("Opertype \2%s\2 receives modes \2%s\2 once identifying."), ot->GetName().c_str(), ot->modes.c_str()); + } + } + else + this->OnSyntaxError(source, subcommand); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Allows you to change and view services operators.\n" + "Note that operators removed by this command but are still set in\n" + "the configuration file are not permanently affected by this.")); + return true; + } +}; + +class OSOper : public Module +{ + CommandOSOper commandosoper; + + public: + OSOper(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandosoper(this) + { + this->SetAuthor("Anope"); + + Implementation i[] = { I_OnDatabaseWrite, I_OnDatabaseRead }; + ModuleManager::Attach(i, this, sizeof(i) / sizeof(Implementation)); + + ModuleManager::RegisterService(&commandosoper); + } + + void OnDatabaseWrite(void (*Write)(const Anope::string &)) + { + for (nickcore_map::const_iterator it = NickCoreList.begin(), it_end = NickCoreList.end(); it != it_end; ++it) + { + NickCore *nc = it->second; + if (!nc->o) + continue; + bool found = false; + for (std::list<NickAlias *>::const_iterator it2 = nc->aliases.begin(), it2_end = nc->aliases.end(); it2 != it2_end; ++it2) + if (Oper::Find((*it2)->nick) != NULL) + found = true; + if (found == false) + { + Write("OPER " + nc->display + " :" + nc->o->ot->GetName()); + } + } + } + + EventReturn OnDatabaseRead(const std::vector<Anope::string> ¶ms) + { + if (params[0] == "OPER" && params.size() > 2) + { + NickCore *nc = findcore(params[1]); + if (!nc || nc->o) + return EVENT_CONTINUE; + OperType *ot = OperType::Find(params[2]); + if (ot == NULL) + return EVENT_CONTINUE; + nc->o = new Oper(nc->display, "", "", ot); + Log(LOG_NORMAL, "operserv/oper") << "Tied oper " << nc->display << " to type " << ot->GetName(); + } + return EVENT_CONTINUE; + } +}; + +MODULE_INIT(OSOper) diff --git a/modules/commands/os_reload.cpp b/modules/commands/os_reload.cpp new file mode 100644 index 000000000..d4d8eac7c --- /dev/null +++ b/modules/commands/os_reload.cpp @@ -0,0 +1,72 @@ +/* OperServ core 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 "module.h" + +class CommandOSReload : public Command +{ + public: + CommandOSReload(Module *creator) : Command(creator, "operserv/reload", 0, 0) + { + this->SetDesc(_("Reload services' configuration file")); + this->SetSyntax(""); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + ServerConfig *old_config = Config; + + try + { + Config = new ServerConfig(); + FOREACH_MOD(I_OnReload, OnReload()); + delete old_config; + source.Reply(_("Services' configuration file has been reloaded.")); + } + catch (const ConfigException &ex) + { + Config = old_config; + Log() << "Error reloading configuration file: " << ex.GetReason(); + source.Reply(_("Error reloading confguration file: ") + ex.GetReason()); + } + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Causes Services to reload the configuration file. Note that\n" + "some directives still need the restart of the Services to\n" + "take effect (such as Services' nicknames, activation of the \n" + "session limitation, etc.)")); + return true; + } +}; + +class OSReload : public Module +{ + CommandOSReload commandosreload; + + public: + OSReload(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandosreload(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandosreload); + } +}; + +MODULE_INIT(OSReload) diff --git a/modules/commands/os_session.cpp b/modules/commands/os_session.cpp new file mode 100644 index 000000000..990cd1b4b --- /dev/null +++ b/modules/commands/os_session.cpp @@ -0,0 +1,749 @@ +/* OperServ core 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 "module.h" +#include "os_session.h" + +static service_reference<SessionService> sessionservice("session"); + +class MySessionService : public SessionService +{ + SessionMap Sessions; + ExceptionVector Exceptions; + public: + MySessionService(Module *m) : SessionService(m) { } + + void AddException(Exception *e) + { + this->Exceptions.push_back(e); + } + + void DelException(Exception *e) + { + ExceptionVector::iterator it = std::find(this->Exceptions.begin(), this->Exceptions.end(), e); + if (it != this->Exceptions.end()) + this->Exceptions.erase(it); + } + + Exception *FindException(User *u) + { + for (std::vector<Exception *>::const_iterator it = this->Exceptions.begin(), it_end = this->Exceptions.end(); it != it_end; ++it) + { + Exception *e = *it; + if (Anope::Match(u->host, e->mask) || (u->ip() && Anope::Match(u->ip.addr(), e->mask))) + return e; + } + return NULL; + } + + Exception *FindException(const Anope::string &host) + { + for (std::vector<Exception *>::const_iterator it = this->Exceptions.begin(), it_end = this->Exceptions.end(); it != it_end; ++it) + { + Exception *e = *it; + if (Anope::Match(host, e->mask)) + return e; + } + + return NULL; + } + + ExceptionVector &GetExceptions() + { + return this->Exceptions; + } + + void AddSession(Session *s) + { + this->Sessions[s->host] = s; + } + + void DelSession(Session *s) + { + this->Sessions.erase(s->host); + } + + Session *FindSession(const Anope::string &mask) + { + SessionMap::iterator it = this->Sessions.find(mask); + if (it != this->Sessions.end()) + return it->second; + return NULL; + } + + SessionMap &GetSessions() + { + return this->Sessions; + } +}; + +class ExpireTimer : public Timer +{ + public: + ExpireTimer() : Timer(Config->ExpireTimeout, Anope::CurTime, true) { } + + void Tick(time_t) + { + if (!sessionservice) + return; + for (unsigned i = sessionservice->GetExceptions().size(); i > 0; --i) + { + Exception *e = sessionservice->GetExceptions()[i - 1]; + + if (!e->expires || e->expires > Anope::CurTime) + continue; + BotInfo *bi = findbot(Config->OperServ); + if (Config->WallExceptionExpire && bi) + ircdproto->SendGlobops(bi, "Session exception for %s has expired.", e->mask.c_str()); + sessionservice->DelException(e); + delete e; + } + } +}; + +class ExceptionDelCallback : public NumberList +{ + protected: + CommandSource &source; + unsigned Deleted; + public: + ExceptionDelCallback(CommandSource &_source, const Anope::string &numlist) : NumberList(numlist, true), source(_source), Deleted(0) + { + } + + ~ExceptionDelCallback() + { + if (!Deleted) + source.Reply(_("No matching entries on session-limit exception list.")); + else if (Deleted == 1) + source.Reply(_("Deleted 1 entry from session-limit exception list.")); + else + source.Reply(_("Deleted %d entries from session-limit exception list."), Deleted); + } + + virtual void HandleNumber(unsigned Number) + { + if (!Number || Number > sessionservice->GetExceptions().size()) + return; + + ++Deleted; + + DoDel(source, Number - 1); + } + + static void DoDel(CommandSource &source, unsigned index) + { + Exception *e = sessionservice->GetExceptions()[index]; + FOREACH_MOD(I_OnExceptionDel, OnExceptionDel(source.u, e)); + + sessionservice->DelException(e); + delete e; + } +}; + +class ExceptionListCallback : public NumberList +{ + protected: + CommandSource &source; + bool SentHeader; + public: + ExceptionListCallback(CommandSource &_source, const Anope::string &numlist) : NumberList(numlist, false), source(_source), SentHeader(false) + { + } + + virtual void HandleNumber(unsigned Number) + { + if (!Number || Number > sessionservice->GetExceptions().size()) + return; + + if (!SentHeader) + { + SentHeader = true; + source.Reply(_("Current Session Limit Exception list:")); + source.Reply(_("Num Limit Host")); + } + + DoList(source, Number - 1); + } + + static void DoList(CommandSource &source, unsigned index) + { + if (index >= sessionservice->GetExceptions().size()) + return; + + source.Reply(_("%3d %4d %s"), index + 1, sessionservice->GetExceptions()[index]->limit, sessionservice->GetExceptions()[index]->mask.c_str()); + } +}; + +class ExceptionViewCallback : public ExceptionListCallback +{ + public: + ExceptionViewCallback(CommandSource &_source, const Anope::string &numlist) : ExceptionListCallback(_source, numlist) + { + } + + void HandleNumber(unsigned Number) + { + if (!Number || Number > sessionservice->GetExceptions().size()) + return; + + if (!SentHeader) + { + SentHeader = true; + source.Reply(_("Current Session Limit Exception list:")); + } + + DoList(source, Number - 1); + } + + static void DoList(CommandSource &source, unsigned index) + { + if (index >= sessionservice->GetExceptions().size()) + return; + + Anope::string expirebuf = expire_left(source.u->Account(), sessionservice->GetExceptions()[index]->expires); + + source.Reply(_("%3d. %s (by %s on %s; %s)\n " " Limit: %-4d - %s"), index + 1, sessionservice->GetExceptions()[index]->mask.c_str(), !sessionservice->GetExceptions()[index]->who.empty() ? sessionservice->GetExceptions()[index]->who.c_str() : "<unknown>", do_strftime((sessionservice->GetExceptions()[index]->time ? sessionservice->GetExceptions()[index]->time : Anope::CurTime)).c_str(), expirebuf.c_str(), sessionservice->GetExceptions()[index]->limit, sessionservice->GetExceptions()[index]->reason.c_str()); + } +}; + +class CommandOSSession : public Command +{ + private: + void DoList(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + Anope::string param = params[1]; + + unsigned mincount = 0; + try + { + mincount = convertTo<unsigned>(param); + } + catch (const ConvertException &) { } + + if (mincount <= 1) + source.Reply(_("Invalid threshold value. It must be a valid integer greater than 1.")); + else + { + source.Reply(_("Hosts with at least \002%d\002 sessions:"), mincount); + source.Reply(_("Sessions Host")); + + for (SessionService::SessionMap::iterator it = sessionservice->GetSessions().begin(), it_end = sessionservice->GetSessions().end(); it != it_end; ++it) + { + Session *session = it->second; + + if (session->count >= mincount) + source.Reply(_("%6d %s"), session->count, session->host.c_str()); + } + } + + return; + } + + void DoView(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + Anope::string param = params[1]; + Session *session = sessionservice->FindSession(param); + + if (!session) + source.Reply(_("\002%s\002 not found on session list."), param.c_str()); + else + { + Exception *exception = sessionservice->FindException(param); + source.Reply(_("The host \002%s\002 currently has \002%d\002 sessions with a limit of \002%d\002."), param.c_str(), session->count, exception ? exception-> limit : Config->DefSessionLimit); + } + + return; + } + public: + CommandOSSession(Module *creator) : Command(creator, "operserv/session", 2, 2) + { + this->SetDesc(_("View the list of host sessions")); + this->SetSyntax(_("LIST \037threshold\037")); + this->SetSyntax(_("VIEW \037host\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &cmd = params[0]; + + if (!Config->LimitSessions) + { + source.Reply(_("Session limiting is disabled.")); + return; + } + + if (cmd.equals_ci("LIST")) + return this->DoList(source, params); + else if (cmd.equals_ci("VIEW")) + return this->DoView(source, params); + else + this->OnSyntaxError(source, ""); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Allows Services Operators to view the session list.\n" + "\002SESSION LIST\002 lists hosts with at least \037threshold\037 sessions.\n" + "The threshold must be a number greater than 1. This is to \n" + "prevent accidental listing of the large number of single \n" + "session hosts.\n" + "\002SESSION VIEW\002 displays detailed information about a specific\n" + "host - including the current session count and session limit.\n" + "The \037host\037 value may not include wildcards.\n" + "See the \002EXCEPTION\002 help for more information about session\n" + "limiting and how to set session limits specific to certain\n" + "hosts and groups thereof.")); + return true; + } +}; + +class CommandOSException : public Command +{ + private: + void DoAdd(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + Anope::string mask, expiry, limitstr; + unsigned last_param = 3; + + mask = params.size() > 1 ? params[1] : ""; + if (!mask.empty() && mask[0] == '+') + { + expiry = mask; + mask = params.size() > 2 ? params[2] : ""; + last_param = 4; + } + + limitstr = params.size() > last_param - 1 ? params[last_param - 1] : ""; + + if (params.size() <= last_param) + { + this->OnSyntaxError(source, "ADD"); + return; + } + + Anope::string reason = params[last_param]; + if (last_param == 3 && params.size() > 4) + reason += " " + params[4]; + if (reason.empty()) + { + this->OnSyntaxError(source, "ADD"); + return; + } + + time_t expires = !expiry.empty() ? dotime(expiry) : Config->ExceptionExpiry; + if (expires < 0) + { + source.Reply(BAD_EXPIRY_TIME); + return; + } + else if (expires > 0) + expires += Anope::CurTime; + + int limit = -1; + try + { + limit = convertTo<int>(limitstr); + } + catch (const ConvertException &) { } + + if (limit < 0 || limit > static_cast<int>(Config->MaxSessionLimit)) + { + source.Reply(_("Invalid session limit. It must be a valid integer greater than or equal to zero and less than \002%d\002."), Config->MaxSessionLimit); + return; + } + else + { + if (mask.find('!') != Anope::string::npos || mask.find('@') != Anope::string::npos) + { + source.Reply(_("Invalid hostmask. Only real hostmasks are valid as sessionservice->GetExceptions() are not matched against nicks or usernames.")); + return; + } + + for (std::vector<Exception *>::iterator it = sessionservice->GetExceptions().begin(), it_end = sessionservice->GetExceptions().end(); it != it_end; ++it) + { + Exception *e = *it; + if (e->mask.equals_ci(mask)) + { + if (e->limit != limit) + { + e->limit = limit; + source.Reply(_("Exception for \002%s\002 has been updated to %d."), mask.c_str(), e->limit); + } + else + source.Reply(_("\002%s\002 already exists on the EXCEPTION list."), mask.c_str()); + } + } + + Exception *exception = new Exception(); + exception->mask = mask; + exception->limit = limit; + exception->reason = reason; + exception->time = Anope::CurTime; + exception->who = u->nick; + exception->expires = expires; + + EventReturn MOD_RESULT; + FOREACH_RESULT(I_OnExceptionAdd, OnExceptionAdd(exception)); + if (MOD_RESULT == EVENT_STOP) + delete exception; + else + { + sessionservice->AddException(exception); + source.Reply(_("Session limit for \002%s\002 set to \002%d\002."), mask.c_str(), limit); + if (readonly) + source.Reply(READ_ONLY_MODE); + } + } + + return; + } + + void DoDel(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &mask = params.size() > 1 ? params[1] : ""; + + if (mask.empty()) + { + this->OnSyntaxError(source, "DEL"); + return; + } + + if (isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos) + { + ExceptionDelCallback list(source, mask); + list.Process(); + } + else + { + unsigned i = 0, end = sessionservice->GetExceptions().size(); + for (; i < end; ++i) + if (mask.equals_ci(sessionservice->GetExceptions()[i]->mask)) + { + ExceptionDelCallback::DoDel(source, i); + source.Reply(_("\002%s\002 deleted from session-limit exception list."), mask.c_str()); + break; + } + if (i == end) + source.Reply(_("\002%s\002 not found on session-limit exception list."), mask.c_str()); + } + + if (readonly) + source.Reply(READ_ONLY_MODE); + + return; + } + + void DoMove(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &n1str = params.size() > 1 ? params[1] : ""; /* From position */ + const Anope::string &n2str = params.size() > 2 ? params[2] : ""; /* To position */ + int n1, n2; + + if (n2str.empty()) + { + this->OnSyntaxError(source, "MOVE"); + return; + } + + n1 = n2 = -1; + try + { + n1 = convertTo<int>(n1str); + n2 = convertTo<int>(n2str); + } + catch (const ConvertException &) { } + + if (n1 >= 0 && n1 < sessionservice->GetExceptions().size() && n2 >= 0 && n2 < sessionservice->GetExceptions().size() && n1 != n2) + { + Exception *temp = sessionservice->GetExceptions()[n1]; + sessionservice->GetExceptions()[n1] = sessionservice->GetExceptions()[n2]; + sessionservice->GetExceptions()[n2] = temp; + + source.Reply(_("Exception for \002%s\002 (#%d) moved to position \002%d\002."), sessionservice->GetExceptions()[n1]->mask.c_str(), n1 + 1, n2 + 1); + + if (readonly) + source.Reply(READ_ONLY_MODE); + } + else + this->OnSyntaxError(source, "MOVE"); + + return; + } + + void DoList(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + Anope::string mask = params.size() > 1 ? params[1] : ""; + + if (!mask.empty() && mask.find_first_not_of("1234567890,-") == Anope::string::npos) + { + ExceptionListCallback list(source, mask); + list.Process(); + } + else + { + bool SentHeader = false; + + for (unsigned i = 0, end = sessionservice->GetExceptions().size(); i < end; ++i) + if (mask.empty() || Anope::Match(sessionservice->GetExceptions()[i]->mask, mask)) + { + if (!SentHeader) + { + SentHeader = true; + source.Reply(_("Current Session Limit Exception list:")); + source.Reply(_("Num Limit Host")); + } + + ExceptionListCallback::DoList(source, i); + } + + if (!SentHeader) + source.Reply(_("No matching entries on session-limit exception list.")); + } + + return; + } + + void DoView(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + Anope::string mask = params.size() > 1 ? params[1] : ""; + + if (!mask.empty() && mask.find_first_not_of("1234567890,-") == Anope::string::npos) + { + ExceptionViewCallback list(source, mask); + list.Process(); + } + else + { + bool SentHeader = false; + + for (unsigned i = 0, end = sessionservice->GetExceptions().size(); i < end; ++i) + if (mask.empty() || Anope::Match(sessionservice->GetExceptions()[i]->mask, mask)) + { + if (!SentHeader) + { + SentHeader = true; + source.Reply(_("Current Session Limit Exception list:")); + } + + ExceptionViewCallback::DoList(source, i); + } + + if (!SentHeader) + source.Reply(_("No matching entries on session-limit exception list.")); + } + + return; + } + + public: + CommandOSException(Module *creator) : Command(creator, "operserv/exception", 1, 5) + { + this->SetDesc(_("Modify the session-limit exception list")); + this->SetSyntax(_("ADD [\037+expiry\037] \037mask\037 \037limit\037 \037reason\037")); + this->SetSyntax(_("DEL {\037mask\037 | \037list\037}")); + this->SetSyntax(_("MOVE \037num\037 \037position\037")); + this->SetSyntax(_("LIST [\037mask\037 | \037list\037]")); + this->SetSyntax(_("VIEW [\037mask\037 | \037list\037]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &cmd = params[0]; + + if (!Config->LimitSessions) + { + source.Reply(_("Session limiting is disabled.")); + return; + } + + if (cmd.equals_ci("ADD")) + return this->DoAdd(source, params); + else if (cmd.equals_ci("DEL")) + return this->DoDel(source, params); + else if (cmd.equals_ci("MOVE")) + return this->DoMove(source, params); + else if (cmd.equals_ci("LIST")) + return this->DoList(source, params); + else if (cmd.equals_ci("VIEW")) + return this->DoView(source, params); + else + this->OnSyntaxError(source, ""); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Allows Services Operators to manipulate the list of hosts that\n" + "have specific session limits - allowing certain machines, \n" + "such as shell servers, to carry more than the default number\n" + "of clients at a time. Once a host reaches its session limit,\n" + "all clients attempting to connect from that host will be\n" + "killed. Before the user is killed, they are notified, via a\n" + "/NOTICE from %s, of a source of help regarding session\n" + "limiting. The content of this notice is a config setting.\n"), + Config->OperServ.c_str()); + source.Reply(" "); + source.Reply(_("\002EXCEPTION ADD\002 adds the given host mask to the exception list.\n" + "Note that \002nick!user@host\002 and \002user@host\002 masks are invalid!\n" + "Only real host masks, such as \002box.host.dom\002 and \002*.host.dom\002,\n" + "are allowed because sessions limiting does not take nick or\n" + "user names into account. \037limit\037 must be a number greater than\n" + "or equal to zero. This determines how many sessions this host\n" + "may carry at a time. A value of zero means the host has an\n" + "unlimited session limit. See the \002AKILL\002 help for details about\n" + "the format of the optional \037expiry\037 parameter.\n" + "\002EXCEPTION DEL\002 removes the given mask from the exception list.\n" + "\002EXCEPTION MOVE\002 moves exception \037num\037 to \037position\037. The\n" + "sessionservice->GetExceptions() inbetween will be shifted up or down to fill the gap.\n" + "\002EXCEPTION LIST\002 and \002EXCEPTION VIEW\002 show all current\n" + "sessionservice->GetExceptions(); if the optional mask is given, the list is limited\n" + "to those sessionservice->GetExceptions() matching the mask. The difference is that\n" + "\002EXCEPTION VIEW\002 is more verbose, displaying the name of the\n" + "person who added the exception, its session limit, reason, \n" + "host mask and the expiry date and time.\n" + " \n" + "Note that a connecting client will \"use\" the first exception\n" + "their host matches.")); + return true; + } +}; + +class OSSession : public Module +{ + MySessionService ss; + ExpireTimer expiretimer; + CommandOSSession commandossession; + CommandOSException commandosexception; + service_reference<XLineManager> akills; + + void AddSession(User *u, bool exempt) + { + Session *session = this->ss.FindSession(u->host); + + if (session) + { + bool kill = false; + if (Config->DefSessionLimit && session->count >= Config->DefSessionLimit) + { + kill = true; + Exception *exception = this->ss.FindException(u); + if (exception) + { + kill = false; + if (exception->limit && session->count >= exception->limit) + kill = true; + } + } + + /* Previously on IRCds that send a QUIT (InspIRCD) when a user is killed, the session for a host was + * decremented in do_quit, which caused problems and fixed here + * + * Now, we create the user struture before calling this to fix some user tracking issues, + * so we must increment this here no matter what because it will either be + * decremented in do_kill or in do_quit - Adam + */ + ++session->count; + + if (kill && !exempt) + { + BotInfo *bi = findbot(Config->OperServ); + if (bi) + { + if (!Config->SessionLimitExceeded.empty()) + u->SendMessage(bi, Config->SessionLimitExceeded.c_str(), u->host.c_str()); + if (!Config->SessionLimitDetailsLoc.empty()) + u->SendMessage(bi, "%s", Config->SessionLimitDetailsLoc.c_str()); + } + + u->Kill(Config->OperServ, "Session limit exceeded"); + + ++session->hits; + if (Config->MaxSessionKill && session->hits >= Config->MaxSessionKill && akills) + { + const Anope::string &akillmask = "*@" + u->host; + const Anope::string &akillreason = "Session limit exceeded for " + u->host; + akills->Add(akillmask, Config->OperServ, Anope::CurTime + Config->SessionAutoKillExpiry, akillreason); + if (bi) + ircdproto->SendGlobops(bi, "Added a temporary AKILL for \2%s\2 due to excessive connections", akillmask.c_str()); + } + } + } + else + { + session = new Session(); + session->host = u->host; + session->count = 1; + session->hits = 0; + + this->ss.AddSession(session); + } + } + + void DelSession(User *u) + { + Session *session = this->ss.FindSession(u->host); + if (!session) + { + if (debug) + Log() << "session: Tried to delete non-existant session: " << u->host; + return; + } + + if (session->count > 1) + { + --session->count; + return; + } + + this->ss.DelSession(session); + delete session; + } + + public: + OSSession(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + ss(this), commandossession(this), commandosexception(this), akills("xlinemanager/sgline") + { + this->SetAuthor("Anope"); + + Implementation i[] = { I_OnUserConnect, I_OnUserLogoff }; + ModuleManager::Attach(i, this, sizeof(i) / sizeof(Implementation)); + + ModuleManager::RegisterService(&commandossession); + ModuleManager::RegisterService(&commandosexception); + + ModuleManager::RegisterService(&this->ss); + } + + void OnUserConnect(dynamic_reference<User> &user, bool &exempt) + { + if (user && Config->LimitSessions) + this->AddSession(user, exempt); + } + + void OnUserLogoff(User *u) + { + if (Config->LimitSessions && (!u->server || !u->server->IsULined())) + this->DelSession(u); + } +}; + +MODULE_INIT(OSSession) diff --git a/modules/commands/os_session.h b/modules/commands/os_session.h new file mode 100644 index 000000000..faab37934 --- /dev/null +++ b/modules/commands/os_session.h @@ -0,0 +1,32 @@ +#ifndef OS_SESSION_H +#define OS_SESSION_H + +class SessionService : public Service +{ + public: + typedef Anope::map<Session *> SessionMap; + typedef std::vector<Exception *> ExceptionVector; + + SessionService(Module *m) : Service(m, "session") { } + + virtual void AddException(Exception *e) = 0; + + virtual void DelException(Exception *e) = 0; + + virtual Exception *FindException(User *u) = 0; + + virtual Exception *FindException(const Anope::string &host) = 0; + + virtual ExceptionVector &GetExceptions() = 0; + + virtual void AddSession(Session *s) = 0; + + virtual void DelSession(Session *s) = 0; + + virtual Session *FindSession(const Anope::string &mask) = 0; + + virtual SessionMap &GetSessions() = 0; +}; + +#endif + diff --git a/modules/commands/os_set.cpp b/modules/commands/os_set.cpp new file mode 100644 index 000000000..2a50f851c --- /dev/null +++ b/modules/commands/os_set.cpp @@ -0,0 +1,261 @@ +/* OperServ core 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 "module.h" + +class CommandOSSet : public Command +{ + private: + void DoList(CommandSource &source) + { + Log(LOG_ADMIN, source.u, this); + + Anope::string index; + + index = readonly ? _("%s is enabled") : _("%s is disabled"); + source.Reply(index.c_str(), "READONLY"); + index = debug ? _("%s is enabled") : _("%s is disabled"); + source.Reply(index.c_str(), "DEBUG"); + index = noexpire ? _("%s is enabled") : _("%s is disabled"); + source.Reply(index.c_str(), "NOEXPIRE"); + + return; + } + + void DoSetReadOnly(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + const Anope::string &setting = params.size() > 1 ? params[1] : ""; + + if (setting.empty()) + { + this->OnSyntaxError(source, "READONLY"); + return; + } + + if (setting.equals_ci("ON")) + { + readonly = true; + Log(LOG_ADMIN, u, this) << "READONLY ON"; + source.Reply(_("Services are now in \002read-only\002 mode.")); + } + else if (setting.equals_ci("OFF")) + { + readonly = false; + Log(LOG_ADMIN, u, this) << "READONLY OFF"; + source.Reply(_("Services are now in \002read-write\002 mode.")); + } + else + source.Reply(_("Setting for READONLY must be \002on\002 or \002off\002.")); + + return; + } + + void DoSetSuperAdmin(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + const Anope::string &setting = params.size() > 1 ? params[1] : ""; + + if (setting.empty()) + { + this->OnSyntaxError(source, "SUPERADMIN"); + return; + } + + /** + * Allow the user to turn super admin on/off + * + * Rob + **/ + if (!Config->SuperAdmin) + source.Reply(_("SuperAdmin setting not enabled in services.conf")); + else if (setting.equals_ci("ON")) + { + u->isSuperAdmin = 1; + source.Reply(_("You are now a SuperAdmin")); + Log(LOG_ADMIN, u, this) << "SUPERADMIN ON"; + ircdproto->SendGlobops(source.owner, _("%s is now a Super-Admin"), u->nick.c_str()); + } + else if (setting.equals_ci("OFF")) + { + u->isSuperAdmin = 0; + source.Reply(_("You are no longer a SuperAdmin")); + Log(LOG_ADMIN, u, this) << "SUPERADMIN OFF"; + ircdproto->SendGlobops(source.owner, _("%s is no longer a Super-Admin"), u->nick.c_str()); + } + else + source.Reply(_("Setting for SuperAdmin must be \002on\002 or \002off\002 (must be enabled in services.conf)")); + + return; + } + + void DoSetDebug(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + const Anope::string &setting = params.size() > 1 ? params[1] : ""; + + if (setting.empty()) + { + this->OnSyntaxError(source, "DEBUG"); + return; + } + + if (setting.equals_ci("ON")) + { + debug = 1; + Log(LOG_ADMIN, u, this) << "DEBUG ON"; + source.Reply(_("Services are now in debug mode.")); + } + else if (setting.equals_ci("OFF") || (setting[0] == '0' && setting.is_number_only() && !convertTo<int>(setting))) + { + Log(LOG_ADMIN, u, this) << "DEBUG OFF"; + debug = 0; + source.Reply(_("Services are now in non-debug mode.")); + } + else + { + try + { + debug = convertTo<int>(setting); + Log(LOG_ADMIN, u, this) << "DEBUG " << debug; + source.Reply(_("Services are now in debug mode (level %d)."), debug); + return; + } + catch (const ConvertException &) { } + + source.Reply(_("Setting for DEBUG must be \002ON\002, \002OFF\002, or a positive number.")); + } + + return; + } + + void DoSetNoExpire(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + const Anope::string &setting = params.size() > 1 ? params[1] : ""; + + if (setting.empty()) + { + this->OnSyntaxError(source, "NOEXPIRE"); + return; + } + + if (setting.equals_ci("ON")) + { + noexpire = true; + Log(LOG_ADMIN, u, this) << "NOEXPIRE ON"; + source.Reply(_("Services are now in \002no expire\002 mode.")); + } + else if (setting.equals_ci("OFF")) + { + noexpire = false; + Log(LOG_ADMIN, u, this) << "NOEXPIRE OFF"; + source.Reply(_("Services are now in \002expire\002 mode.")); + } + else + source.Reply(_("Setting for NOEXPIRE must be \002on\002 or \002off\002.")); + + return; + } + public: + CommandOSSet(Module *creator) : Command(creator, "operserv/set", 1, 2) + { + this->SetDesc(_("Set various global Services options")); + this->SetSyntax(_("\037option\037 \037setting\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &option = params[0]; + + if (option.equals_ci("LIST")) + return this->DoList(source); + else if (option.equals_ci("READONLY")) + return this->DoSetReadOnly(source, params); + else if (option.equals_ci("SUPERADMIN")) + return this->DoSetSuperAdmin(source, params); + else if (option.equals_ci("DEBUG")) + return this->DoSetDebug(source, params); + else if (option.equals_ci("NOEXPIRE")) + return this->DoSetNoExpire(source, params); + else + source.Reply(_("Unknown option \002%s\002."), option.c_str()); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + if (subcommand.empty()) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Sets various global Services options. Option names\n" + "currently defined are:\n" + " READONLY Set read-only or read-write mode\n" + " DEBUG Activate or deactivate debug mode\n" + " NOEXPIRE Activate or deactivate no expire mode\n" + " SUPERADMIN Activate or deactivate super-admin mode\n" + " LIST List the options")); + } + else if (subcommand.equals_ci("LIST")) + source.Reply(_("Syntax: \002LIST\n" + "Display the various %s settings"), Config->OperServ.c_str()); + else if (subcommand.equals_ci("READONLY")) + source.Reply(_("Syntax: \002READONLY {ON | OFF}\002\n" + " \n" + "Sets read-only mode on or off. In read-only mode, normal\n" + "users will not be allowed to modify any Services data,\n" + "including channel and nickname access lists, etc. IRCops\n" + "with sufficient Services privileges will be able to modify\n" + "Services' AKILL list and drop or forbid nicknames and\n" + "channels, but any such changes will not be saved unless\n" + "read-only mode is deactivated before Services is terminated\n" + "or restarted.\n" + " \n" + "This option is equivalent to the command-line option\n" + "\002-readonly\002.")); + else if (subcommand.equals_ci("NOEXPIRE")) + source.Reply(_("Syntax: \002NOEXPIRE {ON | OFF}\002\n" + "Sets no expire mode on or off. In no expire mode, nicks,\n" + "channels, akills and exceptions won't expire until the\n" + "option is unset.\n" + "This option is equivalent to the command-line option\n" + "\002-noexpire\002.")); + else if (subcommand.equals_ci("SUPERADMIN")) + source.Reply(_("Syntax: \002SUPERADMIN {ON | OFF}\002\n" + "Setting this will grant you extra privileges such as the\n" + "ability to be \"founder\" on all channel's etc...\n" + "This option is \002not\002 persistent, and should only be used when\n" + "needed, and set back to OFF when no longer needed.")); + else + return false; + return true; + } +}; + +class OSSet : public Module +{ + CommandOSSet commandosset; + + public: + OSSet(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandosset(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandosset); + } +}; + +MODULE_INIT(OSSet) diff --git a/modules/commands/os_shutdown.cpp b/modules/commands/os_shutdown.cpp new file mode 100644 index 000000000..bc3a19353 --- /dev/null +++ b/modules/commands/os_shutdown.cpp @@ -0,0 +1,117 @@ +/* OperServ core 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 "module.h" + +class CommandOSQuit : public Command +{ + public: + CommandOSQuit(Module *creator) : Command(creator, "operserv/quit", 0, 0) + { + this->SetDesc(_("Terminate Services WITHOUT saving")); + this->SetSyntax(""); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + quitmsg = "QUIT command received from " + u->nick; + quitting = true; + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Causes Services to do an immediate shutdown; databases are\n" + "\002not\002 saved. This command should not be used unless\n" + "damage to the in-memory copies of the databases is feared\n" + "and they should not be saved.")); + return true; + } +}; + +class CommandOSRestart : public Command +{ + public: + CommandOSRestart(Module *creator) : Command(creator, "operserv/restart", 0, 0) + { + this->SetDesc(_("Save databases and restart Services")); + this->SetSyntax(""); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + quitmsg = "RESTART command received from " + u->nick; + save_databases(); + quitting = restarting = true; + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(_("Causes Services to save all databases and then restart\n" + "(i.e. exit and immediately re-run the executable).")); + return true; + } +}; + +class CommandOSShutdown : public Command +{ + public: + CommandOSShutdown(Module *creator) : Command(creator, "operserv/shutdown", 0, 0) + { + this->SetDesc(_("Terminate services with save")); + this->SetSyntax(""); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + quitmsg = source.command + " command received from " + u->nick; + save_databases(); + quitting = true; + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Causes Services to save all databases and then shut down.")); + return true; + } +}; + +class OSShutdown : public Module +{ + CommandOSQuit commandosquit; + CommandOSRestart commandosrestart; + CommandOSShutdown commandosshutdown; + + public: + OSShutdown(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandosquit(this), commandosrestart(this), commandosshutdown(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandosquit); + ModuleManager::RegisterService(&commandosrestart); + ModuleManager::RegisterService(&commandosshutdown); + } +}; + +MODULE_INIT(OSShutdown) diff --git a/modules/commands/os_stats.cpp b/modules/commands/os_stats.cpp new file mode 100644 index 000000000..2c20395d0 --- /dev/null +++ b/modules/commands/os_stats.cpp @@ -0,0 +1,224 @@ +/* OperServ core 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 "module.h" + +/** + * Count servers connected to server s + * @param s The server to start counting from + * @return Amount of servers connected to server s + **/ +static int stats_count_servers(Server *s) +{ + if (!s) + return 0; + + int count = 1; + + if (!s->GetLinks().empty()) + for (unsigned i = 0, j = s->GetLinks().size(); i < j; ++i) + count += stats_count_servers(s->GetLinks()[i]); + + return count; +} + +class CommandOSStats : public Command +{ + service_reference<XLineManager> akills, snlines, sqlines, szlines; + private: + void DoStatsAkill(CommandSource &source) + { + int timeout; + if (akills) + { + /* AKILLs */ + source.Reply(_("Current number of AKILLs: \002%d\002"), akills->GetCount()); + timeout = Config->AutokillExpiry + 59; + if (timeout >= 172800) + source.Reply(_("Default AKILL expiry time: \002%d days\002"), timeout / 86400); + else if (timeout >= 86400) + source.Reply(_("Default AKILL expiry time: \0021 day\002")); + else if (timeout >= 7200) + source.Reply(_("Default AKILL expiry time: \002%d hours\002"), timeout / 3600); + else if (timeout >= 3600) + source.Reply(_("Default AKILL expiry time: \0021 hour\002")); + else if (timeout >= 120) + source.Reply(_("Default AKILL expiry time: \002%d minutes\002"), timeout / 60); + else if (timeout >= 60) + source.Reply(_("Default AKILL expiry time: \0021 minute\002")); + else + source.Reply(_("Default AKILL expiry time: \002No expiration\002")); + } + if (ircd->snline && snlines) + { + /* SNLINEs */ + source.Reply(_("Current number of SNLINEs: \002%d\002"), snlines->GetCount()); + timeout = Config->SNLineExpiry + 59; + if (timeout >= 172800) + source.Reply(_("Default SNLINE expiry time: \002%d days\002"), timeout / 86400); + else if (timeout >= 86400) + source.Reply(_("Default SNLINE expiry time: \0021 day\002")); + else if (timeout >= 7200) + source.Reply(_("Default SNLINE expiry time: \002%d hours\002"), timeout / 3600); + else if (timeout >= 3600) + source.Reply(_("Default SNLINE expiry time: \0021 hour\002")); + else if (timeout >= 120) + source.Reply(_("Default SNLINE expiry time: \002%d minutes\002"), timeout / 60); + else if (timeout >= 60) + source.Reply(_("Default SNLINE expiry time: \0021 minute\002")); + else + source.Reply(_("Default SNLINE expiry time: \002No expiration\002")); + } + if (ircd->sqline && sqlines) + { + /* SQLINEs */ + source.Reply(_("Current number of SQLINEs: \002%d\002"), sqlines->GetCount()); + timeout = Config->SQLineExpiry + 59; + if (timeout >= 172800) + source.Reply(_("Default SQLINE expiry time: \002%d days\002"), timeout / 86400); + else if (timeout >= 86400) + source.Reply(_("Default SQLINE expiry time: \0021 day\002")); + else if (timeout >= 7200) + source.Reply(_("Default SQLINE expiry time: \002%d hours\002"), timeout / 3600); + else if (timeout >= 3600) + source.Reply(_("Default SQLINE expiry time: \0021 hour\002")); + else if (timeout >= 120) + source.Reply(_("Default SQLINE expiry time: \002%d minutes\002"), timeout / 60); + else if (timeout >= 60) + source.Reply(_("Default SQLINE expiry time: \0021 minute\002")); + else + source.Reply(_("Default SQLINE expiry time: \002No expiration\002")); + } + if (ircd->szline && szlines) + { + /* SZLINEs */ + source.Reply(_("Current number of SZLINEs: \002%d\002"), szlines->GetCount()); + timeout = Config->SZLineExpiry + 59; + if (timeout >= 172800) + source.Reply(_("Default SZLINE expiry time: \002%d days\002"), timeout / 86400); + else if (timeout >= 86400) + source.Reply(_("Default SZLINE expiry time: \0021 day\002")); + else if (timeout >= 7200) + source.Reply(_("Default SZLINE expiry time: \002%d hours\002"), timeout / 3600); + else if (timeout >= 3600) + source.Reply(_("Default SZLINE expiry time: \0021 hour\002")); + else if (timeout >= 120) + source.Reply(_("Default SZLINE expiry time: \002%d minutes\002"), timeout / 60); + else if (timeout >= 60) + source.Reply(_("Default SZLINE expiry time: \0021 minute\002")); + else + source.Reply(_("Default SZLINE expiry time: \002No expiration\002")); + } + return; + } + + void DoStatsReset(CommandSource &source) + { + maxusercnt = usercnt; + source.Reply(_("Statistics reset.")); + return; + } + + void DoStatsUptime(CommandSource &source) + { + time_t uptime = Anope::CurTime - start_time; + source.Reply(_("Current users: \002%d\002 (\002%d\002 ops)"), usercnt, opcnt); + source.Reply(_("Maximum users: \002%d\002 (%s)"), maxusercnt, do_strftime(maxusertime).c_str()); + source.Reply(_("Services up %s"), duration(uptime).c_str()); + + return; + } + + void DoStatsUplink(CommandSource &source) + { + Anope::string buf; + + for (unsigned j = 0; !Capab_Info[j].Token.empty(); ++j) + if (Capab.HasFlag(Capab_Info[j].Flag)) + buf += " " + Capab_Info[j].Token; + + if (!buf.empty()) + buf.erase(buf.begin()); + + source.Reply(_("Uplink server: %s"), Me->GetLinks().front()->GetName().c_str()); + source.Reply(_("Uplink capab: %s"), buf.c_str()); + source.Reply(_("Servers found: %d"), stats_count_servers(Me->GetLinks().front())); + return; + } + + public: + CommandOSStats(Module *creator) : Command(creator, "operserv/stats", 0, 1), + akills("xlinemanager/sgline"), snlines("xlinemanager/snline"), sqlines("xlinemanager/sqline"), szlines("xlinemanager/szline") + { + this->SetDesc(_("Show status of Services and network")); + this->SetSyntax(_("[AKILL | ALL | RESET | UPLINK]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + Anope::string extra = !params.empty() ? params[0] : ""; + + if (extra.equals_ci("RESET")) + return this->DoStatsReset(source); + + if (extra.equals_ci("ALL") || extra.equals_ci("AKILL")) + this->DoStatsAkill(source); + + if (extra.empty() || extra.equals_ci("ALL") || extra.equals_ci("UPTIME")) + this->DoStatsUptime(source); + + if (extra.equals_ci("ALL") || extra.equals_ci("UPLINK")) + this->DoStatsUplink(source); + + if (!extra.empty() && !extra.equals_ci("ALL") && !extra.equals_ci("AKILL") && !extra.equals_ci("UPLINK")) + source.Reply(_("Unknown STATS option \002%s\002."), extra.c_str()); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Without any option, shows the current number of users online,\n" + "and the highest number of users online since Services was\n" + "started, and the length of time Services has been running.\n" + " \n" + "With the \002AKILL\002 option, displays the current size of the\n" + "AKILL list and the current default expiry time.\n" + " \n" + "The \002RESET\002 option currently resets the maximum user count\n" + "to the number of users currently present on the network.\n" + " \n" + "The \002UPLINK\002 option displays information about the current\n" + "server Anope uses as an uplink to the network.\n" + " \n" + "The \002ALL\002 displays the user and uptime statistics, and\n" + "everything you'd see with the \002UPLINK\002 option.")); + return true; + } +}; + +class OSStats : public Module +{ + CommandOSStats commandosstats; + + public: + OSStats(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandosstats(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandosstats); + } +}; + +MODULE_INIT(OSStats) diff --git a/modules/commands/os_svsnick.cpp b/modules/commands/os_svsnick.cpp new file mode 100644 index 000000000..dc7c77a6c --- /dev/null +++ b/modules/commands/os_svsnick.cpp @@ -0,0 +1,92 @@ +/* OperServ core 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 "module.h" + +class CommandOSSVSNick : public Command +{ + public: + CommandOSSVSNick(Module *creator) : Command(creator, "operserv/svsnick", 2, 2) + { + this->SetDesc(_("Forcefully change a user's nickname")); + this->SetSyntax(_("\037nick\037 \037newnick\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + const Anope::string &nick = params[0]; + Anope::string newnick = params[1]; + User *u2; + + /* Truncate long nicknames to Config->NickLen characters */ + if (newnick.length() > Config->NickLen) + { + source.Reply(_("Nick \002%s\002 was truncated to %d characters."), newnick.c_str(), Config->NickLen, newnick.c_str()); + newnick = params[1].substr(0, Config->NickLen); + } + + /* Check for valid characters */ + if (newnick[0] == '-' || isdigit(newnick[0])) + { + source.Reply(_("Nick \002%s\002 is an illegal nickname and cannot be used."), newnick.c_str()); + return; + } + for (unsigned i = 0, end = newnick.length(); i < end; ++i) + if (!isvalidnick(newnick[i])) + { + source.Reply(_("Nick \002%s\002 is an illegal nickname and cannot be used."), newnick.c_str()); + return; + } + + /* Check for a nick in use or a forbidden/suspended nick */ + if (!(u2 = finduser(nick))) + source.Reply(NICK_X_NOT_IN_USE, nick.c_str()); + else if (finduser(newnick)) + source.Reply(_("Nick \002%s\002 is currently in use."), newnick.c_str()); + else + { + source.Reply(_("The nick \002%s\002 is now being changed to \002%s\002."), nick.c_str(), newnick.c_str()); + ircdproto->SendGlobops(source.owner, "%s used SVSNICK to change %s to %s", u->nick.c_str(), nick.c_str(), newnick.c_str()); + ircdproto->SendForceNickChange(u2, newnick, Anope::CurTime); + } + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Forcefully changes a user's nickname from nick to newnick.")); + return true; + } +}; + +class OSSVSNick : public Module +{ + CommandOSSVSNick commandossvsnick; + + public: + OSSVSNick(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandossvsnick(this) + { + this->SetAuthor("Anope"); + + if (!ircd || !ircd->svsnick) + throw ModuleException("Your IRCd does not support SVSNICK"); + + ModuleManager::RegisterService(&commandossvsnick); + } +}; + +MODULE_INIT(OSSVSNick) diff --git a/modules/commands/os_sxline.cpp b/modules/commands/os_sxline.cpp new file mode 100644 index 000000000..f949cb7d2 --- /dev/null +++ b/modules/commands/os_sxline.cpp @@ -0,0 +1,807 @@ +/* OperServ core 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 "module.h" + +class SXLineDelCallback : public NumberList +{ + XLineManager *xlm; + Command *command; + CommandSource &source; + unsigned Deleted; + public: + SXLineDelCallback(XLineManager *x, Command *c, CommandSource &_source, const Anope::string &numlist) : NumberList(numlist, true), xlm(x), command(c), source(_source), Deleted(0) + { + } + + ~SXLineDelCallback() + { + if (!Deleted) + source.Reply(_("No matching entries on the %s list."), this->command->name.c_str()); + else if (Deleted == 1) + source.Reply(_("Deleted 1 entry from the %s list."), this->command->name.c_str()); + else + source.Reply(_("Deleted %d entries from the %s list."), Deleted, this->command->name.c_str()); + } + + void HandleNumber(unsigned Number) + { + if (!Number) + return; + + XLine *x = this->xlm->GetEntry(Number - 1); + + if (!x) + return; + + ++Deleted; + DoDel(this->xlm, source, x); + } + + static void DoDel(XLineManager *xlm, CommandSource &source, XLine *x) + { + xlm->DelXLine(x); + } +}; + +class SXLineListCallback : public NumberList +{ + protected: + XLineManager *xlm; + Command *command; + CommandSource &source; + bool SentHeader; + public: + SXLineListCallback(XLineManager *x, Command *c, CommandSource &_source, const Anope::string &numlist) : NumberList(numlist, false), xlm(x), command(c), source(_source), SentHeader(false) + { + } + + ~SXLineListCallback() + { + if (!SentHeader) + source.Reply(_("No matching entries on the %s list."), this->command->name.c_str()); + } + + virtual void HandleNumber(unsigned Number) + { + if (!Number) + return; + + XLine *x = this->xlm->GetEntry(Number - 1); + + if (!x) + return; + + if (!SentHeader) + { + SentHeader = true; + source.Reply(_("Current %s list:\n Num Mask Reason"), this->command->name.c_str()); + } + + DoList(source, x, Number - 1); + } + + static void DoList(CommandSource &source, XLine *x, unsigned Number) + { + source.Reply(OPER_LIST_FORMAT, Number + 1, x->Mask.c_str(), x->Reason.c_str()); + } +}; + +class SXLineViewCallback : public SXLineListCallback +{ + public: + SXLineViewCallback(XLineManager *x, Command *c, CommandSource &_source, const Anope::string &numlist) : SXLineListCallback(x, c, _source, numlist) + { + } + + void HandleNumber(unsigned Number) + { + if (!Number) + return; + + XLine *x = this->xlm->GetEntry(Number - 1); + + if (!x) + return; + + if (!SentHeader) + { + SentHeader = true; + source.Reply(_("Current %s list:"), this->command->name.c_str()); + } + + DoList(source, x, Number - 1); + } + + static void DoList(CommandSource &source, XLine *x, unsigned Number) + { + Anope::string expirebuf = expire_left(source.u->Account(), x->Expires); + source.Reply(OPER_VIEW_FORMAT, Number + 1, x->Mask.c_str(), x->By.c_str(), do_strftime(x->Created).c_str(), expirebuf.c_str(), x->Reason.c_str()); + } +}; + +class CommandOSSXLineBase : public Command +{ + private: + virtual XLineManager* xlm() = 0; + + virtual void OnAdd(CommandSource &source, const std::vector<Anope::string> ¶ms) = 0; + + void OnDel(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + User *u = source.u; + + if (!this->xlm() || this->xlm()->GetList().empty()) + { + source.Reply(_("%s list is empty."), this->name.c_str()); + return; + } + + const Anope::string &mask = params.size() > 1 ? params[1] : ""; + + if (mask.empty()) + { + this->OnSyntaxError(source, "DEL"); + return; + } + + if (isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos) + { + SXLineDelCallback list(this->xlm(), this, source, mask); + list.Process(); + } + else + { + XLine *x = this->xlm()->HasEntry(mask); + + if (!x) + { + source.Reply(_("\002%s\002 not found on the %s list."), mask.c_str(), this->name.c_str()); + return; + } + + FOREACH_MOD(I_OnDelXLine, OnDelXLine(u, x, this->xlm())); + + SXLineDelCallback::DoDel(this->xlm(), source, x); + source.Reply(_("\002%s\002 deleted from the %s list."), mask.c_str(), this->name.c_str()); + } + + if (readonly) + source.Reply(READ_ONLY_MODE); + + return; + } + + void OnList(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + if (!this->xlm() || this->xlm()->GetList().empty()) + { + source.Reply(_("%s list is empty."), this->name.c_str()); + return; + } + + const Anope::string &mask = params.size() > 1 ? params[1] : ""; + + if (!mask.empty() && isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos) + { + SXLineListCallback list(this->xlm(), this, source, mask); + list.Process(); + } + else + { + bool SentHeader = false; + + for (unsigned i = 0, end = this->xlm()->GetCount(); i < end; ++i) + { + XLine *x = this->xlm()->GetEntry(i); + + if (mask.empty() || mask.equals_ci(x->Mask) || Anope::Match(x->Mask, mask)) + { + if (!SentHeader) + { + SentHeader = true; + source.Reply(_("Current %s list:\n Num Mask Reason"), this->name.c_str()); + } + + SXLineListCallback::DoList(source, x, i); + } + } + + if (!SentHeader) + source.Reply(_("No matching entries on the %s list."), this->name.c_str()); + else + source.Reply(END_OF_ANY_LIST, this->name.c_str()); + } + + return; + } + + void OnView(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + if (!this->xlm () || this->xlm()->GetList().empty()) + { + source.Reply(_("%s list is empty."), this->name.c_str()); + return; + } + + const Anope::string &mask = params.size() > 1 ? params[1] : ""; + + if (!mask.empty() && isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos) + { + SXLineViewCallback list(this->xlm(), this, source, mask); + list.Process(); + } + else + { + bool SentHeader = false; + + for (unsigned i = 0, end = this->xlm()->GetCount(); i < end; ++i) + { + XLine *x = this->xlm()->GetEntry(i); + + if (mask.empty() || mask.equals_ci(x->Mask) || Anope::Match(x->Mask, mask)) + { + if (!SentHeader) + { + SentHeader = true; + source.Reply(_("Current %s list:"), this->name.c_str()); + } + + SXLineViewCallback::DoList(source, x, i); + } + } + + if (!SentHeader) + source.Reply(_("No matching entries on the %s list."), this->name.c_str()); + } + + return; + } + + void OnClear(CommandSource &source) + { + User *u = source.u; + FOREACH_MOD(I_OnDelXLine, OnDelXLine(u, NULL, this->xlm())); + this->xlm()->Clear(); + source.Reply(_("The %s list has been cleared."), this->name.c_str()); + + return; + } + public: + CommandOSSXLineBase(Module *creator, const Anope::string &cmd) : Command(creator, cmd, 1, 3) + { + this->SetDesc(Anope::printf(_("Manipulate the %s list"), cmd.c_str())); + this->SetSyntax(_("ADD [+\037expiry\037] \037mask\037:\037reason\037")); + this->SetSyntax(_("DEL {\037mask\037 | \037entry-num\037 | \037list\037}")); + this->SetSyntax(_("LIST [\037mask\037 | \037list\037]")); + this->SetSyntax(_("VIEW [\037mask\037 | \037list\037]")); + this->SetSyntax(_("CLEAR")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + const Anope::string &cmd = params[0]; + + if (cmd.equals_ci("ADD")) + return this->OnAdd(source, params); + else if (cmd.equals_ci("DEL")) + return this->OnDel(source, params); + else if (cmd.equals_ci("LIST")) + return this->OnList(source, params); + else if (cmd.equals_ci("VIEW")) + return this->OnView(source, params); + else if (cmd.equals_ci("CLEAR")) + return this->OnClear(source); + else + this->OnSyntaxError(source, ""); + + return; + } + + virtual bool OnHelp(CommandSource &source, const Anope::string &subcommand) = 0; +}; + +class CommandOSSNLine : public CommandOSSXLineBase +{ + XLineManager *xlm() + { + return this->snlines; + } + + void OnAdd(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + if (!this->xlm()) + return; + + User *u = source.u; + unsigned last_param = 2; + Anope::string param, expiry; + time_t expires; + + param = params.size() > 1 ? params[1] : ""; + if (!param.empty() && param[0] == '+') + { + expiry = param; + param = params.size() > 2 ? params[2] : ""; + last_param = 3; + } + + expires = !expiry.empty() ? dotime(expiry) : Config->SNLineExpiry; + /* If the expiry given does not contain a final letter, it's in days, + * said the doc. Ah well. + */ + if (!expiry.empty() && isdigit(expiry[expiry.length() - 1])) + expires *= 86400; + /* Do not allow less than a minute expiry time */ + if (expires && expires < 60) + { + source.Reply(BAD_EXPIRY_TIME); + return; + } + else if (expires > 0) + expires += Anope::CurTime; + + if (param.empty()) + { + this->OnSyntaxError(source, "ADD"); + return; + } + + Anope::string rest = param; + if (params.size() > last_param) + rest += " " + params[last_param]; + + if (rest.find(':') == Anope::string::npos) + { + this->OnSyntaxError(source, "ADD"); + return; + } + + sepstream sep(rest, ':'); + Anope::string mask; + sep.GetToken(mask); + Anope::string reason = sep.GetRemaining(); + + if (!mask.empty() && !reason.empty()) + { + std::pair<int, XLine *> canAdd = this->xlm()->CanAdd(mask, expires); + if (mask.find_first_not_of("*?") == Anope::string::npos) + source.Reply(USERHOST_MASK_TOO_WIDE, mask.c_str()); + else if (canAdd.first == 1) + source.Reply(_("\002%s\002 already exists on the %s list."), canAdd.second->Mask.c_str(), this->name.c_str()); + else if (canAdd.first == 2) + source.Reply(_("Expiry time of \002%s\002 changed."), canAdd.second->Mask.c_str()); + else if (canAdd.first == 3) + source.Reply(_("\002%s\002 is already covered by %s."), mask.c_str(), canAdd.second->Mask.c_str()); + else + { + /* Clean up the last character of the mask if it is a space + * See bug #761 + */ + unsigned masklen = mask.length(); + if (mask[masklen - 1] == ' ') + mask.erase(masklen - 1); + unsigned int affected = 0; + for (Anope::insensitive_map<User *>::iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it) + if (Anope::Match(it->second->realname, mask)) + ++affected; + float percent = static_cast<float>(affected) / static_cast<float>(UserListByNick.size()) * 100.0; + + if (percent > 95) + { + source.Reply(USERHOST_MASK_TOO_WIDE, mask.c_str()); + Log(LOG_ADMIN, u, this) << "tried to " << this->name << " " << percent << "% of the network (" << affected << " users)"; + return; + } + + XLine *x = this->xlm()->Add(mask, u->nick, expires, reason); + + EventReturn MOD_RESULT; + FOREACH_RESULT(I_OnAddXLine, OnAddXLine(u, x, this->xlm())); + if (MOD_RESULT == EVENT_STOP) + { + delete x; + return; + } + + source.Reply(_("\002%s\002 added to the %s list."), mask.c_str(), this->name.c_str()); + Log(LOG_ADMIN, u, this) << "on " << mask << " (" << reason << ") expires in " << (expires ? duration(expires - Anope::CurTime) : "never") << " [affects " << affected << " user(s) (" << percent << "%)]"; + + if (readonly) + source.Reply(READ_ONLY_MODE); + } + + } + else + this->OnSyntaxError(source, "ADD"); + + return; + } + + service_reference<XLineManager> snlines; + public: + CommandOSSNLine(Module *creator) : CommandOSSXLineBase(creator, "operserv/snline"), snlines("xlinemanager/snline") + { + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Allows Services operators to manipulate the SNLINE list. If\n" + "a user with a realname matching an SNLINE mask attempts to \n" + "connect, Services will not allow it to pursue his IRC\n" + "session.\n")); + source.Reply(_(" \n" + "\002SNLINE ADD\002 adds the given realname mask to the SNLINE\n" + "list for the given reason (which \002must\002 be given).\n" + "\037expiry\037 is specified as an integer followed by one of \037d\037 \n" + "(days), \037h\037 (hours), or \037m\037 (minutes). Combinations (such as \n" + "\0371h30m\037) are not permitted. If a unit specifier is not \n" + "included, the default is days (so \037+30\037 by itself means 30 \n" + "days). To add an SNLINE which does not expire, use \037+0\037. If the\n" + "realname mask to be added starts with a \037+\037, an expiry time must\n" + "be given, even if it is the same as the default. The\n" + "current SNLINE default expiry time can be found with the\n" + "\002STATS AKILL\002 command.\n" + "Note: because the realname mask may contain spaces, the\n" + "separator between it and the reason is a colon.\n")); + source.Reply(_(" \n" + "The \002SNLINE DEL\002 command removes the given mask from the\n" + "SNLINE list if it is present. If a list of entry numbers is \n" + "given, those entries are deleted. (See the example for LIST \n" + "below.)\n" + " \n" + "The \002SNLINE LIST\002 command displays the SNLINE list. \n" + "If a wildcard mask is given, only those entries matching the\n" + "mask are displayed. If a list of entry numbers is given,\n" + "only those entries are shown; for example:\n" + " \002SNLINE LIST 2-5,7-9\002\n" + " Lists SNLINE entries numbered 2 through 5 and 7 \n" + " through 9.\n" + " \n" + "\002SNLINE VIEW\002 is a more verbose version of \002SNLINE LIST\002, and \n" + "will show who added an SNLINE, the date it was added, and when \n" + "it expires, as well as the realname mask and reason.\n" + " \n" + "\002SNLINE CLEAR\002 clears all entries of the SNLINE list.")); + return true; + } +}; + +class CommandOSSQLine : public CommandOSSXLineBase +{ + XLineManager *xlm() + { + return this->sqlines; + } + + void OnAdd(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + if (!this->xlm()) + return; + + User *u = source.u; + unsigned last_param = 2; + Anope::string expiry, mask; + time_t expires; + + mask = params.size() > 1 ? params[1] : ""; + if (!mask.empty() && mask[0] == '+') + { + expiry = mask; + mask = params.size() > 2 ? params[2] : ""; + last_param = 3; + } + + expires = !expiry.empty() ? dotime(expiry) : Config->SQLineExpiry; + /* If the expiry given does not contain a final letter, it's in days, + * said the doc. Ah well. + */ + if (!expiry.empty() && isdigit(expiry[expiry.length() - 1])) + expires *= 86400; + /* Do not allow less than a minute expiry time */ + if (expires && expires < 60) + { + source.Reply(BAD_EXPIRY_TIME); + return; + } + else if (expires > 0) + expires += Anope::CurTime; + + if (params.size() <= last_param) + { + this->OnSyntaxError(source, "ADD"); + return; + } + + Anope::string reason = params[last_param]; + if (last_param == 2 && params.size() > 3) + reason += " " + params[3]; + if (!mask.empty() && !reason.empty()) + { + std::pair<int, XLine *> canAdd = this->sqlines->CanAdd(mask, expires); + if (mask.find_first_not_of("*") == Anope::string::npos) + source.Reply(USERHOST_MASK_TOO_WIDE, mask.c_str()); + else if (canAdd.first == 1) + source.Reply(_("\002%s\002 already exists on the SQLINE list."), canAdd.second->Mask.c_str()); + else if (canAdd.first == 2) + source.Reply(_("Expiry time of \002%s\002 changed."), canAdd.second->Mask.c_str()); + else if (canAdd.first == 3) + source.Reply(_("\002%s\002 is already covered by %s."), mask.c_str(), canAdd.second->Mask.c_str()); + else + { + unsigned int affected = 0; + for (Anope::insensitive_map<User *>::iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it) + if (Anope::Match(it->second->nick, mask)) + ++affected; + float percent = static_cast<float>(affected) / static_cast<float>(UserListByNick.size()) * 100.0; + + if (percent > 95) + { + source.Reply(USERHOST_MASK_TOO_WIDE, mask.c_str()); + Log(LOG_ADMIN, u, this) << "tried to SQLine " << percent << "% of the network (" << affected << " users)"; + return; + } + + XLine *x = this->sqlines->Add(mask, u->nick, expires, reason); + + EventReturn MOD_RESULT; + FOREACH_RESULT(I_OnAddXLine, OnAddXLine(u, x, this->xlm())); + if (MOD_RESULT == EVENT_STOP) + { + delete x; + return; + } + + source.Reply(_("\002%s\002 added to the SQLINE list."), mask.c_str()); + Log(LOG_ADMIN, u, this) << "on " << mask << " (" << reason << ") expires in " << (expires ? duration(expires - Anope::CurTime) : "never") << " [affects " << affected << " user(s) (" << percent << "%)]"; + + if (readonly) + source.Reply(READ_ONLY_MODE); + } + + } + else + this->OnSyntaxError(source, "ADD"); + + return; + } + + service_reference<XLineManager> sqlines; + public: + CommandOSSQLine(Module *creator) : CommandOSSXLineBase(creator, "operserv/sqline"), sqlines("xlinemanager/sqline") + { + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Allows Services operators to manipulate the SQLINE list. If\n" + "a user with a nick matching an SQLINE mask attempts to \n" + "connect, Services will not allow it to pursue his IRC\n" + "session.\n" + "If the first character of the mask is #, services will \n" + "prevent the use of matching channels (on IRCds that \n" + "support it).\n")); + source.Reply(_(" \n" + "\002SQLINE ADD\002 adds the given (nick's) mask to the SQLINE\n" + "list for the given reason (which \002must\002 be given).\n" + "\037expiry\037 is specified as an integer followed by one of \037d\037 \n" + "(days), \037h\037 (hours), or \037m\037 (minutes). Combinations (such as \n" + "\0371h30m\037) are not permitted. If a unit specifier is not \n" + "included, the default is days (so \037+30\037 by itself means 30 \n" + "days). To add an SQLINE which does not expire, use \037+0\037. \n" + "If the mask to be added starts with a \037+\037, an expiry time \n" + "must be given, even if it is the same as the default. The\n" + "current SQLINE default expiry time can be found with the\n" + "\002STATS AKILL\002 command.\n")); + source.Reply(_(" \n" + "The \002SQLINE DEL\002 command removes the given mask from the\n" + "SQLINE list if it is present. If a list of entry numbers is \n" + "given, those entries are deleted. (See the example for LIST \n" + "below.)\n" + " \n" + "The \002SQLINE LIST\002 command displays the SQLINE list. \n" + "If a wildcard mask is given, only those entries matching the\n" + "mask are displayed. If a list of entry numbers is given,\n" + "only those entries are shown; for example:\n" + " \002SQLINE LIST 2-5,7-9\002\n" + " Lists SQLINE entries numbered 2 through 5 and 7 \n" + " through 9.\n" + " \n" + "\002SQLINE VIEW\002 is a more verbose version of \002SQLINE LIST\002, and \n" + "will show who added an SQLINE, the date it was added, and when \n" + "it expires, as well as the mask and reason.\n" + " \n" + "\002SQLINE CLEAR\002 clears all entries of the SQLINE list.")); + return true; + } +}; + +class CommandOSSZLine : public CommandOSSXLineBase +{ + XLineManager *xlm() + { + return this->szlines; + } + + void OnAdd(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + if (!this->xlm()) + return; + + User *u = source.u; + unsigned last_param = 2; + Anope::string expiry, mask; + time_t expires; + + mask = params.size() > 1 ? params[1] : ""; + if (!mask.empty() && mask[0] == '+') + { + expiry = mask; + mask = params.size() > 2 ? params[2] : ""; + last_param = 3; + } + + expires = !expiry.empty() ? dotime(expiry) : Config->SZLineExpiry; + /* If the expiry given does not contain a final letter, it's in days, + * said the doc. Ah well. + */ + if (!expiry.empty() && isdigit(expiry[expiry.length() - 1])) + expires *= 86400; + /* Do not allow less than a minute expiry time */ + if (expires && expires < 60) + { + source.Reply(BAD_EXPIRY_TIME); + return; + } + else if (expires > 0) + expires += Anope::CurTime; + + if (params.size() <= last_param) + { + this->OnSyntaxError(source, "ADD"); + return; + } + + Anope::string reason = params[last_param]; + if (last_param == 2 && params.size() > 3) + reason += " " + params[3]; + if (!mask.empty() && !reason.empty()) + { + std::pair<int, XLine *> canAdd = this->szlines->CanAdd(mask, expires); + if (mask.find('!') != Anope::string::npos || mask.find('@') != Anope::string::npos) + source.Reply(_("You can only add IP masks to the SZLINE list.")); + else if (mask.find_first_not_of("*?") == Anope::string::npos) + source.Reply(USERHOST_MASK_TOO_WIDE, mask.c_str()); + else if (canAdd.first == 1) + source.Reply(_("\002%s\002 already exists on the SZLINE list."), canAdd.second->Mask.c_str()); + else if (canAdd.first == 2) + source.Reply(_("Expiry time of \002%s\002 changed."), canAdd.second->Mask.c_str()); + else if (canAdd.first == 3) + source.Reply(_("\002%s\002 is already covered by %s."), mask.c_str(), canAdd.second->Mask.c_str()); + else + { + User *user = finduser(mask); + if (user && user->ip()) + mask = user->ip.addr(); + unsigned int affected = 0; + for (Anope::insensitive_map<User *>::iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it) + if (it->second->ip() && Anope::Match(it->second->ip.addr(), mask)) + ++affected; + float percent = static_cast<float>(affected) / static_cast<float>(UserListByNick.size()) * 100.0; + + if (percent > 95) + { + source.Reply(USERHOST_MASK_TOO_WIDE, mask.c_str()); + Log(LOG_ADMIN, u, this) << "tried to SZLine " << percent << "% of the network (" << affected << " users)"; + return; + } + + XLine *x = this->szlines->Add(mask, u->nick, expires, reason); + + EventReturn MOD_RESULT; + FOREACH_RESULT(I_OnAddXLine, OnAddXLine(u, x, this->xlm())); + if (MOD_RESULT == EVENT_STOP) + { + delete x; + return; + } + + source.Reply(_("\002%s\002 added to the SZLINE list."), mask.c_str()); + Log(LOG_ADMIN, u, this) << "on " << mask << " (" << reason << ") expires in " << (expires ? duration(expires - Anope::CurTime) : "never") << " [affects " << affected << " user(s) (" << percent << "%)]"; + + if (readonly) + source.Reply(READ_ONLY_MODE); + } + + } + else + this->OnSyntaxError(source, "ADD"); + + return; + } + + service_reference<XLineManager> szlines; + public: + CommandOSSZLine(Module *creator) : CommandOSSXLineBase(creator, "operserv/szline"), szlines("xlinemanager/szline") + { + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Allows Services operators to manipulate the SZLINE list. If\n" + "a user with an IP matching an SZLINE mask attempts to \n" + "connect, Services will not allow it to pursue his IRC\n" + "session (and this, whether the IP has a PTR RR or not).\n" + " \n")); + source.Reply(_("\002SZLINE ADD\002 adds the given (nick's) IP mask to the SZLINE\n" + "list for the given reason (which \002must\002 be given).\n" + "\037expiry\037 is specified as an integer followed by one of \037d\037 \n" + "(days), \037h\037 (hours), or \037m\037 (minutes). Combinations (such as \n" + "\0371h30m\037) are not permitted. If a unit specifier is not \n" + "included, the default is days (so \037+30\037 by itself means 30 \n" + "days). To add an SZLINE which does not expire, use \037+0\037. If the\n" + "realname mask to be added starts with a \037+\037, an expiry time must\n" + "be given, even if it is the same as the default. The\n" + "current SZLINE default expiry time can be found with the\n" + "\002STATS AKILL\002 command.\n")); + source.Reply(_(" \n" + "The \002SZLINE DEL\002 command removes the given mask from the\n" + "SZLINE list if it is present. If a list of entry numbers is \n" + "given, those entries are deleted. (See the example for LIST \n" + "below.)\n" + " \n" + "The \002SZLINE LIST\002 command displays the SZLINE list.\n" + "If a wildcard mask is given, only those entries matching the\n" + "mask are displayed. If a list of entry numbers is given,\n" + "only those entries are shown; for example:\n" + " \002SZLINE LIST 2-5,7-9\002\n" + " Lists SZLINE entries numbered 2 through 5 and 7 \n" + " through 9.\n" + " \n" + "\002SZLINE VIEW\002 is a more verbose version of \002SZLINE LIST\002, and \n" + "will show who added an SZLINE, the date it was added, and when\n" + "it expires, as well as the IP mask and reason.\n" + " \n" + "\002SZLINE CLEAR\002 clears all entries of the SZLINE list.")); + return true; + } +}; + +class OSSXLine : public Module +{ + CommandOSSNLine commandossnline; + CommandOSSQLine commandossqline; + CommandOSSZLine commandosszline; + + public: + OSSXLine(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandossnline(this), commandossqline(this), commandosszline(this) + { + this->SetAuthor("Anope"); + + if (ircd && ircd->snline) + ModuleManager::RegisterService(&commandossnline); + if (ircd && ircd->sqline) + ModuleManager::RegisterService(&commandossqline); + if (ircd && ircd->szline) + ModuleManager::RegisterService(&commandosszline); + } +}; + +MODULE_INIT(OSSXLine) diff --git a/modules/commands/os_update.cpp b/modules/commands/os_update.cpp new file mode 100644 index 000000000..776835cf7 --- /dev/null +++ b/modules/commands/os_update.cpp @@ -0,0 +1,56 @@ +/* OperServ core 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 "module.h" + +class CommandOSUpdate : public Command +{ + public: + CommandOSUpdate(Module *creator) : Command(creator, "operserv/update", 0, 0) + { + this->SetDesc(_("Force the Services databases to be updated immediately")); + this->SetSyntax(""); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) + { + source.Reply(_("Updating databases.")); + save_databases(); + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Causes Services to update all database files as soon as you\n" + "send the command.")); + return true; + } +}; + +class OSUpdate : public Module +{ + CommandOSUpdate commandosupdate; + + public: + OSUpdate(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + commandosupdate(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&commandosupdate); + } +}; + +MODULE_INIT(OSUpdate) |