anope

- supernets anope source code & configuration
git clone git://git.acid.vegas/anope.git
Log | Files | Refs | Archive | README

cs_log.cpp (12191B)

      1 /* ChanServ core functions
      2  *
      3  * (C) 2003-2022 Anope Team
      4  * Contact us at team@anope.org
      5  *
      6  * Please read COPYING and README for further details.
      7  *
      8  * Based on the original code of Epona by Lara.
      9  * Based on the original code of Services by Andy Church.
     10  */
     11 
     12 #include "module.h"
     13 #include "modules/cs_log.h"
     14 
     15 struct LogSettingImpl : LogSetting, Serializable
     16 {
     17 	LogSettingImpl() : Serializable("LogSetting")
     18 	{
     19 	}
     20 
     21 	~LogSettingImpl()
     22 	{
     23 		ChannelInfo *ci = ChannelInfo::Find(chan);
     24 		if (ci)
     25 		{
     26 			LogSettings *ls = ci->GetExt<LogSettings>("logsettings");
     27 			if (ls)
     28 			{
     29 				LogSettings::iterator it = std::find((*ls)->begin(), (*ls)->end(), this);
     30 				if (it != (*ls)->end())
     31 					(*ls)->erase(it);
     32 			}
     33 		}
     34 	}
     35 
     36 	void Serialize(Serialize::Data &data) const anope_override
     37 	{
     38 		data["ci"] << chan;
     39 		data["service_name"] << service_name;
     40 		data["command_service"] << command_service;
     41 		data["command_name"] << command_name;
     42 		data["method"] << method;
     43 		data["extra"] << extra;
     44 		data["creator"] << creator;
     45 		data.SetType("created", Serialize::Data::DT_INT); data["created"] << created;
     46 	}
     47 
     48 	static Serializable* Unserialize(Serializable *obj, Serialize::Data &data)
     49 	{
     50 		Anope::string sci;
     51 		data["ci"] >> sci;
     52 
     53 		ChannelInfo *ci = ChannelInfo::Find(sci);
     54 		if (ci == NULL)
     55 			return NULL;
     56 
     57 		LogSettingImpl *ls;
     58 		if (obj)
     59 			ls = anope_dynamic_static_cast<LogSettingImpl *>(obj);
     60 		else
     61 		{
     62 			LogSettings *lsettings = ci->Require<LogSettings>("logsettings");
     63 			ls = new LogSettingImpl();
     64 			(*lsettings)->push_back(ls);
     65 		}
     66 
     67 		ls->chan = ci->name;
     68 		data["service_name"] >> ls->service_name;
     69 		data["command_service"] >> ls->command_service;
     70 		data["command_name"] >> ls->command_name;
     71 		data["method"] >> ls->method;
     72 		data["extra"] >> ls->extra;
     73 		data["creator"] >> ls->creator;
     74 		data["created"] >> ls->created;
     75 
     76 		return ls;
     77 	}
     78 };
     79 
     80 struct LogSettingsImpl : LogSettings
     81 {
     82 	LogSettingsImpl(Extensible *) { }
     83 
     84 	~LogSettingsImpl()
     85 	{
     86 		for (iterator it = (*this)->begin(); it != (*this)->end();)
     87 		{
     88 			LogSetting *ls = *it;
     89 			++it;
     90 			delete ls;
     91 		}
     92 	}
     93 
     94 	LogSetting *Create() anope_override
     95 	{
     96 		return new LogSettingImpl();
     97 	}
     98 };
     99 
    100 class CommandCSLog : public Command
    101 {
    102 public:
    103 	CommandCSLog(Module *creator) : Command(creator, "chanserv/log", 1, 4)
    104 	{
    105 		this->SetDesc(_("Configures channel logging settings"));
    106 		this->SetSyntax(_("\037channel\037"));
    107 		this->SetSyntax(_("\037channel\037 \037command\037 \037method\037 [\037status\037]"));
    108 	}
    109 
    110 	void Execute(CommandSource &source, const std::vector<Anope::string> &params) anope_override
    111 	{
    112 		const Anope::string &channel = params[0];
    113 
    114 		ChannelInfo *ci = ChannelInfo::Find(channel);
    115 		if (ci == NULL)
    116 			source.Reply(CHAN_X_NOT_REGISTERED, channel.c_str());
    117 		else if (!source.AccessFor(ci).HasPriv("SET") && !source.HasPriv("chanserv/administration"))
    118 			source.Reply(ACCESS_DENIED);
    119 		else if (params.size() == 1)
    120 		{
    121 			LogSettings *ls = ci->Require<LogSettings>("logsettings");
    122 			if (!ls || (*ls)->empty())
    123 				source.Reply(_("There currently are no logging configurations for %s."), ci->name.c_str());
    124 			else
    125 			{
    126 				ListFormatter list(source.GetAccount());
    127 				list.AddColumn(_("Number")).AddColumn(_("Service")).AddColumn(_("Command")).AddColumn(_("Method")).AddColumn("");
    128 
    129 				for (unsigned i = 0; i < (*ls)->size(); ++i)
    130 				{
    131 					const LogSetting *log = (*ls)->at(i);
    132 
    133 					ListFormatter::ListEntry entry;
    134 					entry["Number"] = stringify(i + 1);
    135 					entry["Service"] = log->command_service;
    136 					entry["Command"] = !log->command_name.empty() ? log->command_name : log->service_name;
    137 					entry["Method"] = log->method;
    138 					entry[""] = log->extra;
    139 					list.AddEntry(entry);
    140 				}
    141 
    142 				source.Reply(_("Log list for %s:"), ci->name.c_str());
    143 
    144 				std::vector<Anope::string> replies;
    145 				list.Process(replies);
    146 
    147 				for (unsigned i = 0; i < replies.size(); ++i)
    148 					source.Reply(replies[i]);
    149 			}
    150 		}
    151 		else if (params.size() > 2)
    152 		{
    153 			if (Anope::ReadOnly)
    154 			{
    155 				source.Reply(READ_ONLY_MODE);
    156 				return;
    157 			}
    158 
    159 			LogSettings *ls = ci->Require<LogSettings>("logsettings");
    160 			const Anope::string &command = params[1];
    161 			const Anope::string &method = params[2];
    162 			const Anope::string &extra = params.size() > 3 ? params[3] : "";
    163 
    164 			size_t sl = command.find('/');
    165 			if (sl == Anope::string::npos)
    166 			{
    167 				source.Reply(_("%s is not a valid command."), command.c_str());
    168 				return;
    169 			}
    170 
    171 			Anope::string service = command.substr(0, sl),
    172 				command_name = command.substr(sl + 1);
    173 			BotInfo *bi = BotInfo::Find(service, true);
    174 
    175 			Anope::string service_name;
    176 
    177 			/* Allow either a command name or a service name. */
    178 			if (bi && bi->commands.count(command_name))
    179 			{
    180 				/* Get service name from command */
    181 				service_name = bi->commands[command_name].name;
    182 			}
    183 			else if (ServiceReference<Command>("Command", command.lower()))
    184 			{
    185 				/* This is the service name, don't use any specific command */
    186 				service_name = command;
    187 				bi = NULL;
    188 				command_name.clear();
    189 			}
    190 			else
    191 			{
    192 				source.Reply(_("%s is not a valid command."), command.c_str());
    193 				return;
    194 			}
    195 
    196 			if (!method.equals_ci("MESSAGE") && !method.equals_ci("NOTICE") && !method.equals_ci("MEMO"))
    197 			{
    198 				source.Reply(_("%s is not a valid logging method."), method.c_str());
    199 				return;
    200 			}
    201 
    202 			for (unsigned i = 0; i < extra.length(); ++i)
    203 				if (ModeManager::GetStatusChar(extra[i]) == 0)
    204 				{
    205 					source.Reply(_("%c is an unknown status mode."), extra[i]);
    206 					return;
    207 				}
    208 
    209 			bool override = !source.AccessFor(ci).HasPriv("SET");
    210 
    211 			for (unsigned i = (*ls)->size(); i > 0; --i)
    212 			{
    213 				LogSetting *log = (*ls)->at(i - 1);
    214 
    215 				if (log->service_name == service_name && log->method.equals_ci(method) && command_name.equals_ci(log->command_name))
    216 				{
    217 					if (log->extra == extra)
    218 					{
    219 						Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to remove logging for " << command << " with method " << method << (extra == "" ? "" : " ") << extra;
    220 						source.Reply(_("Logging for command %s on %s with log method %s%s%s has been removed."), !log->command_name.empty() ? log->command_name.c_str() : log->service_name.c_str(), !log->command_service.empty() ? log->command_service.c_str() : "any service", method.c_str(), extra.empty() ? "" : " ", extra.empty() ? "" : extra.c_str());
    221 						delete log;
    222 					}
    223 					else
    224 					{
    225 						log->extra = extra;
    226 						Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to change logging for " << command << " to method " << method << (extra == "" ? "" : " ") << extra;
    227 						source.Reply(_("Logging changed for command %s on %s, now using log method %s%s%s."), !log->command_name.empty() ? log->command_name.c_str() : log->service_name.c_str(), !log->command_service.empty() ? log->command_service.c_str() : "any service", method.c_str(), extra.empty() ? "" : " ", extra.empty() ? "" : extra.c_str());
    228 					}
    229 					return;
    230 				}
    231 			}
    232 
    233 			LogSetting *log = new LogSettingImpl();
    234 			log->chan = ci->name;
    235 			log->service_name = service_name;
    236 			if (bi)
    237 				log->command_service = bi->nick;
    238 			log->command_name = command_name;
    239 			log->method = method;
    240 			log->extra = extra;
    241 			log->created = Anope::CurTime;
    242 			log->creator = source.GetNick();
    243 
    244 			(*ls)->push_back(log);
    245 
    246 			Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to log " << command << " with method " << method << (extra == "" ? "" : " ") << extra;
    247 
    248 			source.Reply(_("Logging is now active for command %s on %s, using log method %s%s%s."), !command_name.empty() ? command_name.c_str() : service_name.c_str(), bi ? bi->nick.c_str() : "any service", method.c_str(), extra.empty() ? "" : " ", extra.empty() ? "" : extra.c_str());
    249 		}
    250 		else
    251 			this->OnSyntaxError(source, "");
    252 	}
    253 
    254 	bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override
    255 	{
    256 		this->SendSyntax(source);
    257 		source.Reply(" ");
    258 		source.Reply(_("The %s command allows users to configure logging settings\n"
    259 				"for their channel. If no parameters are given this command\n"
    260 				"lists the current logging methods in place for this channel.\n"
    261 				" \n"
    262 				"Otherwise, \037command\037 must be a command name, and \037method\037\n"
    263 				"is one of the following logging methods:\n"
    264 				" \n"
    265 				" MESSAGE [status], NOTICE [status], MEMO\n"
    266 				" \n"
    267 				"Which are used to message, notice, and memo the channel respectively.\n"
    268 				"With MESSAGE or NOTICE you must have a service bot assigned to and joined\n"
    269 				"to your channel. Status may be a channel status such as @ or +.\n"
    270 				" \n"
    271 				"To remove a logging method use the same syntax as you would to add it.\n"
    272 				" \n"
    273 				"Example:\n"
    274 				" %s #anope chanserv/access MESSAGE @\n"
    275 				" Would message any channel operators whenever someone used the\n"
    276 				" ACCESS command on ChanServ on the channel."),
    277 				source.command.upper().c_str(), source.command.upper().c_str());
    278 		return true;
    279 	}
    280 };
    281 
    282 class CSLog : public Module
    283 {
    284 	ServiceReference<MemoServService> MSService;
    285 	CommandCSLog commandcslog;
    286 	ExtensibleItem<LogSettingsImpl> logsettings;
    287 	Serialize::Type logsetting_type;
    288 
    289 	struct LogDefault
    290 	{
    291 		Anope::string service, command, method;
    292 	};
    293 
    294 	std::vector<LogDefault> defaults;
    295 
    296  public:
    297 	CSLog(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR),
    298 		MSService("MemoServService", "MemoServ"), commandcslog(this),
    299 		logsettings(this, "logsettings"), logsetting_type("LogSetting", LogSettingImpl::Unserialize)
    300 	{
    301 
    302 	}
    303 
    304 	void OnReload(Configuration::Conf *conf) anope_override
    305 	{
    306 		Configuration::Block *block = conf->GetModule(this);
    307 		defaults.clear();
    308 
    309 		for (int i = 0; i < block->CountBlock("default"); ++i)
    310 		{
    311 			Configuration::Block *def = block->GetBlock("default", i);
    312 
    313 			LogDefault ld;
    314 
    315 			ld.service = def->Get<const Anope::string>("service");
    316 			ld.command = def->Get<const Anope::string>("command");
    317 			ld.method = def->Get<const Anope::string>("method");
    318 
    319 			defaults.push_back(ld);
    320 		}
    321 	}
    322 
    323 	void OnChanRegistered(ChannelInfo *ci) anope_override
    324 	{
    325 		if (defaults.empty())
    326 			return;
    327 
    328 		LogSettings *ls = logsettings.Require(ci);
    329 		for (unsigned i = 0; i < defaults.size(); ++i)
    330 		{
    331 			LogDefault &d = defaults[i];
    332 
    333 			LogSetting *log = new LogSettingImpl();
    334 			log->chan = ci->name;
    335 
    336 			if (!d.service.empty())
    337 			{
    338 				log->service_name = d.service.lower() + "/" + d.command.lower();
    339 				log->command_service = d.service;
    340 				log->command_name = d.command;
    341 			}
    342 			else
    343 				log->service_name = d.command;
    344 
    345 			spacesepstream sep(d.method);
    346 			sep.GetToken(log->method);
    347 			log->extra = sep.GetRemaining();
    348 
    349 			log->created = Anope::CurTime;
    350 			log->creator = ci->GetFounder() ? ci->GetFounder()->display : "(default)";
    351 
    352 			(*ls)->push_back(log);
    353 		}
    354 	}
    355 
    356 	void OnLog(Log *l) anope_override
    357 	{
    358 		if (l->type != LOG_COMMAND || l->u == NULL || l->c == NULL || l->ci == NULL || !Me || !Me->IsSynced())
    359 			return;
    360 
    361 		LogSettings *ls = logsettings.Get(l->ci);
    362 		if (ls)
    363 			for (unsigned i = 0; i < (*ls)->size(); ++i)
    364 			{
    365 				const LogSetting *log = (*ls)->at(i);
    366 
    367 				/* wrong command */
    368 				if (log->service_name != l->c->name)
    369 					continue;
    370 
    371 				/* if a command name is given check the service and the command */
    372 				if (!log->command_name.empty())
    373 				{
    374 					/* wrong service (only check if not a fantasy command, though) */
    375 					if (!l->source->c && log->command_service != l->source->service->nick)
    376 						continue;
    377 
    378 					if (!log->command_name.equals_ci(l->source->command))
    379 						continue;
    380 				}
    381 
    382 				Anope::string buffer = l->u->nick + " used " + l->source->command.upper() + " " + l->buf.str();
    383 
    384 				if (log->method.equals_ci("MEMO") && MSService && l->ci->WhoSends() != NULL)
    385 					MSService->Send(l->ci->WhoSends()->nick, l->ci->name, buffer, true);
    386 				else if (l->source->c)
    387 					/* Sending a channel message or notice in response to a fantasy command */;
    388 				else if (log->method.equals_ci("MESSAGE") && l->ci->c)
    389 				{
    390 					IRCD->SendPrivmsg(l->ci->WhoSends(), log->extra + l->ci->c->name, "%s", buffer.c_str());
    391 					l->ci->WhoSends()->lastmsg = Anope::CurTime;
    392 				}
    393 				else if (log->method.equals_ci("NOTICE") && l->ci->c)
    394 					IRCD->SendNotice(l->ci->WhoSends(), log->extra + l->ci->c->name, "%s", buffer.c_str());
    395 			}
    396 	}
    397 };
    398 
    399 MODULE_INIT(CSLog)