anope

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

os_akill.cpp (13532B)

      1 /* OperServ 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 
     14 static ServiceReference<XLineManager> akills("XLineManager", "xlinemanager/sgline");
     15 
     16 class AkillDelCallback : public NumberList
     17 {
     18 	CommandSource &source;
     19 	unsigned deleted;
     20 	Command *cmd;
     21  public:
     22 	AkillDelCallback(CommandSource &_source, const Anope::string &numlist, Command *c) : NumberList(numlist, true), source(_source), deleted(0), cmd(c)
     23 	{
     24 	}
     25 
     26 	~AkillDelCallback()
     27 	{
     28 		if (!deleted)
     29 			source.Reply(_("No matching entries on the AKILL list."));
     30 		else if (deleted == 1)
     31 			source.Reply(_("Deleted 1 entry from the AKILL list."));
     32 		else
     33 			source.Reply(_("Deleted %d entries from the AKILL list."), deleted);
     34 	}
     35 
     36 	void HandleNumber(unsigned number) anope_override
     37 	{
     38 		if (!number)
     39 			return;
     40 
     41 		XLine *x = akills->GetEntry(number - 1);
     42 
     43 		if (!x)
     44 			return;
     45 
     46 		Log(LOG_ADMIN, source, cmd) << "to remove " << x->mask << " from the list";
     47 
     48 		++deleted;
     49 		DoDel(source, x);
     50 	}
     51 
     52 	static void DoDel(CommandSource &source, XLine *x)
     53 	{
     54 		akills->DelXLine(x);
     55 	}
     56 };
     57 
     58 class CommandOSAKill : public Command
     59 {
     60  private:
     61 	void DoAdd(CommandSource &source, const std::vector<Anope::string> &params)
     62 	{
     63 		Anope::string expiry, mask;
     64 
     65 		if (params.size() < 2)
     66 		{
     67 			this->OnSyntaxError(source, "ADD");
     68 			return;
     69 		}
     70 
     71 		spacesepstream sep(params[1]);
     72 		sep.GetToken(mask);
     73 
     74 		if (mask[0] == '+')
     75 		{
     76 			expiry = mask;
     77 			sep.GetToken(mask);
     78 		}
     79 
     80 		time_t expires = !expiry.empty() ? Anope::DoTime(expiry) : Config->GetModule("operserv")->Get<time_t>("autokillexpiry", "30d");
     81 		/* If the expiry given does not contain a final letter, it's in days,
     82 		 * said the doc. Ah well.
     83 		 */
     84 		if (!expiry.empty() && isdigit(expiry[expiry.length() - 1]))
     85 			expires *= 86400;
     86 		/* Do not allow less than a minute expiry time */
     87 		if (expires && expires < 60)
     88 		{
     89 			source.Reply(BAD_EXPIRY_TIME);
     90 			return;
     91 		}
     92 		else if (expires > 0)
     93 			expires += Anope::CurTime;
     94 
     95 		if (sep.StreamEnd())
     96 		{
     97 			this->OnSyntaxError(source, "ADD");
     98 			return;
     99 		}
    100 
    101 		Anope::string reason;
    102 		if (mask.find('#') != Anope::string::npos)
    103 		{
    104 			Anope::string remaining = sep.GetRemaining();
    105 
    106 			size_t co = remaining[0] == ':' ? 0 : remaining.rfind(" :");
    107 			if (co == Anope::string::npos)
    108 			{
    109 				this->OnSyntaxError(source, "ADD");
    110 				return;
    111 			}
    112 
    113 			if (co != 0)
    114 				++co;
    115 
    116 			reason = remaining.substr(co + 1);
    117 			mask += " " + remaining.substr(0, co);
    118 			mask.trim();
    119 		}
    120 		else
    121 			reason = sep.GetRemaining();
    122 
    123 		if (mask[0] == '/' && mask[mask.length() - 1] == '/')
    124 		{
    125 			const Anope::string &regexengine = Config->GetBlock("options")->Get<const Anope::string>("regexengine");
    126 
    127 			if (regexengine.empty())
    128 			{
    129 				source.Reply(_("Regex is disabled."));
    130 				return;
    131 			}
    132 
    133 			ServiceReference<RegexProvider> provider("Regex", regexengine);
    134 			if (!provider)
    135 			{
    136 				source.Reply(_("Unable to find regex engine %s."), regexengine.c_str());
    137 				return;
    138 			}
    139 
    140 			try
    141 			{
    142 				Anope::string stripped_mask = mask.substr(1, mask.length() - 2);
    143 				delete provider->Compile(stripped_mask);
    144 			}
    145 			catch (const RegexException &ex)
    146 			{
    147 				source.Reply("%s", ex.GetReason().c_str());
    148 				return;
    149 			}
    150 		}
    151 
    152 		User *targ = User::Find(mask, true);
    153 		if (targ)
    154 			mask = "*@" + targ->host;
    155 
    156 		if (Config->GetModule("operserv")->Get<bool>("addakiller", "yes") && !source.GetNick().empty())
    157 			reason = "[" + source.GetNick() + "] " + reason;
    158 
    159 		if (mask.find_first_not_of("/~@.*?") == Anope::string::npos)
    160 		{
    161 			source.Reply(USERHOST_MASK_TOO_WIDE, mask.c_str());
    162 			return;
    163 		}
    164 		else if (mask.find('@') == Anope::string::npos)
    165 		{
    166 			source.Reply(BAD_USERHOST_MASK);
    167 			return;
    168 		}
    169 
    170 		XLine *x = new XLine(mask, source.GetNick(), expires, reason);
    171 		if (Config->GetModule("operserv")->Get<bool>("akillids"))
    172 			x->id = XLineManager::GenerateUID();
    173 
    174 		unsigned int affected = 0;
    175 		for (user_map::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it)
    176 			if (akills->Check(it->second, x))
    177 				++affected;
    178 		float percent = static_cast<float>(affected) / static_cast<float>(UserListByNick.size()) * 100.0;
    179 
    180 		if (percent > 95)
    181 		{
    182 			source.Reply(USERHOST_MASK_TOO_WIDE, mask.c_str());
    183 			Log(LOG_ADMIN, source, this) << "tried to akill " << percent << "% of the network (" << affected << " users)";
    184 			delete x;
    185 			return;
    186 		}
    187 
    188 		if (!akills->CanAdd(source, mask, expires, reason))
    189 			return;
    190 
    191 		EventReturn MOD_RESULT;
    192 		FOREACH_RESULT(OnAddXLine, MOD_RESULT, (source, x, akills));
    193 		if (MOD_RESULT == EVENT_STOP)
    194 		{
    195 			delete x;
    196 			return;
    197 		}
    198 
    199 		akills->AddXLine(x);
    200 		if (Config->GetModule("operserv")->Get<bool>("akillonadd"))
    201 			akills->Send(NULL, x);
    202 
    203 		source.Reply(_("\002%s\002 added to the AKILL list."), mask.c_str());
    204 
    205 		Log(LOG_ADMIN, source, this) << "on " << mask << " (" << x->reason << "), expires in " << (expires ? Anope::Duration(expires - Anope::CurTime) : "never") << " [affects " << affected << " user(s) (" << percent << "%)]";
    206 		if (Anope::ReadOnly)
    207 			source.Reply(READ_ONLY_MODE);
    208 	}
    209 
    210 	void DoDel(CommandSource &source, const std::vector<Anope::string> &params)
    211 	{
    212 		const Anope::string &mask = params.size() > 1 ? params[1] : "";
    213 
    214 		if (mask.empty())
    215 		{
    216 			this->OnSyntaxError(source, "DEL");
    217 			return;
    218 		}
    219 
    220 		if (akills->GetList().empty())
    221 		{
    222 			source.Reply(_("AKILL list is empty."));
    223 			return;
    224 		}
    225 
    226 		if (isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos)
    227 		{
    228 			AkillDelCallback list(source, mask, this);
    229 			list.Process();
    230 		}
    231 		else
    232 		{
    233 			XLine *x = akills->HasEntry(mask);
    234 
    235 			if (!x)
    236 			{
    237 				source.Reply(_("\002%s\002 not found on the AKILL list."), mask.c_str());
    238 				return;
    239 			}
    240 
    241 			do
    242 			{
    243 				FOREACH_MOD(OnDelXLine, (source, x, akills));
    244 
    245 				Log(LOG_ADMIN, source, this) << "to remove " << x->mask << " from the list";
    246 				source.Reply(_("\002%s\002 deleted from the AKILL list."), x->mask.c_str());
    247 				AkillDelCallback::DoDel(source, x);
    248 			}
    249 			while ((x = akills->HasEntry(mask)));
    250 
    251 		}
    252 
    253 		if (Anope::ReadOnly)
    254 			source.Reply(READ_ONLY_MODE);
    255 
    256 		return;
    257 	}
    258 
    259 	void ProcessList(CommandSource &source, const std::vector<Anope::string> &params, ListFormatter &list)
    260 	{
    261 		const Anope::string &mask = params.size() > 1 ? params[1] : "";
    262 
    263 		if (!mask.empty() && isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos)
    264 		{
    265 			class ListCallback : public NumberList
    266 			{
    267 				CommandSource &source;
    268 				ListFormatter &list;
    269 			 public:
    270 				ListCallback(CommandSource &_source, ListFormatter &_list, const Anope::string &numstr) : NumberList(numstr, false), source(_source), list(_list)
    271 				{
    272 				}
    273 
    274 				void HandleNumber(unsigned number) anope_override
    275 				{
    276 					if (!number)
    277 						return;
    278 
    279 					const XLine *x = akills->GetEntry(number - 1);
    280 
    281 					if (!x)
    282 						return;
    283 
    284 					ListFormatter::ListEntry entry;
    285 					entry["Number"] = stringify(number);
    286 					entry["Mask"] = x->mask;
    287 					entry["Creator"] = x->by;
    288 					entry["Created"] = Anope::strftime(x->created, NULL, true);
    289 					entry["Expires"] = Anope::Expires(x->expires, source.nc);
    290 					entry["ID"] = x->id;
    291 					entry["Reason"] = x->reason;
    292 					this->list.AddEntry(entry);
    293 				}
    294 			}
    295 			nl_list(source, list, mask);
    296 			nl_list.Process();
    297 		}
    298 		else
    299 		{
    300 			for (unsigned i = 0, end = akills->GetCount(); i < end; ++i)
    301 			{
    302 				const XLine *x = akills->GetEntry(i);
    303 
    304 				if (mask.empty() || mask.equals_ci(x->mask) || mask == x->id || Anope::Match(x->mask, mask, false, true))
    305 				{
    306 					ListFormatter::ListEntry entry;
    307 					entry["Number"] = stringify(i + 1);
    308 					entry["Mask"] = x->mask;
    309 					entry["Creator"] = x->by;
    310 					entry["Created"] = Anope::strftime(x->created, NULL, true);
    311 					entry["Expires"] = Anope::Expires(x->expires, source.nc);
    312 					entry["ID"] = x->id;
    313 					entry["Reason"] = x->reason;
    314 					list.AddEntry(entry);
    315 				}
    316 			}
    317 		}
    318 
    319 		if (list.IsEmpty())
    320 			source.Reply(_("No matching entries on the AKILL list."));
    321 		else
    322 		{
    323 			source.Reply(_("Current AKILL list:"));
    324 
    325 			std::vector<Anope::string> replies;
    326 			list.Process(replies);
    327 
    328 			for (unsigned i = 0; i < replies.size(); ++i)
    329 				source.Reply(replies[i]);
    330 
    331 			source.Reply(_("End of AKILL list."));
    332 		}
    333 	}
    334 
    335 	void DoList(CommandSource &source, const std::vector<Anope::string> &params)
    336 	{
    337 		if (akills->GetList().empty())
    338 		{
    339 			source.Reply(_("AKILL list is empty."));
    340 			return;
    341 		}
    342 
    343 		ListFormatter list(source.GetAccount());
    344 		list.AddColumn(_("Number")).AddColumn(_("Mask")).AddColumn(_("Reason"));
    345 
    346 		this->ProcessList(source, params, list);
    347 	}
    348 
    349 	void DoView(CommandSource &source, const std::vector<Anope::string> &params)
    350 	{
    351 		if (akills->GetList().empty())
    352 		{
    353 			source.Reply(_("AKILL list is empty."));
    354 			return;
    355 		}
    356 
    357 		ListFormatter list(source.GetAccount());
    358 		list.AddColumn(_("Number")).AddColumn(_("Mask")).AddColumn(_("Creator")).AddColumn(_("Created")).AddColumn(_("Expires"));
    359 		if (Config->GetModule("operserv")->Get<bool>("akillids"))
    360 			list.AddColumn(_("ID"));
    361 		list.AddColumn(_("Reason"));
    362 
    363 		this->ProcessList(source, params, list);
    364 	}
    365 
    366 	void DoClear(CommandSource &source)
    367 	{
    368 
    369 		for (unsigned i = akills->GetCount(); i > 0; --i)
    370 		{
    371 			XLine *x = akills->GetEntry(i - 1);
    372 			FOREACH_MOD(OnDelXLine, (source, x, akills));
    373 			akills->DelXLine(x);
    374 		}
    375 
    376 		Log(LOG_ADMIN, source, this) << "to CLEAR the list";
    377 		source.Reply(_("The AKILL list has been cleared."));
    378 
    379 		if (Anope::ReadOnly)
    380 			source.Reply(READ_ONLY_MODE);
    381 	}
    382  public:
    383 	CommandOSAKill(Module *creator) : Command(creator, "operserv/akill", 1, 2)
    384 	{
    385 		this->SetDesc(_("Manipulate the AKILL list"));
    386 		this->SetSyntax(_("ADD [+\037expiry\037] \037mask\037 \037reason\037"));
    387 		this->SetSyntax(_("DEL {\037mask\037 | \037entry-num\037 | \037list\037 | \037id\037}"));
    388 		this->SetSyntax(_("LIST [\037mask\037 | \037list\037 | \037id\037]"));
    389 		this->SetSyntax(_("VIEW [\037mask\037 | \037list\037 | \037id\037]"));
    390 		this->SetSyntax("CLEAR");
    391 	}
    392 
    393 	void Execute(CommandSource &source, const std::vector<Anope::string> &params) anope_override
    394 	{
    395 		const Anope::string &cmd = params[0];
    396 
    397 		if (!akills)
    398 			return;
    399 
    400 		if (cmd.equals_ci("ADD"))
    401 			return this->DoAdd(source, params);
    402 		else if (cmd.equals_ci("DEL"))
    403 			return this->DoDel(source, params);
    404 		else if (cmd.equals_ci("LIST"))
    405 			return this->DoList(source, params);
    406 		else if (cmd.equals_ci("VIEW"))
    407 			return this->DoView(source, params);
    408 		else if (cmd.equals_ci("CLEAR"))
    409 			return this->DoClear(source);
    410 		else
    411 			this->OnSyntaxError(source, "");
    412 
    413 		return;
    414 	}
    415 
    416 	bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override
    417 	{
    418 		this->SendSyntax(source);
    419 		source.Reply(" ");
    420 		source.Reply(_("Allows Services Operators to manipulate the AKILL list. If\n"
    421 				"a user matching an AKILL mask attempts to connect, Services\n"
    422 				"will issue a KILL for that user and, on supported server\n"
    423 				"types, will instruct all servers to add a ban for the mask\n"
    424 				"which the user matched.\n"
    425 				" \n"
    426 				"\002AKILL ADD\002 adds the given mask to the AKILL\n"
    427 				"list for the given reason, which \002must\002 be given.\n"
    428 				"Mask should be in the format of nick!user@host#real name,\n"
    429 				"though all that is required is user@host. If a real name is specified,\n"
    430 				"the reason must be prepended with a :.\n"
    431 				"\037expiry\037 is specified as an integer followed by one of \037d\037\n"
    432 				"(days), \037h\037 (hours), or \037m\037 (minutes). Combinations (such as\n"
    433 				"\0371h30m\037) are not permitted. If a unit specifier is not\n"
    434 				"included, the default is days (so \037+30\037 by itself means 30\n"
    435 				"days). To add an AKILL which does not expire, use \037+0\037. If the\n"
    436 				"usermask to be added starts with a \037+\037, an expiry time must\n"
    437 				"be given, even if it is the same as the default. The\n"
    438 				"current AKILL default expiry time can be found with the\n"
    439 				"\002STATS AKILL\002 command."));
    440 		const Anope::string &regexengine = Config->GetBlock("options")->Get<const Anope::string>("regexengine");
    441 		if (!regexengine.empty())
    442 		{
    443 			source.Reply(" ");
    444 			source.Reply(_("Regex matches are also supported using the %s engine.\n"
    445 					"Enclose your mask in // if this is desired."), regexengine.c_str());
    446 		}
    447 		source.Reply(_(
    448 				" \n"
    449 				"The \002AKILL DEL\002 command removes the given mask from the\n"
    450 				"AKILL list if it is present.  If a list of entry numbers is\n"
    451 				"given, those entries are deleted.  (See the example for LIST\n"
    452 				"below.)\n"
    453 				" \n"
    454 				"The \002AKILL LIST\002 command displays the AKILL list.\n"
    455 				"If a wildcard mask is given, only those entries matching the\n"
    456 				"mask are displayed.  If a list of entry numbers is given,\n"
    457 				"only those entries are shown; for example:\n"
    458 				"   \002AKILL LIST 2-5,7-9\002\n"
    459 				"      Lists AKILL entries numbered 2 through 5 and 7\n"
    460 				"      through 9.\n"
    461 				"      \n"
    462 				"\002AKILL VIEW\002 is a more verbose version of \002AKILL LIST\002, and\n"
    463 				"will show who added an AKILL, the date it was added, and when\n"
    464 				"it expires, as well as the user@host/ip mask and reason.\n"
    465 				" \n"
    466 				"\002AKILL CLEAR\002 clears all entries of the AKILL list."));
    467 		return true;
    468 	}
    469 };
    470 
    471 class OSAKill : public Module
    472 {
    473 	CommandOSAKill commandosakill;
    474 
    475  public:
    476 	OSAKill(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR),
    477 		commandosakill(this)
    478 	{
    479 
    480 	}
    481 };
    482 
    483 MODULE_INIT(OSAKill)