aboutsummaryrefslogtreecommitdiff
/*       +------------------------------------+
 *       | Inspire Internet Relay Chat Daemon |
 *       +------------------------------------+
 *
 *  InspIRCd: (C) 2002-2011 InspIRCd Development Team
 * See: http://wiki.inspircd.org/Credits
 *
 * This program is free but copyrighted software; see
 *            the file COPYING for details.
 *
 * ---------------------------------------------------
 */

#include "inspircd.h"
#include "command_parse.h"
#include "cull_list.h"

/* $ModDesc: Provides fake clients that respond to messages. */

namespace m_serverbots
{

/** Command definition
 */
class Alias
{
 public:
	/** The text of the alias command */
	irc::string AliasedCommand;

	/** Text to replace with */
	std::string ReplaceFormat;

	/** Nickname required to perform alias */
	std::string RequiredNick;

	/** RequiredNick must be on a ulined server */
	bool ULineOnly;

	/** Requires oper? */
	bool OperOnly;

	/** Format that must be matched for use */
	std::string format;
};

typedef std::multimap<irc::string, Alias>::iterator AliasIter;

class AliasFormatSubst : public FormatSubstitute
{
 public:
	SubstMap info;
	const std::string &line;
	AliasFormatSubst(const std::string &Line) : line(Line) {}
	std::string lookup(const std::string& key)
	{
		if (isdigit(key[0]))
		{
			int index = atoi(key.c_str());
			irc::spacesepstream ss(line);
			bool everything_after = (key.find('-') != std::string::npos);
			bool good = true;
			std::string word;

			for (int j = 0; j < index && good; j++)
				good = ss.GetToken(word);

			if (everything_after)
			{
				std::string more;
				while (ss.GetToken(more))
				{
					word.append(" ");
					word.append(more);
				}
			}

			return word;
		}
		SubstMap::iterator i = info.find(key);
		if (i != info.end())
			return i->second;
		return "";
	}
};

class ServerBot : public FakeUser
{
 public:
	ServerBot(const std::string& uid) : FakeUser(uid, ServerInstance->Config->ServerName) {}
	virtual const std::string& GetFullHost()
	{
		return this->User::GetFullHost();
	}
};

class BotData
{
 public:
	std::multimap<irc::string, Alias> Aliases;
	ServerBot* const bot;
	BotData(ServerBot* Bot) : bot(Bot) {}

	void HandleMessage(LocalUser* user, const std::string& text)
	{
		if(!user)
			return;
		irc::spacesepstream ss(text);
		std::string command, params;
		ss.GetToken(command);
		params = ss.GetRemaining();

		std::pair<AliasIter, AliasIter> range = Aliases.equal_range(command);

		for(AliasIter i = range.first; i != range.second; ++i)
			if(DoAlias(user, &i->second, params, text))
				return;

		// also support no-command aliases (presumably they have format checks)
		range = Aliases.equal_range("");
		for(AliasIter i = range.first; i != range.second; ++i)
			if(DoAlias(user, &i->second, text, text))
				return;
	}

	bool DoAlias(LocalUser *user, Alias *a, const std::string& params, const std::string& text)
	{
		/* Does it match the pattern? */
		if (!a->format.empty())
		{
			if (!InspIRCd::Match(params, a->format))
				return false;
		}

		if ((a->OperOnly) && (!IS_OPER(user)))
			return 0;

		if (!a->RequiredNick.empty())
		{
			User* u = ServerInstance->FindNick(a->RequiredNick);
			if (!u)
			{
				user->WriteFrom(bot, "NOTICE %s :%s is currently unavailable. Please try again later.",
					user->nick.c_str(), a->RequiredNick.c_str());
				return true;
			}
			if (a->ULineOnly && !ServerInstance->ULine(u->server))
			{
				ServerInstance->SNO->WriteToSnoMask('a', "NOTICE -- Service "+a->RequiredNick+" required by alias "+std::string(a->AliasedCommand.c_str())+" is not on a u-lined server, possibly underhanded antics detected!");
				user->WriteFrom(bot, "NOTICE %s :%s is an imposter! Please inform an IRC operator as soon as possible.",
					user->nick.c_str(), a->RequiredNick.c_str());
				return true;
			}
		}

		/* Now, search and replace in a copy of the original_line, replacing $1 through $9 and $1- etc */

		irc::sepstream commands(a->ReplaceFormat, '\n');
		std::string scommand;
		while (commands.GetToken(scommand))
		{
			DoCommand(scommand, user, text);
		}
		return true;
	}

	void DoCommand(const std::string& format, LocalUser* user, const std::string &text)
	{
		AliasFormatSubst subst(text);
		user->PopulateInfoMap(subst.info);
		std::string bothost = bot->GetFullHost();
		subst.info["bot"] = bot->nick;
		subst.info["fullbot"] = bothost;
		std::string result = subst.format(format);

		irc::tokenstream ss(result);
		std::vector<std::string> pars;
		std::string command, token;

		ss.GetToken(command);
		if (irc::string(command) == "BOTNOTICE")
		{
			ss.GetToken(token);
			user->Write(":%s NOTICE %s :%s", bothost.c_str(), user->nick.c_str(), token.c_str());
			return;
		}

		while (ss.GetToken(token) && (pars.size() <= MAXPARAMETERS))
		{
			pars.push_back(token);
		}
		CmdResult res = ServerInstance->Parser->CallHandler(command, pars, user);
		FOREACH_MOD(I_OnPostCommand,OnPostCommand(command, pars, user, res,text));
	}

