From c8c23158a4ff74822d6c7d201dc53d879e3d91e8 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 22 Apr 2011 03:16:11 -0400 Subject: Moved the core pseudo clients out into their own modules --- modules/core/botserv.h | 65 +++++ modules/core/bs_act.cpp | 8 +- modules/core/bs_assign.cpp | 6 +- modules/core/bs_badwords.cpp | 8 +- modules/core/bs_bot.cpp | 6 +- modules/core/bs_botlist.cpp | 6 +- modules/core/bs_help.cpp | 14 +- modules/core/bs_info.cpp | 8 +- modules/core/bs_kick.cpp | 301 +++++++++++++++++++-- modules/core/bs_main.cpp | 265 ++++++++++++++++++ modules/core/bs_say.cpp | 8 +- modules/core/bs_set.cpp | 8 +- modules/core/bs_unassign.cpp | 8 +- modules/core/chanserv.h | 15 + modules/core/cs_access.cpp | 14 +- modules/core/cs_akick.cpp | 10 +- modules/core/cs_ban.cpp | 10 +- modules/core/cs_clearusers.cpp | 10 +- modules/core/cs_clone.cpp | 6 +- modules/core/cs_drop.cpp | 8 +- modules/core/cs_forbid.cpp | 8 +- modules/core/cs_getkey.cpp | 6 +- modules/core/cs_help.cpp | 13 +- modules/core/cs_info.cpp | 6 +- modules/core/cs_invite.cpp | 14 +- modules/core/cs_kick.cpp | 10 +- modules/core/cs_list.cpp | 7 +- modules/core/cs_main.cpp | 256 ++++++++++++++++++ modules/core/cs_mode.cpp | 6 +- modules/core/cs_modes.cpp | 39 +-- modules/core/cs_register.cpp | 8 +- modules/core/cs_saset.cpp | 10 +- modules/core/cs_saset_noexpire.cpp | 8 +- modules/core/cs_set.cpp | 10 +- modules/core/cs_set_bantype.cpp | 12 +- modules/core/cs_set_description.cpp | 12 +- modules/core/cs_set_founder.cpp | 12 +- modules/core/cs_set_keeptopic.cpp | 14 +- modules/core/cs_set_opnotice.cpp | 14 +- modules/core/cs_set_peace.cpp | 14 +- modules/core/cs_set_persist.cpp | 20 +- modules/core/cs_set_private.cpp | 14 +- modules/core/cs_set_restricted.cpp | 12 +- modules/core/cs_set_secure.cpp | 16 +- modules/core/cs_set_securefounder.cpp | 12 +- modules/core/cs_set_secureops.cpp | 12 +- modules/core/cs_set_signkick.cpp | 14 +- modules/core/cs_set_successor.cpp | 12 +- modules/core/cs_set_topiclock.cpp | 14 +- modules/core/cs_set_xop.cpp | 12 +- modules/core/cs_status.cpp | 3 +- modules/core/cs_suspend.cpp | 5 +- modules/core/cs_topic.cpp | 5 +- modules/core/cs_unban.cpp | 5 +- modules/core/cs_xop.cpp | 35 +-- modules/core/db_plain.cpp | 40 +-- modules/core/gl_global.cpp | 67 +++++ modules/core/gl_help.cpp | 58 ++++ modules/core/gl_main.cpp | 111 ++++++++ modules/core/global.h | 22 ++ modules/core/hostserv.h | 17 ++ modules/core/hs_del.cpp | 6 +- modules/core/hs_delall.cpp | 6 +- modules/core/hs_group.cpp | 8 +- modules/core/hs_help.cpp | 10 +- modules/core/hs_list.cpp | 6 +- modules/core/hs_main.cpp | 123 +++++++++ modules/core/hs_off.cpp | 6 +- modules/core/hs_on.cpp | 6 +- modules/core/hs_set.cpp | 6 +- modules/core/hs_setall.cpp | 8 +- modules/core/memoserv.h | 44 +++ modules/core/ms_cancel.cpp | 14 +- modules/core/ms_check.cpp | 6 +- modules/core/ms_del.cpp | 6 +- modules/core/ms_help.cpp | 14 +- modules/core/ms_ignore.cpp | 8 +- modules/core/ms_info.cpp | 6 +- modules/core/ms_list.cpp | 6 +- modules/core/ms_main.cpp | 250 +++++++++++++++++ modules/core/ms_read.cpp | 42 ++- modules/core/ms_rsend.cpp | 44 ++- modules/core/ms_send.cpp | 18 +- modules/core/ms_sendall.cpp | 8 +- modules/core/ms_set.cpp | 8 +- modules/core/ms_staff.cpp | 8 +- modules/core/nickserv.h | 17 ++ modules/core/ns_access.cpp | 8 +- modules/core/ns_ajoin.cpp | 10 +- modules/core/ns_alist.cpp | 3 +- modules/core/ns_cert.cpp | 12 +- modules/core/ns_drop.cpp | 10 +- modules/core/ns_forbid.cpp | 8 +- modules/core/ns_getemail.cpp | 6 +- modules/core/ns_getpass.cpp | 12 +- modules/core/ns_ghost.cpp | 8 +- modules/core/ns_group.cpp | 18 +- modules/core/ns_help.cpp | 17 +- modules/core/ns_identify.cpp | 8 +- modules/core/ns_info.cpp | 6 +- modules/core/ns_list.cpp | 7 +- modules/core/ns_logout.cpp | 10 +- modules/core/ns_main.cpp | 282 +++++++++++++++++++ modules/core/ns_recover.cpp | 12 +- modules/core/ns_register.cpp | 23 +- modules/core/ns_release.cpp | 8 +- modules/core/ns_resetpass.cpp | 16 +- modules/core/ns_saset.cpp | 12 +- modules/core/ns_saset_noexpire.cpp | 8 +- modules/core/ns_sendpass.cpp | 14 +- modules/core/ns_set.cpp | 12 +- modules/core/ns_set_autoop.cpp | 12 +- modules/core/ns_set_email.cpp | 16 +- modules/core/ns_set_greet.cpp | 12 +- modules/core/ns_set_hide.cpp | 16 +- modules/core/ns_set_kill.cpp | 16 +- modules/core/ns_set_language.cpp | 12 +- modules/core/ns_set_message.cpp | 12 +- modules/core/ns_set_private.cpp | 18 +- modules/core/ns_set_secure.cpp | 16 +- modules/core/ns_status.cpp | 6 +- modules/core/ns_suspend.cpp | 8 +- modules/core/ns_update.cpp | 17 +- modules/core/operserv.h | 15 + modules/core/os_akill.cpp | 64 +++-- modules/core/os_chankill.cpp | 8 +- modules/core/os_chanlist.cpp | 8 +- modules/core/os_config.cpp | 6 +- modules/core/os_defcon.cpp | 496 ---------------------------------- modules/core/os_global.cpp | 67 ----- modules/core/os_help.cpp | 14 +- modules/core/os_ignore.cpp | 6 +- modules/core/os_jupe.cpp | 6 +- modules/core/os_kick.cpp | 10 +- modules/core/os_login.cpp | 12 +- modules/core/os_main.cpp | 137 ++++++++++ modules/core/os_mode.cpp | 12 +- modules/core/os_modinfo.cpp | 24 +- modules/core/os_modlist.cpp | 6 +- modules/core/os_modload.cpp | 8 +- modules/core/os_modreload.cpp | 8 +- modules/core/os_modunload.cpp | 8 +- modules/core/os_news.cpp | 22 +- modules/core/os_noop.cpp | 6 +- modules/core/os_oline.cpp | 14 +- modules/core/os_quit.cpp | 9 +- modules/core/os_reload.cpp | 16 +- modules/core/os_restart.cpp | 9 +- modules/core/os_session.cpp | 234 +++++++++++++--- modules/core/os_session.h | 80 ++++++ modules/core/os_set.cpp | 12 +- modules/core/os_shutdown.cpp | 9 +- modules/core/os_snline.cpp | 67 +++-- modules/core/os_sqline.cpp | 54 ++-- modules/core/os_staff.cpp | 6 +- modules/core/os_stats.cpp | 160 ++--------- modules/core/os_svsnick.cpp | 10 +- modules/core/os_szline.cpp | 62 +++-- modules/core/os_update.cpp | 6 +- modules/core/os_userlist.cpp | 6 +- modules/core/ss_main.cpp | 65 ----- 161 files changed, 3489 insertions(+), 1417 deletions(-) create mode 100644 modules/core/botserv.h create mode 100644 modules/core/bs_main.cpp create mode 100644 modules/core/chanserv.h create mode 100644 modules/core/cs_main.cpp create mode 100644 modules/core/gl_global.cpp create mode 100644 modules/core/gl_help.cpp create mode 100644 modules/core/gl_main.cpp create mode 100644 modules/core/global.h create mode 100644 modules/core/hostserv.h create mode 100644 modules/core/hs_main.cpp create mode 100644 modules/core/memoserv.h create mode 100644 modules/core/ms_main.cpp create mode 100644 modules/core/nickserv.h create mode 100644 modules/core/ns_main.cpp create mode 100644 modules/core/operserv.h delete mode 100644 modules/core/os_defcon.cpp delete mode 100644 modules/core/os_global.cpp create mode 100644 modules/core/os_main.cpp create mode 100644 modules/core/os_session.h delete mode 100644 modules/core/ss_main.cpp (limited to 'modules/core') diff --git a/modules/core/botserv.h b/modules/core/botserv.h new file mode 100644 index 000000000..5c707278a --- /dev/null +++ b/modules/core/botserv.h @@ -0,0 +1,65 @@ +#ifndef BOTSERV_H +#define BOTSERV_H + +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; +}; + +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; + } +}; + +class BotServService : public Service +{ + public: + BotServService(Module *m) : Service(m, "BotServ") { } + + virtual BotInfo *Bot() = 0; + + virtual UserData *GetUserData(User *u, Channel *c) = 0; + + virtual BanData *GetBanData(User *u, Channel *c) = 0; +}; + +static service_reference botserv("BotServ"); + +#endif // BOTSERV_H + diff --git a/modules/core/bs_act.cpp b/modules/core/bs_act.cpp index 3e868c25a..2547ce3c1 100644 --- a/modules/core/bs_act.cpp +++ b/modules/core/bs_act.cpp @@ -12,6 +12,7 @@ /*************************************************************************/ #include "module.h" +#include "botserv.h" class CommandBSAct : public Command { @@ -35,7 +36,7 @@ class CommandBSAct : public Command if (!ci->bi) { - source.Reply(_(BOT_NOT_ASSIGNED), Config->UseStrictPrivMsgString.c_str(), BotServ->nick.c_str()); + source.Reply(_(BOT_NOT_ASSIGNED), Config->UseStrictPrivMsgString.c_str(), Config->s_BotServ.c_str()); return MOD_CONT; } @@ -83,7 +84,10 @@ class BSAct : public Module this->SetAuthor("Anope"); this->SetType(CORE); - this->AddCommand(BotServ, &commandbsact); + if (!botserv) + throw ModuleException("BotServ is not loaded!"); + + this->AddCommand(botserv->Bot(), &commandbsact); } }; diff --git a/modules/core/bs_assign.cpp b/modules/core/bs_assign.cpp index f1000cb11..5be9e6bf3 100644 --- a/modules/core/bs_assign.cpp +++ b/modules/core/bs_assign.cpp @@ -12,6 +12,7 @@ /*************************************************************************/ #include "module.h" +#include "botserv.h" class CommandBSAssign : public Command { @@ -93,7 +94,10 @@ class BSAssign : public Module this->SetAuthor("Anope"); this->SetType(CORE); - this->AddCommand(BotServ, &commandbsassign); + if (!botserv) + throw ModuleException("BotServ is not loaded!"); + + this->AddCommand(botserv->Bot(), &commandbsassign); } }; diff --git a/modules/core/bs_badwords.cpp b/modules/core/bs_badwords.cpp index 5acbf913f..1b3b2a884 100644 --- a/modules/core/bs_badwords.cpp +++ b/modules/core/bs_badwords.cpp @@ -12,6 +12,7 @@ /*************************************************************************/ #include "module.h" +#include "botserv.h" class BadwordsListCallback : public NumberList { @@ -291,7 +292,7 @@ class CommandBSBadwords : public Command "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(), BotServ->nick.c_str()); + " \n"), Config->UseStrictPrivMsgString.c_str(), Config->s_BotServ.c_str()); source.Reply(_("The \002BADWORDS DEL\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" @@ -325,7 +326,10 @@ class BSBadwords : public Module this->SetAuthor("Anope"); this->SetType(CORE); - this->AddCommand(BotServ, &commandbsbadwords); + if (!botserv) + throw ModuleException("BotServ is not loaded!"); + + this->AddCommand(botserv->Bot(), &commandbsbadwords); } }; diff --git a/modules/core/bs_bot.cpp b/modules/core/bs_bot.cpp index d35d90ec1..64413b5f0 100644 --- a/modules/core/bs_bot.cpp +++ b/modules/core/bs_bot.cpp @@ -12,6 +12,7 @@ /*************************************************************************/ #include "module.h" +#include "botserv.h" class CommandBSBot : public Command { @@ -423,7 +424,10 @@ class BSBot : public Module this->SetAuthor("Anope"); this->SetType(CORE); - this->AddCommand(BotServ, &commandbsbot); + if (!botserv) + throw ModuleException("BotServ is not loaded!"); + + this->AddCommand(botserv->Bot(), &commandbsbot); } }; diff --git a/modules/core/bs_botlist.cpp b/modules/core/bs_botlist.cpp index 1310a6358..a6a56f088 100644 --- a/modules/core/bs_botlist.cpp +++ b/modules/core/bs_botlist.cpp @@ -12,6 +12,7 @@ /*************************************************************************/ #include "module.h" +#include "botserv.h" class CommandBSBotList : public Command { @@ -83,7 +84,10 @@ class BSBotList : public Module this->SetAuthor("Anope"); this->SetType(CORE); - this->AddCommand(BotServ, &commandbsbotlist); + if (!botserv) + throw ModuleException("BotServ is not loaded!"); + + this->AddCommand(botserv->Bot(), &commandbsbotlist); } }; diff --git a/modules/core/bs_help.cpp b/modules/core/bs_help.cpp index c2632b437..eafa5b44e 100644 --- a/modules/core/bs_help.cpp +++ b/modules/core/bs_help.cpp @@ -12,6 +12,7 @@ /*************************************************************************/ #include "module.h" +#include "botserv.h" class CommandBSHelp : public Command { @@ -25,7 +26,7 @@ class CommandBSHelp : public Command CommandReturn Execute(CommandSource &source, const std::vector ¶ms) { - mod_help_cmd(BotServ, source.u, NULL, params[0]); + mod_help_cmd(botserv->Bot(), source.u, NULL, params[0]); return MOD_CONT; } @@ -40,9 +41,9 @@ class CommandBSHelp : public Command "below; to use them, type \002%s%s \037command\037\002. For\n" "more information on a specific command, type\n" "\002%s%s HELP \037command\037\002."), - BotServ->nick.c_str(), Config->UseStrictPrivMsgString.c_str(), BotServ->nick.c_str(), - Config->UseStrictPrivMsgString.c_str(), BotServ->nick.c_str()); - for (CommandMap::const_iterator it = BotServ->Commands.begin(), it_end = BotServ->Commands.end(); it != it_end; ++it) + Config->s_BotServ.c_str(), Config->UseStrictPrivMsgString.c_str(), Config->s_BotServ.c_str(), + Config->UseStrictPrivMsgString.c_str(), Config->s_BotServ.c_str()); + for (CommandMap::const_iterator it = botserv->Bot()->Commands.begin(), it_end = botserv->Bot()->Commands.end(); it != it_end; ++it) if (!Config->HidePrivilegedCommands || it->second->permission.empty() || u->HasCommand(it->second->permission)) it->second->OnServHelp(source); source.Reply(_("Bot will join a channel whenever there is at least\n" @@ -62,7 +63,10 @@ class BSHelp : public Module this->SetAuthor("Anope"); this->SetType(CORE); - this->AddCommand(BotServ, &commandbshelp); + if (!botserv) + throw ModuleException("BotServ is not loaded!"); + + this->AddCommand(botserv->Bot(), &commandbshelp); } }; diff --git a/modules/core/bs_info.cpp b/modules/core/bs_info.cpp index a5853f611..afb13c66d 100644 --- a/modules/core/bs_info.cpp +++ b/modules/core/bs_info.cpp @@ -13,6 +13,7 @@ /*************************************************************************/ #include "module.h" +#include "botserv.h" class CommandBSInfo : public Command { @@ -227,7 +228,7 @@ class CommandBSInfo : public Command "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."), NickServ->nick.c_str()); + "time or number of channels it is on."), Config->s_NickServ.c_str()); return true; } @@ -247,7 +248,10 @@ class BSInfo : public Module this->SetAuthor("Anope"); this->SetType(CORE); - this->AddCommand(BotServ, &commandbsinfo); + if (!botserv) + throw ModuleException("BotServ is not loaded!"); + + this->AddCommand(botserv->Bot(), &commandbsinfo); } }; diff --git a/modules/core/bs_kick.cpp b/modules/core/bs_kick.cpp index ff68b7099..9b78d6e7b 100644 --- a/modules/core/bs_kick.cpp +++ b/modules/core/bs_kick.cpp @@ -13,6 +13,7 @@ /*************************************************************************/ #include "module.h" +#include "botserv.h" class CommandBSKick : public Command { @@ -41,7 +42,7 @@ class CommandBSKick : public Command else if (!check_access(u, ci, CA_SET) && !u->HasPriv("botserv/administration")) source.Reply(_(ACCESS_DENIED)); else if (!ci->bi) - source.Reply(_(BOT_NOT_ASSIGNED), Config->UseStrictPrivMsgString.c_str(), BotServ->nick.c_str()); + source.Reply(_(BOT_NOT_ASSIGNED), Config->UseStrictPrivMsgString.c_str(), Config->s_BotServ.c_str()); else { bool override = !check_access(u, ci, CA_SET); @@ -479,7 +480,7 @@ class CommandBSKick : public Command "on a specific option.\n" " \n" "Note: access to this command is controlled by the\n" - "level SET."), Config->UseStrictPrivMsgString.c_str(), BotServ->nick.c_str()); + "level SET."), Config->UseStrictPrivMsgString.c_str(), Config->s_BotServ.c_str()); else if (subcommand.equals_ci("BADWORDS")) source.Reply(_("Syntax: \002KICK \037#channel\037 BADWORDS {\037ON|OFF\037} [\037ttb\037]\002\n" "Sets the bad words kicker on or off. When enabled, this\n" @@ -490,7 +491,7 @@ class CommandBSKick : public Command "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(), BotServ->nick.c_str()); + "the ban system once activated."), Config->UseStrictPrivMsgString.c_str(), Config->s_BotServ.c_str()); else if (subcommand.equals_ci("BOLDS")) source.Reply(_("Syntax: \002KICK \037channel\037 BOLDS {\037ON|OFF\037} [\037ttb\037]\002\n" "Sets the bolds kicker on or off. When enabled, this\n" @@ -581,47 +582,301 @@ class BSKick : public Module { CommandBSKick commandbskick; + void check_ban(ChannelInfo *ci, User *u, int ttbtype) + { + /* Don't ban ulines */ + if (u->server->IsULined()) + return; + + BanData *bd = botserv->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 = GetString(u->Account(), message); + va_start(args, message); + if (fmt.empty()) + return; + 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) { this->SetAuthor("Anope"); this->SetType(CORE); - this->AddCommand(BotServ, &commandbskick); + if (!botserv) + throw ModuleException("BotServ is not loaded!"); + + this->AddCommand(botserv->Bot(), &commandbskick); ModuleManager::Attach(I_OnPrivmsg, this); } - EventReturn OnPrivmsg(User *u, ChannelInfo *ci, Anope::string &msg, bool &Allow) + void OnPrivmsg(User *u, ChannelInfo *ci, Anope::string &msg) { - Anope::string m, ch; - time_t time; - - if (u->GetExtRegular("bs_kick_lastmsg", m) && u->GetExtRegular("bs_kick_lasttime", time) && u->GetExtRegular("bs_kick_lastchan", ch)) + /* 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. + */ + + bool Allow = true; + if (check_access(u, ci, CA_NOKICK)) + Allow = false; + else if (ci->botflags.HasFlag(BS_DONTKICKOPS) && (ci->c->HasUserStatus(u, CMODE_HALFOP) || ci->c->HasUserStatus(u, CMODE_OP) || ci->c->HasUserStatus(u, CMODE_PROTECT) || ci->c->HasUserStatus(u, CMODE_OWNER))) + Allow = false; + else if (ci->botflags.HasFlag(BS_DONTKICKVOICES) && ci->c->HasUserStatus(u, CMODE_VOICE)) + Allow = false; + + if (Allow) { - if (time == Anope::CurTime && m == msg && ch != ci->name) + Anope::string realbuf = msg; + + /* 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)) { - for (UChannelList::iterator it = u->chans.begin(); it != u->chans.end();) + bool mustkick = false; + + /* Normalize the buffer */ + Anope::string nbuf = normalizeBuffer(realbuf); + + for (unsigned i = 0, end = ci->GetBadWordCount(); i < end; ++i) { - Channel *c = (*it)->chan; - ++it; + BadWord *bw = ci->GetBadWord(i); - if (c->ci != NULL && c->ci->botflags.HasFlag(BS_KICK_AMSGS)) + 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) { - check_ban(c->ci, u, TTB_AMSGS); - bot_kick(c->ci, u, _("Don't use AMSGs!")); + 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; } } - return EVENT_CONTINUE; + UserData *ud = NULL; + + /* Flood kicker */ + if (ci->botflags.HasFlag(BS_KICK_FLOOD)) + { + ud = botserv->GetUserData(u, ci->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 = botserv->GetUserData(u, ci->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 *c = (*it)->chan; + ++it; + + if (c->ci != NULL && c->ci->botflags.HasFlag(BS_KICK_AMSGS) && !check_access(u, c->ci, CA_NOKICK)) + { + check_ban(c->ci, u, TTB_AMSGS); + bot_kick(c->ci, u, _("Don't use AMSGs!")); + } + } + } + + if (ud) + ud->lasttarget = ci->name; } } - - u->Extend("bs_kick_lastmsg", new ExtensibleItemRegular(msg)); - u->Extend("bs_kick_lasttime", new ExtensibleItemRegular(Anope::CurTime)); - u->Extend("bs_kick_lastchan", new ExtensibleItemRegular(ci->name)); - - return EVENT_CONTINUE; } }; diff --git a/modules/core/bs_main.cpp b/modules/core/bs_main.cpp new file mode 100644 index 000000000..5bd7e5ce2 --- /dev/null +++ b/modules/core/bs_main.cpp @@ -0,0 +1,265 @@ +/* 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" +#include "botserv.h" +#include "chanserv.h" + +static BotInfo *BotServ = NULL; + +class BotServBotInfo : public BotInfo +{ + public: + BotServBotInfo(const Anope::string &bnick, const Anope::string &user = "", const Anope::string &bhost = "", const Anope::string &real = "") : BotInfo(bnick, user, bhost, real) { } + + void OnMessage(User *u, const Anope::string &message) + { + PushLanguage("anope", u->Account() ? u->Account()->language : ""); + + spacesepstream sep(message); + Anope::string command, param; + if (sep.GetToken(command) && sep.GetToken(param)) + { + Command *c = FindCommand(this, command); + if (c) + { + if (ircdproto->IsChannelValid(param)) + { + ChannelInfo *ci = cs_findchan(param); + if (ci) + { + if (ci->HasFlag(CI_FORBIDDEN) && !c->HasFlag(CFLAG_ALLOW_FORBIDDEN)) + { + u->SendMessage(this, _(_(CHAN_X_FORBIDDEN)), ci->name.c_str()); + Log(LOG_COMMAND, "denied", this) << "Access denied for user " << u->GetMask() << " with command " << command << " because of FORBIDDEN channel " << ci->name; + PopLanguage(); + return; + } + else if (ci->HasFlag(CI_SUSPENDED) && !c->HasFlag(CFLAG_ALLOW_SUSPENDED)) + { + u->SendMessage(this, _(_(CHAN_X_FORBIDDEN)), ci->name.c_str()); + Log(LOG_COMMAND, "denied", this) << "Access denied for user " << u->GetMask() << " with command " << command << " because of SUSPENDED channel " << ci->name; + PopLanguage(); + return; + } + } + else if (!c->HasFlag(CFLAG_ALLOW_UNREGISTEREDCHANNEL)) + { + u->SendMessage(this, _(_(CHAN_X_NOT_REGISTERED)), param.c_str()); + PopLanguage(); + return; + } + } + /* A user not giving a channel name for a param that should be a channel */ + else + { + u->SendMessage(this, _(CHAN_X_INVALID), param.c_str()); + PopLanguage(); + return; + } + } + } + + PopLanguage(); + BotInfo::OnMessage(u, message); + } +}; + +class MyBotServService : public BotServService +{ + public: + MyBotServService(Module *m) : BotServService(m) { } + + BotInfo *Bot() + { + return BotServ; + } + + UserData *GetUserData(User *u, Channel *c) + { + UserData *ud = NULL; + UserContainer *uc = c->FindUser(u); + if (uc != NULL) + { + if (!uc->GetExtPointer("bs_main_userdata", ud)) + { + ud = new UserData(); + uc->Extend("bs_main_userdata", new ExtensibleItemPointer(ud)); + } + } + return ud; + } + + BanData *GetBanData(User *u, Channel *c) + { + std::map bandatamap; + if (!c->GetExtRegular("bs_main_bandata", bandatamap)); + c->Extend("bs_main_bandata", new ExtensibleItemRegular >(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; + } +}; + +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 bandata; + if (c->GetExtRegular("bs_main_bandata", bandata)) + { + for (std::map::iterator it2 = bandata.begin(), it2_end = bandata.end(); it2 != it2_end; ++it2) + { + BanData *bd = &it2->second; + + if (Anope::CurTime - bd->last_use > Config->BSKeepData) + { + bandata.erase(it2); + continue; + } + } + + if (bandata.empty()) + c->Shrink("bs_main_bandata"); + } + } + } +}; + +class BotServCore : public Module +{ + MyBotServService mybotserv; + BanDataPurger bdpurger; + + public: + BotServCore(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator), mybotserv(this), bdpurger(this) + { + this->SetAuthor("Anope"); + this->SetType(CORE); + + ModuleManager::RegisterService(&this->mybotserv); + + BotServ = new BotServBotInfo(Config->s_BotServ, Config->ServiceUser, Config->ServiceHost, Config->desc_BotServ); + BotServ->SetFlag(BI_CORE); + + Implementation i[] = { I_OnPrivmsg }; + ModuleManager::Attach(i, this, 1); + + spacesepstream coreModules(Config->BotCoreModules); + Anope::string module; + while (coreModules.GetToken(module)) + ModuleManager::LoadModule(module, NULL); + } + + ~BotServCore() + { + spacesepstream coreModules(Config->BotCoreModules); + Anope::string module; + while (coreModules.GetToken(module)) + { + Module *m = FindModule(module); + if (m != NULL) + ModuleManager::UnloadModule(m, NULL); + } + + delete BotServ; + } + + void OnPrivmsg(User *u, ChannelInfo *ci, Anope::string &msg) + { + if (!u || !ci || !ci->c || !ci->bi || msg.empty()) + return; + + /* Answer to ping if needed */ + if (msg.substr(0, 6).equals_ci("\1PING ") && msg[msg.length() - 1] == '\1') + { + Anope::string ctcp = msg; + ctcp.erase(ctcp.begin()); + ctcp.erase(ctcp.length() - 1); + ircdproto->SendCTCP(ci->bi, u->nick, "%s", ctcp.c_str()); + } + + bool was_action = false; + + 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); + was_action = true; + } + + if (realbuf.empty()) + return; + + /* Fantaisist commands */ + if (ci->botflags.HasFlag(BS_FANTASY) && realbuf[0] == Config->BSFantasyCharacter[0] && !was_action && chanserv) + { + /* Strip off the fantasy character */ + realbuf.erase(realbuf.begin()); + + size_t space = realbuf.find(' '); + Anope::string command, rest; + if (space == Anope::string::npos) + command = realbuf; + else + { + command = realbuf.substr(0, space); + rest = realbuf.substr(space + 1); + } + + if (check_access(u, ci, CA_FANTASIA)) + { + Command *cmd = FindCommand(chanserv->Bot(), command); + + /* Command exists and can be called by fantasy */ + if (cmd && !cmd->HasFlag(CFLAG_DISABLE_FANTASY)) + { + Anope::string params = rest; + /* Some commands don't need the channel name added.. eg !help */ + if (!cmd->HasFlag(CFLAG_STRIP_CHANNEL)) + params = ci->name + " " + params; + params = command + " " + params; + + mod_run_cmd(chanserv->Bot(), u, ci, params); + } + + FOREACH_MOD(I_OnBotFantasy, OnBotFantasy(command, u, ci, rest)); + } + else + { + FOREACH_MOD(I_OnBotNoFantasyAccess, OnBotNoFantasyAccess(command, u, ci, rest)); + } + } + } +}; + +MODULE_INIT(BotServCore) + diff --git a/modules/core/bs_say.cpp b/modules/core/bs_say.cpp index d64880964..673244a6c 100644 --- a/modules/core/bs_say.cpp +++ b/modules/core/bs_say.cpp @@ -12,6 +12,7 @@ /*************************************************************************/ #include "module.h" +#include "botserv.h" class CommandBSSay : public Command { @@ -36,7 +37,7 @@ class CommandBSSay : public Command if (!ci->bi) { - source.Reply(_(BOT_NOT_ASSIGNED), Config->UseStrictPrivMsgString.c_str(), BotServ->nick.c_str()); + source.Reply(_(BOT_NOT_ASSIGNED), Config->UseStrictPrivMsgString.c_str(), Config->s_BotServ.c_str()); return MOD_CONT; } @@ -85,7 +86,10 @@ class BSSay : public Module this->SetAuthor("Anope"); this->SetType(CORE); - this->AddCommand(BotServ, &commandbssay); + if (!botserv) + throw ModuleException("BotServ is not loaded!"); + + this->AddCommand(botserv->Bot(), &commandbssay); } }; diff --git a/modules/core/bs_set.cpp b/modules/core/bs_set.cpp index 24843fff7..daf37c199 100644 --- a/modules/core/bs_set.cpp +++ b/modules/core/bs_set.cpp @@ -12,6 +12,7 @@ /*************************************************************************/ #include "module.h" +#include "botserv.h" class CommandBSSet : public Command { @@ -216,7 +217,7 @@ class CommandBSSet : public Command "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(), BotServ->nick.c_str()); + "level SET."), Config->UseStrictPrivMsgString.c_str(), Config->s_BotServ.c_str()); User *u = source.u; if (u->IsServicesOper()) source.Reply(_("These options are reserved to Services Operators:\n" @@ -308,7 +309,10 @@ class BSSet : public Module this->SetAuthor("Anope"); this->SetType(CORE); - this->AddCommand(BotServ, &commandbsset); + if (!botserv) + throw ModuleException("BotServ is not loaded!"); + + this->AddCommand(botserv->Bot(), &commandbsset); } }; diff --git a/modules/core/bs_unassign.cpp b/modules/core/bs_unassign.cpp index 8e28508ab..104eded56 100644 --- a/modules/core/bs_unassign.cpp +++ b/modules/core/bs_unassign.cpp @@ -12,6 +12,7 @@ /*************************************************************************/ #include "module.h" +#include "botserv.h" class CommandBSUnassign : public Command { @@ -33,7 +34,7 @@ class CommandBSUnassign : public Command else if (!u->HasPriv("botserv/administration") && !check_access(u, ci, CA_ASSIGN)) source.Reply(_(ACCESS_DENIED)); else if (!ci->bi) - source.Reply(_(BOT_NOT_ASSIGNED), Config->UseStrictPrivMsgString.c_str(), BotServ->nick.c_str()); + source.Reply(_(BOT_NOT_ASSIGNED), Config->UseStrictPrivMsgString.c_str(), Config->s_BotServ.c_str()); else if (ci->HasFlag(CI_PERSIST) && !cm) source.Reply(_("You can not unassign bots while persist is set on the channel.")); else @@ -75,7 +76,10 @@ class BSUnassign : public Module this->SetAuthor("Anope"); this->SetType(CORE); - this->AddCommand(BotServ, &commandbsunassign); + if (!botserv) + throw ModuleException("BotServ is not loaded!"); + + this->AddCommand(botserv->Bot(), &commandbsunassign); } }; diff --git a/modules/core/chanserv.h b/modules/core/chanserv.h new file mode 100644 index 000000000..2017aa156 --- /dev/null +++ b/modules/core/chanserv.h @@ -0,0 +1,15 @@ +#ifndef CHANSERV_H +#define CHANSERV_H + +class ChanServService : public Service +{ + public: + ChanServService(Module *m) : Service(m, "ChanServ") { } + + virtual BotInfo *Bot() = 0; +}; + +static service_reference chanserv("ChanServ"); + +#endif // CHANSERV_H + diff --git a/modules/core/cs_access.cpp b/modules/core/cs_access.cpp index 0cf8263ac..5fc4e7290 100644 --- a/modules/core/cs_access.cpp +++ b/modules/core/cs_access.cpp @@ -12,6 +12,7 @@ /*************************************************************************/ #include "module.h" +#include "chanserv.h" class AccessListCallback : public NumberList { @@ -504,7 +505,7 @@ class CommandCSAccess : public Command " \n" "The \002ACCESS CLEAR\002 command clears all entries of the\n" "access list."), - ChanServ->nick.c_str(), Config->UseStrictPrivMsgString.c_str(), ChanServ->nick.c_str()); + Config->s_ChanServ.c_str(), Config->UseStrictPrivMsgString.c_str(), Config->s_ChanServ.c_str()); source.Reply(_("\002User access levels\002\n" " \n" "By default, the following access levels are defined:\n" @@ -523,7 +524,7 @@ class CommandCSAccess : public Command " \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."), ChanServ->nick.c_str(), Config->UseStrictPrivMsgString.c_str(), ChanServ->nick.c_str()); + "information."), Config->s_ChanServ.c_str(), Config->UseStrictPrivMsgString.c_str(), Config->s_ChanServ.c_str()); return true; } @@ -746,7 +747,7 @@ class CommandCSLevels : public Command "\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."), ChanServ->nick.c_str()); + "set, see \002HELP LEVELS DESC\002."), Config->s_ChanServ.c_str()); return true; } @@ -767,8 +768,11 @@ class CSAccess : public Module this->SetAuthor("Anope"); this->SetType(CORE); - this->AddCommand(ChanServ, &commandcsaccess); - this->AddCommand(ChanServ, &commandcslevels); + if (!chanserv) + throw ModuleException("ChanServ is not loaded!"); + + this->AddCommand(chanserv->Bot(), &commandcsaccess); + this->AddCommand(chanserv->Bot(), &commandcslevels); } }; diff --git a/modules/core/cs_akick.cpp b/modules/core/cs_akick.cpp index 4584ac6b5..e168bdb67 100644 --- a/modules/core/cs_akick.cpp +++ b/modules/core/cs_akick.cpp @@ -12,6 +12,7 @@ /*************************************************************************/ #include "module.h" +#include "chanserv.h" /* Split a usermask up into its constitutent parts. Returned strings are * malloc()'d, and should be free()'d when done with. Returns "*" for @@ -532,7 +533,7 @@ class CommandCSAKick : public Command "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"), - ChanServ->nick.c_str()); + Config->s_ChanServ.c_str()); source.Reply(_( " \n" "The \002AKICK DEL\002 command removes the given nick or mask\n" @@ -552,7 +553,7 @@ class CommandCSAKick : public Command "AKICK mask.\n" " \n" "The \002AKICK CLEAR\002 command clears all entries of the\n" - "akick list."), ChanServ->nick.c_str()); + "akick list."), Config->s_ChanServ.c_str()); return true; } @@ -572,7 +573,10 @@ class CSAKick : public Module this->SetAuthor("Anope"); this->SetType(CORE); - this->AddCommand(ChanServ, &commandcsakick); + if (!chanserv) + throw ModuleException("ChanServ is not loaded!"); + + this->AddCommand(chanserv->Bot(), &commandcsakick); } }; diff --git a/modules/core/cs_ban.cpp b/modules/core/cs_ban.cpp index 97d205156..3bccd9897 100644 --- a/modules/core/cs_ban.cpp +++ b/modules/core/cs_ban.cpp @@ -12,6 +12,7 @@ /*************************************************************************/ #include "module.h" +#include "chanserv.h" class CommandCSBan : public Command { @@ -67,9 +68,9 @@ class CommandCSBan : public Command return MOD_CONT; if (ci->HasFlag(CI_SIGNKICK) || (ci->HasFlag(CI_SIGNKICK_LEVEL) && !check_access(u, ci, CA_SIGNKICK))) - c->Kick(whosends(ci), u2, "%s (%s)", reason.c_str(), u->nick.c_str()); + c->Kick(ci->WhoSends(), u2, "%s (%s)", reason.c_str(), u->nick.c_str()); else - c->Kick(whosends(ci), u2, "%s", reason.c_str()); + c->Kick(ci->WhoSends(), u2, "%s", reason.c_str()); } return MOD_CONT; @@ -102,7 +103,10 @@ class CSBan : public Module this->SetAuthor("Anope"); this->SetType(CORE); - this->AddCommand(ChanServ, &commandcsban); + if (!chanserv) + throw ModuleException("ChanServ is not loaded!"); + + this->AddCommand(chanserv->Bot(), &commandcsban); } }; diff --git a/modules/core/cs_clearusers.cpp b/modules/core/cs_clearusers.cpp index 572c396af..7cf5055b7 100644 --- a/modules/core/cs_clearusers.cpp +++ b/modules/core/cs_clearusers.cpp @@ -12,13 +12,14 @@ /*************************************************************************/ #include "module.h" +#include "chanserv.h" class CommandCSClearUsers : public Command { public: CommandCSClearUsers() : Command("CLEARUSERS", 1, 1) { - this->SetDesc(_("Tells ChanServ to clear (kick) all users on a channel")); + this->SetDesc(Anope::printf(_("Tells %s to kick all users on a channel"), Config->s_ChanServ.c_str())); } CommandReturn Execute(CommandSource &source, const std::vector ¶ms) @@ -57,7 +58,7 @@ class CommandCSClearUsers : public Command "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."), ChanServ->nick.c_str()); + "channel."), Config->s_ChanServ.c_str()); return true; } @@ -77,7 +78,10 @@ class CSClearUsers : public Module this->SetAuthor("Anope"); this->SetType(CORE); - this->AddCommand(ChanServ, &commandcsclearusers); + if (!chanserv) + throw ModuleException("ChanServ is not loaded!"); + + this->AddCommand(chanserv->Bot(), &commandcsclearusers); } }; diff --git a/modules/core/cs_clone.cpp b/modules/core/cs_clone.cpp index bbdae4c5f..52161abf1 100644 --- a/modules/core/cs_clone.cpp +++ b/modules/core/cs_clone.cpp @@ -12,6 +12,7 @@ /*************************************************************************/ #include "module.h" +#include "chanserv.h" class CommandCSClone : public Command { @@ -177,7 +178,10 @@ class CSClone : public Module this->SetAuthor("Anope"); this->SetType(CORE); - this->AddCommand(ChanServ, &commandcsclone); + if (!chanserv) + throw ModuleException("ChanServ is not loaded!"); + + this->AddCommand(chanserv->Bot(), &commandcsclone); } }; diff --git a/modules/core/cs_drop.cpp b/modules/core/cs_drop.cpp index 248cfa37e..66f1b31f6 100644 --- a/modules/core/cs_drop.cpp +++ b/modules/core/cs_drop.cpp @@ -12,6 +12,7 @@ /*************************************************************************/ #include "module.h" +#include "chanserv.h" class CommandCSDrop : public Command { @@ -67,8 +68,6 @@ class CommandCSDrop : public Command bool override = (ci->HasFlag(CI_SECUREFOUNDER) ? !IsFounder(u, ci) : !check_access(u, ci, CA_FOUNDER)); Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "founder: " << (ci->founder ? ci->founder->display : "none"); - if (override) - ircdproto->SendGlobops(ChanServ, "\2%s\2 used DROP on channel \2%s\2", u->nick.c_str(), ci->name.c_str()); delete ci; @@ -112,7 +111,10 @@ class CSDrop : public Module this->SetAuthor("Anope"); this->SetType(CORE); - this->AddCommand(ChanServ, &commandcsdrop); + if (!chanserv) + throw ModuleException("ChanServ is not loaded!"); + + this->AddCommand(chanserv->Bot(), &commandcsdrop); } }; diff --git a/modules/core/cs_forbid.cpp b/modules/core/cs_forbid.cpp index e17a1777c..9c0573971 100644 --- a/modules/core/cs_forbid.cpp +++ b/modules/core/cs_forbid.cpp @@ -12,6 +12,7 @@ /*************************************************************************/ #include "module.h" +#include "chanserv.h" class CommandCSForbid : public Command { @@ -75,7 +76,7 @@ class CommandCSForbid : public Command if (uc->user->HasMode(UMODE_OPER)) continue; - c->Kick(ChanServ, uc->user, "%s", !reason.empty() ? reason.c_str() : GetString(uc->user->Account(), "This channel has been forbidden.").c_str()); + c->Kick(chanserv->Bot(), uc->user, "%s", !reason.empty() ? reason.c_str() : GetString(uc->user->Account(), "This channel has been forbidden.").c_str()); } } @@ -121,7 +122,10 @@ class CSForbid : public Module this->SetAuthor("Anope"); this->SetType(CORE); - this->AddCommand(ChanServ, &commandcsforbid); + if (!chanserv) + throw ModuleException("ChanServ is not loaded!"); + + this->AddCommand(chanserv->Bot(), &commandcsforbid); } }; diff --git a/modules/core/cs_getkey.cpp b/modules/core/cs_getkey.cpp index da71e690e..b46a7df53 100644 --- a/modules/core/cs_getkey.cpp +++ b/modules/core/cs_getkey.cpp @@ -12,6 +12,7 @@ /*************************************************************************/ #include "module.h" +#include "chanserv.h" class CommandCSGetKey : public Command { @@ -72,7 +73,10 @@ class CSGetKey : public Module this->SetAuthor("Anope"); this->SetType(CORE); - this->AddCommand(ChanServ, &commandcsgetkey); + if (!chanserv) + throw ModuleException("ChanServ is not loaded!"); + + this->AddCommand(chanserv->Bot(), &commandcsgetkey); } }; diff --git a/modules/core/cs_help.cpp b/modules/core/cs_help.cpp index be2436d29..aff4c5165 100644 --- a/modules/core/cs_help.cpp +++ b/modules/core/cs_help.cpp @@ -12,6 +12,7 @@ /*************************************************************************/ #include "module.h" +#include "chanserv.h" class CommandCSHelp : public Command { @@ -25,7 +26,7 @@ class CommandCSHelp : public Command CommandReturn Execute(CommandSource &source, const std::vector ¶ms) { - mod_help_cmd(ChanServ, source.u, NULL, params[0]); + mod_help_cmd(chanserv->Bot(), source.u, NULL, params[0]); return MOD_CONT; } @@ -40,9 +41,8 @@ class CommandCSHelp : public Command "commands are listed below; to use them, type\n" "\002%s%s \037command\037\002. For more information on a\n" "specific command, type \002%s%s HELP \037command\037\002."), - ChanServ->nick.c_str(), ChanServ->nick.c_str(), Config->UseStrictPrivMsgString.c_str(), ChanServ->nick.c_str(), Config->UseStrictPrivMsgString.c_str(), ChanServ->nick.c_str(), - ChanServ->nick.c_str()); - for (CommandMap::const_iterator it = ChanServ->Commands.begin(); it != ChanServ->Commands.end(); ++it) + Config->s_ChanServ.c_str(), Config->s_ChanServ.c_str(), Config->UseStrictPrivMsgString.c_str(), Config->s_ChanServ.c_str(), Config->UseStrictPrivMsgString.c_str(), Config->s_ChanServ.c_str(), Config->s_ChanServ.c_str()); + for (CommandMap::const_iterator it = chanserv->Bot()->Commands.begin(); it != chanserv->Bot()->Commands.end(); ++it) if (!Config->HidePrivilegedCommands || it->second->permission.empty() || u->HasCommand(it->second->permission)) it->second->OnServHelp(source); if (Config->CSExpire >= 86400) @@ -67,7 +67,10 @@ class CSHelp : public Module this->SetAuthor("Anope"); this->SetType(CORE); - this->AddCommand(ChanServ, &commandcshelp); + if (!chanserv) + throw ModuleException("ChanServ is not loaded!"); + + this->AddCommand(chanserv->Bot(), &commandcshelp); } }; diff --git a/modules/core/cs_info.cpp b/modules/core/cs_info.cpp index c9c8b674c..19823004d 100644 --- a/modules/core/cs_info.cpp +++ b/modules/core/cs_info.cpp @@ -12,6 +12,7 @@ /*************************************************************************/ #include "module.h" +#include "chanserv.h" class CommandCSInfo : public Command { @@ -140,7 +141,10 @@ class CSInfo : public Module this->SetAuthor("Anope"); this->SetType(CORE); - this->AddCommand(ChanServ, &commandcsinfo); + if (!chanserv) + throw ModuleException("ChanServ is not loaded!"); + + this->AddCommand(chanserv->Bot(), &commandcsinfo); } }; diff --git a/modules/core/cs_invite.cpp b/modules/core/cs_invite.cpp index efafdeedd..e7bcbbdab 100644 --- a/modules/core/cs_invite.cpp +++ b/modules/core/cs_invite.cpp @@ -12,13 +12,14 @@ /*************************************************************************/ #include "module.h" +#include "chanserv.h" class CommandCSInvite : public Command { public: CommandCSInvite() : Command("INVITE", 1, 3) { - this->SetDesc(_("Tells ChanServ to invite you into a channel")); + this->SetDesc(Anope::printf(_("Tells %s to invite you into a channel"), Config->s_ChanServ.c_str())); } CommandReturn Execute(CommandSource &source, const std::vector ¶ms) @@ -62,9 +63,9 @@ class CommandCSInvite : public Command source.Reply(_("You are already in \002%s\002! "), c->name.c_str()); else { - ircdproto->SendInvite(whosends(ci), chan, u2->nick); + 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(whosends(ci), _("You have been invited to \002%s\002."), c->name.c_str()); + u2->SendMessage(ci->WhoSends(), _("You have been invited to \002%s\002."), c->name.c_str()); } return MOD_CONT; } @@ -76,7 +77,7 @@ class CommandCSInvite : public Command "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."), ChanServ->nick.c_str()); + "on the channel."), Config->s_ChanServ.c_str()); return true; } @@ -96,7 +97,10 @@ class CSInvite : public Module this->SetAuthor("Anope"); this->SetType(CORE); - this->AddCommand(ChanServ, &commandcsinvite); + if (!chanserv) + throw ModuleException("ChanServ is not loaded!"); + + this->AddCommand(chanserv->Bot(), &commandcsinvite); } }; diff --git a/modules/core/cs_kick.cpp b/modules/core/cs_kick.cpp index 2f165fb72..2cf06e1e4 100644 --- a/modules/core/cs_kick.cpp +++ b/modules/core/cs_kick.cpp @@ -12,6 +12,7 @@ /*************************************************************************/ #include "module.h" +#include "chanserv.h" class CommandCSKick : public Command { @@ -54,9 +55,9 @@ class CommandCSKick : public Command Log(LOG_COMMAND, u, this, ci) << "for " << u2->nick; if (ci->HasFlag(CI_SIGNKICK) || (ci->HasFlag(CI_SIGNKICK_LEVEL) && !check_access(u, ci, CA_SIGNKICK))) - ci->c->Kick(whosends(ci), u2, "%s (%s)", reason.c_str(), u->nick.c_str()); + ci->c->Kick(ci->WhoSends(), u2, "%s (%s)", reason.c_str(), u->nick.c_str()); else - ci->c->Kick(whosends(ci), u2, "%s", reason.c_str()); + ci->c->Kick(ci->WhoSends(), u2, "%s", reason.c_str()); } return MOD_CONT; } @@ -88,7 +89,10 @@ class CSKick : public Module this->SetAuthor("Anope"); this->SetType(CORE); - this->AddCommand(ChanServ, &commandcskick); + if (!chanserv) + throw ModuleException("ChanServ is not loaded!"); + + this->AddCommand(chanserv->Bot(), &commandcskick); } }; diff --git a/modules/core/cs_list.cpp b/modules/core/cs_list.cpp index 8e3d75e5f..318eea59f 100644 --- a/modules/core/cs_list.cpp +++ b/modules/core/cs_list.cpp @@ -12,7 +12,7 @@ /*************************************************************************/ #include "module.h" -#include "hashcomp.h" +#include "chanserv.h" class CommandCSList : public Command { @@ -147,7 +147,10 @@ class CSList : public Module this->SetAuthor("Anope"); this->SetType(CORE); - this->AddCommand(ChanServ, &commandcslist); + if (!chanserv) + throw ModuleException("ChanServ is not loaded!"); + + this->AddCommand(chanserv->Bot(), &commandcslist); } }; diff --git a/modules/core/cs_main.cpp b/modules/core/cs_main.cpp new file mode 100644 index 000000000..04258b646 --- /dev/null +++ b/modules/core/cs_main.cpp @@ -0,0 +1,256 @@ +/* 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" +#include "chanserv.h" + +static BotInfo *ChanServ = NULL; + +class ChanServBotInfo : public BotInfo +{ + public: + ChanServBotInfo(const Anope::string &bnick, const Anope::string &user = "", const Anope::string &bhost = "", const Anope::string &real = "") : BotInfo(bnick, user, bhost, real) { } + + void OnMessage(User *u, const Anope::string &message) + { + PushLanguage("anope", u->Account() ? u->Account()->language : ""); + + if (!u->HasMode(UMODE_OPER) && Config->CSOpersOnly) + { + u->SendMessage(ChanServ, _(ACCESS_DENIED)); + PopLanguage(); + return; + } + + spacesepstream sep(message); + Anope::string command, param; + if (sep.GetToken(command) && sep.GetToken(param)) + { + Command *c = FindCommand(this, command); + if (c) + { + if (ircdproto->IsChannelValid(param)) + { + ChannelInfo *ci = cs_findchan(param); + if (ci) + { + if (ci->HasFlag(CI_FORBIDDEN) && !c->HasFlag(CFLAG_ALLOW_FORBIDDEN)) + { + u->SendMessage(this, _(_(CHAN_X_FORBIDDEN)), ci->name.c_str()); + Log(LOG_COMMAND, "denied", this) << "Access denied for user " << u->GetMask() << " with command " << command << " because of FORBIDDEN channel " << ci->name; + PopLanguage(); + return; + } + else if (ci->HasFlag(CI_SUSPENDED) && !c->HasFlag(CFLAG_ALLOW_SUSPENDED)) + { + u->SendMessage(this, _(_(CHAN_X_FORBIDDEN)), ci->name.c_str()); + Log(LOG_COMMAND, "denied", this) << "Access denied for user " << u->GetMask() << " with command " << command << " because of SUSPENDED channel " << ci->name; + PopLanguage(); + return; + } + } + else if (!c->HasFlag(CFLAG_ALLOW_UNREGISTEREDCHANNEL)) + { + u->SendMessage(this, _(_(CHAN_X_NOT_REGISTERED)), param.c_str()); + PopLanguage(); + return; + } + } + /* A user not giving a channel name for a param that should be a channel */ + else + { + u->SendMessage(this, _(CHAN_X_INVALID), param.c_str()); + PopLanguage(); + return; + } + } + } + + PopLanguage(); + BotInfo::OnMessage(u, message); + } +}; + +class MyChanServService : public ChanServService +{ + public: + MyChanServService(Module *m) : ChanServService(m) { } + + BotInfo *Bot() + { + return ChanServ; + } +}; + +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->HasFlag(CI_FORBIDDEN)) + { + if (Config->CSForbidExpire && Anope::CurTime - ci->last_used >= Config->CSForbidExpire) + expire = true; + } + else if (!ci->c && Anope::CurTime - ci->last_used >= Config->CSExpire) + expire = true; + + if (ci->HasFlag(CI_NO_EXPIRE)) + expire = false; + + if (expire) + { + EventReturn MOD_RESULT; + FOREACH_RESULT(I_OnPreChanExpire, OnPreChanExpire(ci)); + if (MOD_RESULT == EVENT_STOP) + continue; + + Anope::string extra; + if (ci->HasFlag(CI_FORBIDDEN)) + extra = "forbidden "; + else if (ci->HasFlag(CI_SUSPENDED)) + extra = "suspended "; + + Log(LOG_NORMAL, "chanserv/expire", ChanServ) << "Expiring " << extra << "channel " << ci->name << " (founder: " << (ci->founder ? ci->founder->display : "(none)") << ")"; + FOREACH_MOD(I_OnChanExpire, OnChanExpire(ci)); + delete ci; + } + } + } +}; + +class ChanServCore : public Module +{ + MyChanServService mychanserv; + ExpireCallback expires; + + public: + ChanServCore(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator), mychanserv(this), expires(this) + { + this->SetAuthor("Anope"); + this->SetType(CORE); + + ModuleManager::RegisterService(&this->mychanserv); + + ChanServ = new ChanServBotInfo(Config->s_ChanServ, Config->ServiceUser, Config->ServiceHost, Config->desc_ChanServ); + ChanServ->SetFlag(BI_CORE); + + Implementation i[] = { I_OnDelCore, I_OnDelChan }; + ModuleManager::Attach(i, this, 2); + + spacesepstream coreModules(Config->ChanCoreModules); + Anope::string module; + while (coreModules.GetToken(module)) + ModuleManager::LoadModule(module, NULL); + } + + ~ChanServCore() + { + spacesepstream coreModules(Config->ChanCoreModules); + Anope::string module; + while (coreModules.GetToken(module)) + { + Module *m = FindModule(module); + if (m != NULL) + ModuleManager::UnloadModule(m, NULL); + } + + delete ChanServ; + } + + void OnDelCore(NickCore *nc) + { + // XXX this is slightly inefficient + for (registered_channel_map::const_iterator it = RegisteredChannelList.begin(), it_end = RegisteredChannelList.end(); it != it_end;) + { + ChannelInfo *ci = it->second; + ++it; + + if (ci->founder == nc) + { + NickCore *newowner = NULL; + if (ci->successor && (ci->successor->IsServicesOper() || !Config->CSMaxReg || ci->successor->channelcount < Config->CSMaxReg)) + newowner = ci->successor; + else + { + ChanAccess *highest = NULL; + for (unsigned j = 0; j < ci->GetAccessCount(); ++j) + { + ChanAccess *ca = ci->GetAccess(j); + + if (!ca->nc || (!ca->nc->IsServicesOper() && Config->CSMaxReg && ca->nc->channelcount >= Config->CSMaxReg) || (ca->nc == nc)) + continue; + if (!highest || ca->level > highest->level) + highest = ca; + } + if (highest) + newowner = highest->nc; + } + + if (newowner) + { + Log(LOG_NORMAL, "chanserv/expire") << "Transferring foundership of " << ci->name << " from deleted nick " << nc->display << " to " << newowner->display; + ci->founder = newowner; + ci->successor = NULL; + ++newowner->channelcount; + } + else + { + Log(LOG_NORMAL, "chanserv/expire") << "Deleting channel " << ci->name << " owned by deleted nick " << nc->display; + + delete ci; + continue; + } + } + + if (ci->successor == nc) + ci->successor = NULL; + + ChanAccess *access = ci->GetAccess(nc); + if (access) + ci->EraseAccess(access); + + for (unsigned j = ci->GetAkickCount(); j > 0; --j) + { + AutoKick *akick = ci->GetAkick(j - 1); + if (akick->HasFlag(AK_ISNICK) && akick->nc == nc) + ci->EraseAkick(j - 1); + } + } + } + + void OnDelChan(ChannelInfo *ci) + { + if (ci->c && ci->c->HasMode(CMODE_REGISTERED)) + ci->c->RemoveMode(NULL, CMODE_REGISTERED, "", false); + } +}; + +MODULE_INIT(ChanServCore) + diff --git a/modules/core/cs_mode.cpp b/modules/core/cs_mode.cpp index 55da9762c..fc4538033 100644 --- a/modules/core/cs_mode.cpp +++ b/modules/core/cs_mode.cpp @@ -12,6 +12,7 @@ /*************************************************************************/ #include "module.h" +#include "chanserv.h" class CommandCSMode : public Command { @@ -366,7 +367,10 @@ class CSMode : public Module this->SetAuthor("Anope"); this->SetType(CORE); - this->AddCommand(ChanServ, &commandcsmode); + if (!chanserv) + throw ModuleException("ChanServ is not loaded!"); + + this->AddCommand(chanserv->Bot(), &commandcsmode); } }; diff --git a/modules/core/cs_modes.cpp b/modules/core/cs_modes.cpp index 96520b215..eeaf9f733 100644 --- a/modules/core/cs_modes.cpp +++ b/modules/core/cs_modes.cpp @@ -12,7 +12,7 @@ /*************************************************************************/ #include "module.h" - +#include "chanserv.h" class CommandModeBase : public Command { @@ -76,7 +76,7 @@ class CommandModeBase : public Command Log(LOG_COMMAND, u, com, ci) << "for " << u2->nick; if (notice && ci->HasFlag(notice)) - ircdproto->SendMessage(whosends(ci), c->name, "%s command used for %s by %s", com->name.c_str(), u2->nick.c_str(), u->nick.c_str()); + 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()); } } @@ -462,10 +462,13 @@ class CSModes : public Module this->SetAuthor("Anope"); this->SetType(CORE); - this->AddCommand(ChanServ, &commandcsop); - this->AddCommand(ChanSe