summaryrefslogtreecommitdiff
path: root/modules/commands
diff options
context:
space:
mode:
Diffstat (limited to 'modules/commands')
-rw-r--r--modules/commands/bs_assign.cpp168
-rw-r--r--modules/commands/bs_badwords.cpp331
-rw-r--r--modules/commands/bs_bot.cpp414
-rw-r--r--modules/commands/bs_botlist.cpp91
-rw-r--r--modules/commands/bs_control.cpp158
-rw-r--r--modules/commands/bs_info.cpp244
-rw-r--r--modules/commands/bs_kick.cpp1011
-rw-r--r--modules/commands/bs_set.cpp307
-rw-r--r--modules/commands/cs_access.cpp915
-rw-r--r--modules/commands/cs_akick.cpp564
-rw-r--r--modules/commands/cs_appendtopic.cpp117
-rw-r--r--modules/commands/cs_ban.cpp109
-rw-r--r--modules/commands/cs_clearusers.cpp83
-rw-r--r--modules/commands/cs_clone.cpp186
-rw-r--r--modules/commands/cs_drop.cpp102
-rw-r--r--modules/commands/cs_enforce.cpp215
-rw-r--r--modules/commands/cs_entrymsg.cpp224
-rw-r--r--modules/commands/cs_flags.cpp455
-rw-r--r--modules/commands/cs_getkey.cpp80
-rw-r--r--modules/commands/cs_info.cpp142
-rw-r--r--modules/commands/cs_invite.cpp102
-rw-r--r--modules/commands/cs_kick.cpp91
-rw-r--r--modules/commands/cs_list.cpp136
-rw-r--r--modules/commands/cs_mode.cpp364
-rw-r--r--modules/commands/cs_modes.cpp431
-rw-r--r--modules/commands/cs_register.cpp193
-rw-r--r--modules/commands/cs_saset.cpp74
-rw-r--r--modules/commands/cs_saset_noexpire.cpp81
-rw-r--r--modules/commands/cs_set.cpp74
-rw-r--r--modules/commands/cs_set_bantype.cpp98
-rw-r--r--modules/commands/cs_set_description.cpp89
-rw-r--r--modules/commands/cs_set_founder.cpp105
-rw-r--r--modules/commands/cs_set_keeptopic.cpp94
-rw-r--r--modules/commands/cs_set_misc.cpp110
-rw-r--r--modules/commands/cs_set_opnotice.cpp93
-rw-r--r--modules/commands/cs_set_peace.cpp93
-rw-r--r--modules/commands/cs_set_persist.cpp180
-rw-r--r--modules/commands/cs_set_private.cpp93
-rw-r--r--modules/commands/cs_set_restricted.cpp91
-rw-r--r--modules/commands/cs_set_secure.cpp94
-rw-r--r--modules/commands/cs_set_securefounder.cpp95
-rw-r--r--modules/commands/cs_set_secureops.cpp92
-rw-r--r--modules/commands/cs_set_signkick.cpp104
-rw-r--r--modules/commands/cs_set_successor.cpp119
-rw-r--r--modules/commands/cs_set_topiclock.cpp92
-rw-r--r--modules/commands/cs_suspend.cpp158
-rw-r--r--modules/commands/cs_sync.cpp63
-rw-r--r--modules/commands/cs_tban.cpp116
-rw-r--r--modules/commands/cs_topic.cpp84
-rw-r--r--modules/commands/cs_unban.cpp94
-rw-r--r--modules/commands/cs_xop.cpp880
-rw-r--r--modules/commands/gl_global.cpp64
-rw-r--r--modules/commands/help.cpp130
-rw-r--r--modules/commands/hs_del.cpp107
-rw-r--r--modules/commands/hs_group.cpp80
-rw-r--r--modules/commands/hs_list.cpp137
-rw-r--r--modules/commands/hs_off.cpp67
-rw-r--r--modules/commands/hs_on.cpp77
-rw-r--r--modules/commands/hs_request.cpp424
-rw-r--r--modules/commands/hs_set.cpp236
-rw-r--r--modules/commands/ms_cancel.cpp77
-rw-r--r--modules/commands/ms_check.cpp89
-rw-r--r--modules/commands/ms_del.cpp163
-rw-r--r--modules/commands/ms_ignore.cpp110
-rw-r--r--modules/commands/ms_info.cpp219
-rw-r--r--modules/commands/ms_list.cpp168
-rw-r--r--modules/commands/ms_read.cpp195
-rw-r--r--modules/commands/ms_rsend.cpp106
-rw-r--r--modules/commands/ms_send.cpp70
-rw-r--r--modules/commands/ms_sendall.cpp74
-rw-r--r--modules/commands/ms_set.cpp313
-rw-r--r--modules/commands/ms_staff.cpp71
-rw-r--r--modules/commands/ns_access.cpp195
-rw-r--r--modules/commands/ns_ajoin.cpp239
-rw-r--r--modules/commands/ns_alist.cpp96
-rw-r--r--modules/commands/ns_cert.cpp231
-rw-r--r--modules/commands/ns_drop.cpp130
-rw-r--r--modules/commands/ns_getemail.cpp82
-rw-r--r--modules/commands/ns_getpass.cpp79
-rw-r--r--modules/commands/ns_ghost.cpp122
-rw-r--r--modules/commands/ns_group.cpp305
-rw-r--r--modules/commands/ns_identify.cpp93
-rw-r--r--modules/commands/ns_info.cpp173
-rw-r--r--modules/commands/ns_list.cpp169
-rw-r--r--modules/commands/ns_logout.cpp97
-rw-r--r--modules/commands/ns_recover.cpp132
-rw-r--r--modules/commands/ns_register.cpp355
-rw-r--r--modules/commands/ns_release.cpp110
-rw-r--r--modules/commands/ns_resetpass.cpp152
-rw-r--r--modules/commands/ns_saset.cpp173
-rw-r--r--modules/commands/ns_saset_noexpire.cpp76
-rw-r--r--modules/commands/ns_sendpass.cpp108
-rw-r--r--modules/commands/ns_set.cpp158
-rw-r--r--modules/commands/ns_set_autoop.cpp107
-rw-r--r--modules/commands/ns_set_email.cpp186
-rw-r--r--modules/commands/ns_set_greet.cpp108
-rw-r--r--modules/commands/ns_set_hide.cpp149
-rw-r--r--modules/commands/ns_set_kill.cpp149
-rw-r--r--modules/commands/ns_set_language.cpp121
-rw-r--r--modules/commands/ns_set_message.cpp114
-rw-r--r--modules/commands/ns_set_misc.cpp126
-rw-r--r--modules/commands/ns_set_private.cpp114
-rw-r--r--modules/commands/ns_set_secure.cpp114
-rw-r--r--modules/commands/ns_status.cpp94
-rw-r--r--modules/commands/ns_suspend.cpp160
-rw-r--r--modules/commands/ns_update.cpp70
-rw-r--r--modules/commands/os_akill.cpp464
-rw-r--r--modules/commands/os_chankill.cpp120
-rw-r--r--modules/commands/os_config.cpp217
-rw-r--r--modules/commands/os_defcon.cpp663
-rw-r--r--modules/commands/os_forbid.cpp342
-rw-r--r--modules/commands/os_forbid.h37
-rw-r--r--modules/commands/os_ignore.cpp350
-rw-r--r--modules/commands/os_ignore.h39
-rw-r--r--modules/commands/os_jupe.cpp79
-rw-r--r--modules/commands/os_kick.cpp83
-rw-r--r--modules/commands/os_kill.cpp72
-rw-r--r--modules/commands/os_list.cpp187
-rw-r--r--modules/commands/os_login.cpp97
-rw-r--r--modules/commands/os_mode.cpp109
-rw-r--r--modules/commands/os_modinfo.cpp245
-rw-r--r--modules/commands/os_module.cpp202
-rw-r--r--modules/commands/os_news.cpp450
-rw-r--r--modules/commands/os_news.h39
-rw-r--r--modules/commands/os_noop.cpp94
-rw-r--r--modules/commands/os_oline.cpp83
-rw-r--r--modules/commands/os_oper.cpp219
-rw-r--r--modules/commands/os_reload.cpp72
-rw-r--r--modules/commands/os_session.cpp749
-rw-r--r--modules/commands/os_session.h32
-rw-r--r--modules/commands/os_set.cpp261
-rw-r--r--modules/commands/os_shutdown.cpp117
-rw-r--r--modules/commands/os_stats.cpp224
-rw-r--r--modules/commands/os_svsnick.cpp92
-rw-r--r--modules/commands/os_sxline.cpp807
-rw-r--r--modules/commands/os_update.cpp56
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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ User *u = source.u;
+ const Anope::string &subcommand = params[2];
+ const Anope::string &param = 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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 &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params, 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> &params, 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> &params, 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> &params, 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> &params) = 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params, MemoInfo *mi)
+ {
+ User *u = source.u;
+ const Anope::string &param = 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> &params, 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params, 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ User *u = source.u;
+
+ const Anope::string &nick = !params.empty() ? params[0] : "";
+ const Anope::string &param = 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ User *u = source.u;
+
+ const Anope::string &param = 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 &param)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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 &param)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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 &param)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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 &param, 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> &params)
+ {
+ 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> &params)
+ {
+ 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 &param)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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 &param)
+ {
+ 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> &param)
+ {
+ 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> &params)
+ {
+ 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 &param)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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 &param)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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 &param)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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 &param)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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 &param)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ const Anope::string &param = !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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params, 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> &params, 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> &params, 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> &params) = 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params) = 0;
+
+ void OnDel(CommandSource &source, const std::vector<Anope::string> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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> &params)
+ {
+ 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)