	std::string GetVar(std::string varname, const std::string &original_line)
	{
		irc::spacesepstream ss(original_line);
		int index = varname[1] - '0';
		bool everything_after = (varname.length() == 3);
		std::string word;

		for (int j = 0; j < index; j++)
			ss.GetToken(word);

		if (everything_after)
			return ss.GetRemaining();

		ss.GetToken(word);
		return word;
	}
};

class BotTracker : public SimpleExtItem<BotData>
{
 public:
	std::map<std::string, BotData*> bots;
	BotTracker(Module* Creator) : SimpleExtItem<BotData>(EXTENSIBLE_USER, "serverbot", Creator) {}

	void free(void* item)
	{
		BotData* ext = static_cast<BotData*>(item);
		if (!ext)
			return;
		bots.erase(ext->bot->nick);
		ServerInstance->GlobalCulls->AddItem(ext->bot);
		delete ext;
	}
};

class ModuleServerBots : public Module
{
	BotTracker dataExt;
	bool recursing;
	int botID;
 public:
	ModuleServerBots() : dataExt(this), recursing(false), botID(0) {}

	void early_init()
	{
		ServerInstance->Modules->AddService(dataExt);
	}

	void init()
	{
		ServerInstance->Modules->Attach(I_OnUserMessage, this);
	}

	Version GetVersion()
	{
		return Version("Provides fake clients for handling IRCd commands.", VF_VENDOR);
	}

	void OnUserMessage(User *user, void *dest, int target_type, const std::string &text, char status, const CUList &exempt_list)
	{
		if (target_type != TYPE_USER)
			return;
		User* b = (User*)dest;
		BotData* bot = dataExt.get(b);
		if (!bot)
			return;

		if (recursing)
		{
			user->WriteFrom(bot->bot, "NOTICE %s :Your command caused a recursive bot message which was not processed.", user->nick.c_str());
			return;
		}

		recursing = true;
		bot->HandleMessage(IS_LOCAL(user), text);
		recursing = false;
	}

	void ReadConfig(ConfigReadStatus&)
	{
		std::map<std::string, BotData*> oldbots;
		oldbots.swap(dataExt.bots);

		ConfigTagList tags = ServerInstance->Config->GetTags("bot");
		for(ConfigIter i = tags.first; i != tags.second; i++)
		{
			ConfigTag* tag = i->second;
			// UID is of the form "12!BOT"
			std::string nick = tag->getString("nick");
			if (nick.empty())
				continue;
			std::map<std::string, BotData*>::iterator found = oldbots.find(nick);
			ServerBot* bot;
			if (found != oldbots.end())
			{
				dataExt.bots.insert(*found);
				bot = found->second->bot;
				found->second->Aliases.clear();
				oldbots.erase(found);
			}
			else
			{
				User* bump = ServerInstance->FindNick(nick);
				if (bump)
					bump->ChangeNick(bump->uuid, true);
				std::string uid = ConvToStr(++botID) + "!BOT";
				bot = new ServerBot(uid);
				BotData* bd = new BotData(bot);
				dataExt.set(bot, bd);
				dataExt.bots.insert(std::make_pair(nick, bd));

				bot->ChangeNick(nick, true);
			}
			bot->ident = tag->getString("ident", "bot");
			bot->host = tag->getString("host", ServerInstance->Config->ServerName);
			bot->dhost = bot->host;
			bot->fullname = tag->getString("name", "Server-side Bot");
			bot->InvalidateCache();
			std::string oper = tag->getString("oper", "Server_Bot");
			if (!oper.empty())
			{
				OperIndex::iterator iter = ServerInstance->Config->oper_blocks.find(" " + oper);
				if (iter != ServerInstance->Config->oper_blocks.end())
					bot->oper = iter->second;
				else
					bot->oper = new OperInfo(oper);
			}
		}
		for(std::map<std::string, BotData*>::iterator i = oldbots.begin(); i != oldbots.end(); i++)
		{
			ServerInstance->GlobalCulls->AddItem(i->second->bot);
		}

		tags = ServerInstance->Config->GetTags("botcmd");
		for(ConfigIter i = tags.first; i != tags.second; i++)
		{
			ConfigTag* tag = i->second;
			std::string botnick = tag->getString("bot");
			std::map<std::string, BotData*>::iterator found = dataExt.bots.find(botnick);
			if (found == dataExt.bots.end())
				continue;
			BotData* bot = found->second;
			Alias a;
			a.AliasedCommand = tag->getString("text").c_str();
			tag->readString("replace", a.ReplaceFormat, true);
			a.RequiredNick = tag->getString("requires");
			a.ULineOnly = tag->getBool("uline");
			a.OperOnly = tag->getBool("operonly");
			a.format = tag->getString("format");

			bot->Aliases.insert(std::make_pair(a.AliasedCommand, a));
		}
 	}
};

}

using m_serverbots::ModuleServerBots;

MODULE_INIT(ModuleServerBots)