summaryrefslogtreecommitdiff
path: root/modules/pseudoclients
diff options
context:
space:
mode:
Diffstat (limited to 'modules/pseudoclients')
-rw-r--r--modules/pseudoclients/botserv.h63
-rw-r--r--modules/pseudoclients/bs_main.cpp204
-rw-r--r--modules/pseudoclients/cs_main.cpp143
-rw-r--r--modules/pseudoclients/gl_main.cpp93
-rw-r--r--modules/pseudoclients/global.h20
-rw-r--r--modules/pseudoclients/hs_main.cpp85
-rw-r--r--modules/pseudoclients/memoserv.h41
-rw-r--r--modules/pseudoclients/ms_main.cpp228
-rw-r--r--modules/pseudoclients/nickserv.h15
-rw-r--r--modules/pseudoclients/ns_main.cpp307
-rw-r--r--modules/pseudoclients/os_main.cpp371
11 files changed, 1570 insertions, 0 deletions
diff --git a/modules/pseudoclients/botserv.h b/modules/pseudoclients/botserv.h
new file mode 100644
index 000000000..5fa03f870
--- /dev/null
+++ b/modules/pseudoclients/botserv.h
@@ -0,0 +1,63 @@
+#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 UserData *GetUserData(User *u, Channel *c) = 0;
+
+ virtual BanData *GetBanData(User *u, Channel *c) = 0;
+};
+
+static service_reference<BotServService> botserv("BotServ");
+
+#endif // BOTSERV_H
+
diff --git a/modules/pseudoclients/bs_main.cpp b/modules/pseudoclients/bs_main.cpp
new file mode 100644
index 000000000..69eab6040
--- /dev/null
+++ b/modules/pseudoclients/bs_main.cpp
@@ -0,0 +1,204 @@
+/* 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 BotServCore : public Module
+{
+ Channel *fantasy_channel;
+ public:
+ BotServCore(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), fantasy_channel(NULL)
+ {
+ this->SetAuthor("Anope");
+
+ BotInfo *BotServ = findbot(Config->BotServ);
+ if (BotServ == NULL)
+ throw ModuleException("No bot named " + Config->BotServ);
+
+ Implementation i[] = { I_OnPrivmsg, I_OnPreCommand, I_OnJoinChannel, I_OnLeaveChannel,
+ I_OnPreHelp, I_OnPostHelp, I_OnChannelModeSet };
+ ModuleManager::Attach(i, this, sizeof(i) / sizeof(Implementation));
+
+ }
+
+ void OnPrivmsg(User *u, Channel *c, Anope::string &msg)
+ {
+ if (!u || !c || !c->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(c->ci->bi, u->nick, "%s", ctcp.c_str());
+ }
+
+
+ Anope::string realbuf = msg;
+
+ bool was_action = false;
+ 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 (c->ci->botflags.HasFlag(BS_FANTASY) && realbuf[0] == Config->BSFantasyCharacter[0] && !was_action)
+ {
+ /* 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);
+ }
+
+ BotInfo *bi = findbot(Config->ChanServ);
+ if (bi == NULL)
+ bi = findbot(Config->BotServ);
+ if (bi == NULL || !bi->commands.count(command))
+ return;
+
+ if (c->ci->HasPriv(u, CA_FANTASIA))
+ {
+
+ this->fantasy_channel = c;
+ bi->OnMessage(u, realbuf);
+ this->fantasy_channel = NULL;
+
+ FOREACH_MOD(I_OnBotFantasy, OnBotFantasy(command, u, c->ci, rest));
+ }
+ else
+ {
+ FOREACH_MOD(I_OnBotNoFantasyAccess, OnBotNoFantasyAccess(command, u, c->ci, rest));
+ }
+ }
+ }
+
+ EventReturn OnPreCommand(CommandSource &source, Command *command, std::vector<Anope::string> &params)
+ {
+ if (this->fantasy_channel != NULL)
+ {
+ if (!command->HasFlag(CFLAG_STRIP_CHANNEL))
+ params.insert(params.begin(), this->fantasy_channel->name);
+ source.c = this->fantasy_channel;
+ }
+
+ return EVENT_CONTINUE;
+ }
+
+ void OnJoinChannel(User *user, Channel *c)
+ {
+ if (c->ci && c->ci->bi)
+ {
+ /**
+ * We let the bot join even if it was an ignored user, as if we don't,
+ * and the ignored user doesnt just leave, the bot will never
+ * make it into the channel, leaving the channel botless even for
+ * legit users - Rob
+ **/
+ if (c->users.size() >= Config->BSMinUsers && !c->FindUser(c->ci->bi))
+ c->ci->bi->Join(c, &Config->BotModeList);
+ /* Only display the greet if the main uplink we're connected
+ * to has synced, or we'll get greet-floods when the net
+ * recovers from a netsplit. -GD
+ */
+ if (c->FindUser(c->ci->bi) && c->ci->botflags.HasFlag(BS_GREET) && user->Account() && !user->Account()->greet.empty() && c->ci->HasPriv(user, CA_GREET) && user->server->IsSynced())
+ {
+ ircdproto->SendPrivmsg(c->ci->bi, c->name, "[%s] %s", user->Account()->display.c_str(), user->Account()->greet.c_str());
+ c->ci->bi->lastmsg = Anope::CurTime;
+ }
+ }
+ }
+
+ void OnLeaveChannel(User *u, Channel *c)
+ {
+ /* Channel is persistant, it shouldn't be deleted and the service bot should stay */
+ if (c->HasFlag(CH_PERSIST) || (c->ci && c->ci->HasFlag(CI_PERSIST)))
+ return;
+
+ /* Channel is syncing from a netburst, don't destroy it as more users are probably wanting to join immediatly
+ * We also don't part the bot here either, if necessary we will part it after the sync
+ */
+ if (c->HasFlag(CH_SYNCING))
+ return;
+
+ /* Additionally, do not delete this channel if ChanServ/a BotServ bot is inhabiting it */
+ if (c->HasFlag(CH_INHABIT))
+ return;
+
+ if (c->ci && c->ci->bi && c->users.size() - 1 <= Config->BSMinUsers && c->FindUser(c->ci->bi))
+ {
+ bool persist = c->HasFlag(CH_PERSIST);
+ c->SetFlag(CH_PERSIST);
+ c->ci->bi->Part(c->ci->c);
+ if (!persist)
+ c->UnsetFlag(CH_PERSIST);
+ }
+ }
+
+ void OnPreHelp(CommandSource &source, const std::vector<Anope::string> &params)
+ {
+ if (!params.empty() || source.owner->nick != Config->BotServ)
+ return;
+ source.Reply(_("\002%s\002 allows you to have a bot on your own channel.\n"
+ "It has been created for users that can't host or\n"
+ "configure a bot, or for use on networks that don't\n"
+ "allow user bots. Available commands are listed \n"
+ "below; to use them, type \002%s%s \037command\037\002. For\n"
+ "more information on a specific command, type\n"
+ "\002%s%s %s \037command\037\002.\n "),
+ Config->BotServ.c_str(), Config->UseStrictPrivMsgString.c_str(), Config->BotServ.c_str(),
+ Config->UseStrictPrivMsgString.c_str(), Config->BotServ.c_str(), source.command.c_str());
+ }
+
+ void OnPostHelp(CommandSource &source, const std::vector<Anope::string> &params)
+ {
+ if (!params.empty() || source.owner->nick != Config->BotServ)
+ return;
+ source.Reply(_(" \n"
+ "Bot will join a channel whenever there is at least\n"
+ "\002%d\002 user(s) on it. Additionally, all %s commands\n"
+ "can be used if fantasy is enabled by prefixing the command\n"
+ "name with a %c."), Config->BSMinUsers, Config->ChanServ.c_str(), Config->BSFantasyCharacter[0]);
+ }
+
+ EventReturn OnChannelModeSet(Channel *c, ChannelModeName Name, const Anope::string &param)
+ {
+ if (Config->BSSmartJoin && Name == CMODE_BAN && c->ci && c->ci->bi && c->FindUser(c->ci->bi))
+ {
+ BotInfo *bi = c->ci->bi;
+
+ Entry ban(CMODE_BAN, param);
+ if (ban.Matches(bi))
+ c->RemoveMode(bi, CMODE_BAN, param);
+ }
+
+ return EVENT_CONTINUE;
+ }
+};
+
+MODULE_INIT(BotServCore)
+
diff --git a/modules/pseudoclients/cs_main.cpp b/modules/pseudoclients/cs_main.cpp
new file mode 100644
index 000000000..5c490e4ff
--- /dev/null
+++ b/modules/pseudoclients/cs_main.cpp
@@ -0,0 +1,143 @@
+/* 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 ChanServCore : public Module
+{
+ public:
+ ChanServCore(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE)
+ {
+ this->SetAuthor("Anope");
+
+ BotInfo *ChanServ = findbot(Config->ChanServ);
+ if (ChanServ == NULL)
+ throw ModuleException("No bot named " + Config->ChanServ);
+
+ Implementation i[] = { I_OnBotPrivmsg, I_OnDelCore, I_OnPreHelp, I_OnPostHelp };
+ ModuleManager::Attach(i, this, sizeof(i) / sizeof(Implementation));
+ }
+
+ EventReturn OnBotPrivmsg(User *u, BotInfo *bi, Anope::string &message)
+ {
+ if (Config->CSOpersOnly && !u->HasMode(UMODE_OPER) && bi->nick == Config->ChanServ)
+ {
+ u->SendMessage(bi, ACCESS_DENIED);
+ return EVENT_STOP;
+ }
+
+ return EVENT_CONTINUE;
+ }
+
+ 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->GetFounder() == 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);
+ NickCore *anc = findcore(ca->mask);
+
+ if (!anc || (!anc->IsServicesOper() && Config->CSMaxReg && anc->channelcount >= Config->CSMaxReg) || (anc == nc))
+ continue;
+ if (!highest || *ca > *highest)
+ highest = ca;
+ }
+ if (highest)
+ newowner = findcore(highest->mask);
+ }
+
+ if (newowner)
+ {
+ Log(LOG_NORMAL, "chanserv/expire") << "Transferring foundership of " << ci->name << " from deleted nick " << nc->display << " to " << newowner->display;
+ ci->SetFounder(newowner);
+ ci->successor = NULL;
+ }
+ 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;
+
+ for (unsigned j = 0; j < ci->GetAccessCount(); ++j)
+ {
+ ChanAccess *ca = ci->GetAccess(j);
+ NickCore *anc = findcore(ca->mask);
+
+ if (anc && anc == nc)
+ {
+ ci->EraseAccess(j);
+ break;
+ }
+ }
+
+ 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 OnPreHelp(CommandSource &source, const std::vector<Anope::string> &params)
+ {
+ if (!params.empty() || source.owner->nick != Config->ChanServ)
+ return;
+ source.Reply(_("\002%s\002 allows you to register and control various\n"
+ "aspects of channels. %s can often prevent\n"
+ "malicious users from \"taking over\" channels by limiting\n"
+ "who is allowed channel operator privileges. Available\n"
+ "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.\n "),
+ Config->ChanServ.c_str(), Config->ChanServ.c_str(), Config->UseStrictPrivMsgString.c_str(), Config->ChanServ.c_str(), Config->UseStrictPrivMsgString.c_str(), Config->ChanServ.c_str(), Config->ChanServ.c_str(), source.command.c_str());
+ }
+
+ void OnPostHelp(CommandSource &source, const std::vector<Anope::string> &params)
+ {
+ if (!params.empty() || source.owner->nick != Config->ChanServ)
+ return;
+ if (Config->CSExpire >= 86400)
+ source.Reply(_(" \n"
+ "Note that any channel which is not used for %d days\n"
+ "(i.e. which no user on the channel's access list enters\n"
+ "for that period of time) will be automatically dropped."), Config->CSExpire / 86400);
+ if (source.u->IsServicesOper())
+ source.Reply(_(" \n"
+ "Services Operators can also drop any channel without needing\n"
+ "to identify via password, and may view the access, akick,\n"
+ "and level setting lists for any channel."));
+ }
+};
+
+MODULE_INIT(ChanServCore)
+
diff --git a/modules/pseudoclients/gl_main.cpp b/modules/pseudoclients/gl_main.cpp
new file mode 100644
index 000000000..c7ccf9209
--- /dev/null
+++ b/modules/pseudoclients/gl_main.cpp
@@ -0,0 +1,93 @@
+/* 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 MyGlobalService : public GlobalService
+{
+ void ServerGlobal(Server *s, const Anope::string &message)
+ {
+ if (s != Me && !s->HasFlag(SERVER_JUPED))
+ notice_server(Config->Global, s, "%s", message.c_str());
+ for (unsigned i = 0, j = s->GetLinks().size(); i < j; ++i)
+ this->ServerGlobal(s->GetLinks()[i], message);
+ }
+
+ public:
+ MyGlobalService(Module *m) : GlobalService(m) { }
+
+ void SendGlobal(BotInfo *sender, const Anope::string &source, const Anope::string &message)
+ {
+ if (Me->GetLinks().empty())
+ return;
+
+ Anope::string rmessage;
+
+ if (!source.empty() && !Config->AnonymousGlobal)
+ rmessage = "[" + source + "] " + message;
+ else
+ rmessage = message;
+
+ this->ServerGlobal(Me->GetLinks().front(), rmessage);
+ }
+};
+
+class GlobalCore : public Module
+{
+ MyGlobalService myglobalservice;
+
+ public:
+ GlobalCore(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE),
+ myglobalservice(this)
+ {
+ this->SetAuthor("Anope");
+
+ BotInfo *Global = findbot(Config->Global);
+ if (Global == NULL)
+ throw ModuleException("No bot named " + Config->Global);
+
+ ModuleManager::RegisterService(&this->myglobalservice);
+
+ Implementation i[] = { I_OnRestart, I_OnShutdown, I_OnNewServer, I_OnPreHelp };
+ ModuleManager::Attach(i, this, sizeof(i) / sizeof(Implementation));
+ }
+
+ void OnRestart()
+ {
+ if (Config->GlobalOnCycle)
+ global->SendGlobal(findbot(Config->Global), "", Config->GlobalOnCycleMessage);
+ }
+
+ void OnShutdown()
+ {
+ if (Config->GlobalOnCycle)
+ global->SendGlobal(findbot(Config->Global), "", Config->GlobalOnCycleMessage);
+ }
+
+ void OnNewServer(Server *s)
+ {
+ if (Config->GlobalOnCycle && !Config->GlobalOnCycleUP.empty())
+ notice_server(Config->Global, s, "%s", Config->GlobalOnCycleUP.c_str());
+ }
+
+ void OnPreHelp(CommandSource &source, const std::vector<Anope::string> &params)
+ {
+ if (!params.empty() || source.owner->nick != Config->Global)
+ return;
+ source.Reply(_("%s commands:\n"), Config->Global.c_str());
+ }
+};
+
+MODULE_INIT(GlobalCore)
+
diff --git a/modules/pseudoclients/global.h b/modules/pseudoclients/global.h
new file mode 100644
index 000000000..454e879c2
--- /dev/null
+++ b/modules/pseudoclients/global.h
@@ -0,0 +1,20 @@
+#ifndef GLOBAL_H
+#define GLOBAL_H
+
+class GlobalService : public Service
+{
+ public:
+ GlobalService(Module *m) : Service(m, "Global") { }
+
+ /** Send out a global message to all users
+ * @param sender Our client which should send the global
+ * @param source The sender of the global
+ * @param message The message
+ */
+ virtual void SendGlobal(BotInfo *sender, const Anope::string &source, const Anope::string &message) = 0;
+};
+
+static service_reference<GlobalService> global("Global");
+
+#endif // GLOBAL_H
+
diff --git a/modules/pseudoclients/hs_main.cpp b/modules/pseudoclients/hs_main.cpp
new file mode 100644
index 000000000..a6cc6bbb7
--- /dev/null
+++ b/modules/pseudoclients/hs_main.cpp
@@ -0,0 +1,85 @@
+/* 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 HostServCore : public Module
+{
+ public:
+ HostServCore(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE)
+ {
+ this->SetAuthor("Anope");
+
+ if (!ircd || !ircd->vhost)
+ throw ModuleException("Your IRCd does not suppor vhosts");
+
+ BotInfo *HostServ = findbot(Config->HostServ);
+ if (HostServ == NULL)
+ throw ModuleException("No bot named " + Config->HostServ);
+
+ Implementation i[] = { I_OnNickIdentify, I_OnNickUpdate, I_OnPreHelp };
+ ModuleManager::Attach(i, this, sizeof(i) / sizeof(Implementation));
+ }
+
+ void OnNickIdentify(User *u)
+ {
+ HostInfo *ho = NULL;
+ NickAlias *na = findnick(u->nick);
+ if (na && na->hostinfo.HasVhost())
+ ho = &na->hostinfo;
+ else
+ {
+ na = findnick(u->Account()->display);
+ if (na && na->hostinfo.HasVhost())
+ ho = &na->hostinfo;
+ }
+ if (ho == NULL)
+ return;
+
+ if (u->vhost.empty() || !u->vhost.equals_cs(na->hostinfo.GetHost()) || (!na->hostinfo.GetIdent().empty() && !u->GetVIdent().equals_cs(na->hostinfo.GetIdent())))
+ {
+ ircdproto->SendVhost(u, na->hostinfo.GetIdent(), na->hostinfo.GetHost());
+ if (ircd->vhost)
+ {
+ u->vhost = na->hostinfo.GetHost();
+ u->UpdateHost();
+ }
+ if (ircd->vident && !na->hostinfo.GetIdent().empty())
+ u->SetVIdent(na->hostinfo.GetIdent());
+
+ BotInfo *bi = findbot(Config->HostServ);
+ if (bi)
+ {
+ if (!na->hostinfo.GetIdent().empty())
+ u->SendMessage(bi, _("Your vhost of \002%s\002@\002%s\002 is now activated."), na->hostinfo.GetIdent().c_str(), na->hostinfo.GetHost().c_str());
+ else
+ u->SendMessage(bi, _("Your vhost of \002%s\002 is now activated."), na->hostinfo.GetHost().c_str());
+ }
+ }
+ }
+
+ void OnNickUpdate(User *u)
+ {
+ this->OnNickIdentify(u);
+ }
+
+ void OnPreHelp(CommandSource &source, const std::vector<Anope::string> &params)
+ {
+ if (!params.empty() || source.owner->nick != Config->HostServ)
+ return;
+ source.Reply(_("%s commands:\n"), Config->HostServ.c_str());
+ }
+};
+
+MODULE_INIT(HostServCore)
+
diff --git a/modules/pseudoclients/memoserv.h b/modules/pseudoclients/memoserv.h
new file mode 100644
index 000000000..0bf0c79ca
--- /dev/null
+++ b/modules/pseudoclients/memoserv.h
@@ -0,0 +1,41 @@
+#ifndef MEMOSERV_H
+#define MEMOSERV_H
+
+class MemoServService : public Service
+{
+ public:
+ enum MemoResult
+ {
+ MEMO_SUCCESS,
+ MEMO_INVALID_TARGET,
+ MEMO_TOO_FAST,
+ MEMO_TARGET_FULL
+ };
+
+ MemoServService(Module *m) : Service(m, "MemoServ") { }
+
+ /** Retrieve the memo info for a nick or channel
+ * @param target Target
+ * @param ischan Set to true if target is a channel
+ * @return A memoinfo structure or NULL
+ */
+ virtual MemoInfo *GetMemoInfo(const Anope::string &target, bool &ischan) = 0;
+
+ /** Sends a memo.
+ * @param source The source of the memo, can be anythin.
+ * @param target The target of the memo, nick or channel.
+ * @param message Memo text
+ * @param force true to force the memo, restrictions/delays etc are not checked
+ */
+ virtual MemoResult Send(const Anope::string &source, const Anope::string &target, const Anope::string &message, bool force = false) = 0;
+
+ /** Check for new memos and notify the user if there are any
+ * @param u The user
+ */
+ virtual void Check(User *u) = 0;
+};
+
+static service_reference<MemoServService> memoserv("MemoServ");
+
+#endif // MEMOSERV_H
+
diff --git a/modules/pseudoclients/ms_main.cpp b/modules/pseudoclients/ms_main.cpp
new file mode 100644
index 000000000..bb6d7847e
--- /dev/null
+++ b/modules/pseudoclients/ms_main.cpp
@@ -0,0 +1,228 @@
+/* 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"
+
+static BotInfo *MemoServ;
+
+static bool SendMemoMail(NickCore *nc, MemoInfo *mi, Memo *m)
+{
+ Anope::string message = Anope::printf(translate(nc, _(
+ "Hi %s\n"
+ " \n"
+ "You've just received a new memo from %s. This is memo number %d.\n"
+ " \n"
+ "Memo text:\n"
+ " \n"
+ "%s")), nc->display.c_str(), m->sender.c_str(), mi->GetIndex(m), m->text.c_str());
+
+ return Mail(nc, translate(nc, _("New memo")), message);
+}
+
+class MyMemoServService : public MemoServService
+{
+ public:
+ MyMemoServService(Module *m) : MemoServService(m) { }
+
+ MemoInfo *GetMemoInfo(const Anope::string &target, bool &ischan)
+ {
+ if (!target.empty() && target[0] == '#')
+ {
+ ischan = true;
+ ChannelInfo *ci = cs_findchan(target);
+ if (ci != NULL)
+ return &ci->memos;
+ }
+ else
+ {
+ ischan = false;
+ NickAlias *na = findnick(target);
+ if (na != NULL)
+ return &na->nc->memos;
+ }
+
+ return NULL;
+ }
+
+ MemoResult Send(const Anope::string &source, const Anope::string &target, const Anope::string &message, bool force)
+ {
+ bool ischan;
+ MemoInfo *mi = this->GetMemoInfo(target, ischan);
+
+ if (mi == NULL)
+ return MEMO_INVALID_TARGET;
+
+ User *sender = finduser(source);
+ if (sender != NULL && !sender->HasPriv("memoserv/no-limit") && !force)
+ {
+ if (Config->MSSendDelay > 0 && sender->lastmemosend + Config->MSSendDelay > Anope::CurTime)
+ return MEMO_TOO_FAST;
+ else if (!mi->memomax)
+ return MEMO_TARGET_FULL;
+ else if (mi->memomax > 0 && mi->memos.size() >= mi->memomax)
+ return MEMO_TARGET_FULL;
+ else if (mi->HasIgnore(sender))
+ return MEMO_SUCCESS;
+ }
+
+ if (sender != NULL)
+ sender->lastmemosend = Anope::CurTime;
+
+ Memo *m = new Memo();
+ mi->memos.push_back(m);
+ m->sender = source;
+ m->time = Anope::CurTime;
+ m->text = message;
+ m->SetFlag(MF_UNREAD);
+
+ FOREACH_MOD(I_OnMemoSend, OnMemoSend(source, target, mi, m));
+
+ if (ischan)
+ {
+ ChannelInfo *ci = cs_findchan(target);
+
+ if (ci->c)
+ {
+ for (CUserList::iterator it = ci->c->users.begin(), it_end = ci->c->users.end(); it != it_end; ++it)
+ {
+ UserContainer *cu = *it;
+
+ if (ci->HasPriv(cu->user, CA_MEMO))
+ {
+ if (cu->user->Account() && cu->user->Account()->HasFlag(NI_MEMO_RECEIVE))
+ cu->user->SendMessage(MemoServ, MEMO_NEW_X_MEMO_ARRIVED, ci->name.c_str(), Config->UseStrictPrivMsgString.c_str(), Config->MemoServ.c_str(), ci->name.c_str(), mi->memos.size());
+ }
+ }
+ }
+ }
+ else
+ {
+ NickCore *nc = findnick(target)->nc;
+
+ if (nc->HasFlag(NI_MEMO_RECEIVE))
+ {
+ for (std::list<NickAlias *>::iterator it = nc->aliases.begin(), it_end = nc->aliases.end(); it != it_end; ++it)
+ {
+ NickAlias *na = *it;
+ User *user = finduser(na->nick);
+ if (user && user->IsIdentified())
+ user->SendMessage(MemoServ, MEMO_NEW_MEMO_ARRIVED, source.c_str(), Config->UseStrictPrivMsgString.c_str(), Config->MemoServ.c_str(), mi->memos.size());
+ }
+ }
+
+ /* let's get out the mail if set in the nickcore - certus */
+ if (nc->HasFlag(NI_MEMO_MAIL))
+ SendMemoMail(nc, mi, m);
+ }
+
+ return MEMO_SUCCESS;
+ }
+
+ void Check(User *u)
+ {
+ NickCore *nc = u->Account();
+ if (!nc)
+ return;
+
+ unsigned i = 0, end = nc->memos.memos.size(), newcnt = 0;
+ for (; i < end; ++i)
+ {
+ if (nc->memos.memos[i]->HasFlag(MF_UNREAD))
+ ++newcnt;
+ }
+ if (newcnt > 0)
+ u->SendMessage(MemoServ, newcnt == 1 ? _("You have 1 new memo.") : _("You have %d new memos."), newcnt);
+ if (nc->memos.memomax > 0 && nc->memos.memos.size() >= nc->memos.memomax)
+ {
+ if (nc->memos.memos.size() > nc->memos.memomax)
+ u->SendMessage(MemoServ, _("You are over your maximum number of memos (%d). You will be unable to receive any new memos until you delete some of your current ones."), nc->memos.memomax);
+ else
+ u->SendMessage(MemoServ, _("You have reached your maximum number of memos (%d). You will be unable to receive any new memos until you delete some of your current ones."), nc->memos.memomax);
+ }
+ }
+};
+
+class MemoServCore : public Module
+{
+ MyMemoServService mymemoserv;
+ public:
+ MemoServCore(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE),
+ mymemoserv(this)
+ {
+ this->SetAuthor("Anope");
+
+ MemoServ = findbot(Config->MemoServ);
+ if (MemoServ == NULL)
+ throw ModuleException("No bot named " + Config->MemoServ);
+
+ Implementation i[] = { I_OnNickIdentify, I_OnJoinChannel, I_OnUserAway, I_OnNickUpdate, I_OnPreHelp, I_OnPostHelp };
+ ModuleManager::Attach(i, this, sizeof(i) / sizeof(Implementation));
+
+ ModuleManager::RegisterService(&this->mymemoserv);
+ }
+
+ void OnNickIdentify(User *u)
+ {
+ this->mymemoserv.Check(u);
+ }
+
+ void OnJoinChannel(User *u, Channel *c)
+ {
+ if (c->ci && c->ci->HasPriv(u, CA_MEMO) && c->ci->memos.memos.size() > 0)
+ {
+ if (c->ci->memos.memos.size() == 1)
+ u->SendMessage(MemoServ, _("There is \002%d\002 memo on channel %s."), c->ci->memos.memos.size(), c->ci->name.c_str());
+ else
+ u->SendMessage(MemoServ, _("There are \002%d\002 memos on channel %s."), c->ci->memos.memos.size(), c->ci->name.c_str());
+ }
+ }
+
+ void OnUserAway(User *u, const Anope::string &message)
+ {
+ if (message.empty())
+ this->mymemoserv.Check(u);
+ }
+
+ void OnNickUpdate(User *u)
+ {
+ this->mymemoserv.Check(u);
+ }
+
+ void OnPreHelp(CommandSource &source, const std::vector<Anope::string> &params)
+ {
+ if (!params.empty() || source.owner->nick != Config->MemoServ)
+ return;
+ source.Reply(_("\002%s\002 is a utility allowing IRC users to send short\n"
+ "messages to other IRC users, whether they are online at\n"
+ "the time or not, or to channels(*). Both the sender's\n"
+ "nickname and the target nickname or channel must be\n"
+ "registered in order to send a memo.\n"
+ "%s's commands include:"), Config->MemoServ.c_str(), Config->MemoServ.c_str());
+ }
+
+ void OnPostHelp(CommandSource &source, const std::vector<Anope::string> &params)
+ {
+ if (!params.empty() || source.owner->nick != Config->MemoServ)
+ return;
+ source.Reply(_(" \n"
+ "Type \002%s%s HELP \037command\037\002 for help on any of the\n"
+ "above commands.\n"
+ "(*) By default, any user with at least level 10 access on a\n"
+ " channel can read that channel's memos. This can be\n"
+ " changed with the %s \002LEVELS\002 command."), Config->UseStrictPrivMsgString.c_str(), Config->MemoServ.c_str(), Config->ChanServ.c_str());
+ }
+};
+
+MODULE_INIT(MemoServCore)
+
diff --git a/modules/pseudoclients/nickserv.h b/modules/pseudoclients/nickserv.h
new file mode 100644
index 000000000..b58dbd65f
--- /dev/null
+++ b/modules/pseudoclients/nickserv.h
@@ -0,0 +1,15 @@
+#ifndef NICKSERV_H
+#define NICKSERV_H
+
+class NickServService : public Service
+{
+ public:
+ NickServService(Module *m) : Service(m, "NickServ") { }
+
+ virtual void Validate(User *u) = 0;
+};
+
+static service_reference<NickServService> nickserv("NickServ");
+
+#endif // NICKSERV_H
+
diff --git a/modules/pseudoclients/ns_main.cpp b/modules/pseudoclients/ns_main.cpp
new file mode 100644
index 000000000..57323d0a5
--- /dev/null
+++ b/modules/pseudoclients/ns_main.cpp
@@ -0,0 +1,307 @@
+/* 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"
+
+static BotInfo *NickServ;
+
+class MyNickServService : public NickServService
+{
+ public:
+ MyNickServService(Module *m) : NickServService(m) { }
+
+ void Validate(User *u)
+ {
+ NickAlias *na = findnick(u->nick);
+ if (!na)
+ return;
+
+ if (na->nc->HasFlag(NI_SUSPENDED))
+ {
+ u->SendMessage(NickServ, NICK_X_SUSPENDED, u->nick.c_str());
+ u->Collide(na);
+ return;
+ }
+
+ if (!u->IsIdentified() && !u->fingerprint.empty() && na->nc->FindCert(u->fingerprint))
+ {
+ u->SendMessage(NickServ, _("SSL Fingerprint accepted, you are now identified."));
+ u->Identify(na);
+ return;
+ }
+
+ if (!na->nc->HasFlag(NI_SECURE) && u->IsRecognized())
+ {
+ na->last_seen = Anope::CurTime;
+ Anope::string last_usermask = u->GetIdent() + "@" + u->GetDisplayedHost();
+ na->last_usermask = last_usermask;
+ na->last_realname = u->realname;
+ return;
+ }
+
+ if (u->IsRecognized() || !na->nc->HasFlag(NI_KILL_IMMED))
+ {
+ if (na->nc->HasFlag(NI_SECURE))
+ u->SendMessage(NickServ, NICK_IS_SECURE, Config->UseStrictPrivMsgString.c_str(), Config->NickServ.c_str());
+ else
+ u->SendMessage(NickServ, NICK_IS_REGISTERED, Config->UseStrictPrivMsgString.c_str(), Config->NickServ.c_str());
+ }
+
+ if (na->nc->HasFlag(NI_KILLPROTECT) && !u->IsRecognized())
+ {
+ if (na->nc->HasFlag(NI_KILL_IMMED))
+ {
+ u->SendMessage(NickServ, FORCENICKCHANGE_NOW);
+ u->Collide(na);
+ }
+ else if (na->nc->HasFlag(NI_KILL_QUICK))
+ {
+ u->SendMessage(NickServ, _("If you do not change within 20 seconds, I will change your nick."));
+ new NickServCollide(u, 20);
+ }
+ else
+ {
+ u->SendMessage(NickServ, _("If you do not change within one minute, I will change your nick."));
+ new NickServCollide(u, 60);
+ }
+ }
+
+ }
+};
+
+class ExpireCallback : public CallBack
+{
+ public:
+ ExpireCallback(Module *owner) : CallBack(owner, Config->ExpireTimeout, Anope::CurTime, true) { }
+
+ void Tick(time_t)
+ {
+ if (noexpire || readonly)
+ return;
+
+ for (nickalias_map::const_iterator it = NickAliasList.begin(), it_end = NickAliasList.end(); it != it_end; )
+ {