anope

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

os_sxline.cpp (22591B)

      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 class SXLineDelCallback : public NumberList
     15 {
     16 	XLineManager *xlm;
     17 	Command *command;
     18 	CommandSource &source;
     19 	unsigned deleted;
     20  public:
     21 	SXLineDelCallback(XLineManager *x, Command *c, CommandSource &_source, const Anope::string &numlist) : NumberList(numlist, true), xlm(x), command(c), source(_source), deleted(0)
     22 	{
     23 	}
     24 
     25 	~SXLineDelCallback()
     26 	{
     27 		if (!deleted)
     28 			source.Reply(_("No matching entries on the %s list."), source.command.c_str());
     29 		else if (deleted == 1)
     30 			source.Reply(_("Deleted 1 entry from the %s list."), source.command.c_str());
     31 		else
     32 			source.Reply(_("Deleted %d entries from the %s list."), deleted, source.command.c_str());
     33 	}
     34 
     35 	void HandleNumber(unsigned number) anope_override
     36 	{
     37 		if (!number)
     38 			return;
     39 
     40 		XLine *x = this->xlm->GetEntry(number - 1);
     41 
     42 		if (!x)
     43 			return;
     44 
     45 		Log(LOG_ADMIN, source, command) << "to remove " << x->mask << " from the list";
     46 
     47 		++deleted;
     48 		DoDel(this->xlm, source, x);
     49 	}
     50 
     51 	static void DoDel(XLineManager *xlm, CommandSource &source, XLine *x)
     52 	{
     53 		xlm->DelXLine(x);
     54 	}
     55 };
     56 
     57 class CommandOSSXLineBase : public Command
     58 {
     59  private:
     60 	virtual XLineManager* xlm() = 0;
     61 
     62 	virtual void OnAdd(CommandSource &source, const std::vector<Anope::string> &params) = 0;
     63 
     64 	void OnDel(CommandSource &source, const std::vector<Anope::string> &params)
     65 	{
     66 
     67 		if (!this->xlm() || this->xlm()->GetList().empty())
     68 		{
     69 			source.Reply(_("%s list is empty."), source.command.c_str());
     70 			return;
     71 		}
     72 
     73 		const Anope::string &mask = params.size() > 1 ? params[1] : "";
     74 
     75 		if (mask.empty())
     76 		{
     77 			this->OnSyntaxError(source, "DEL");
     78 			return;
     79 		}
     80 
     81 		if (isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos)
     82 		{
     83 			SXLineDelCallback list(this->xlm(), this, source, mask);
     84 			list.Process();
     85 		}
     86 		else
     87 		{
     88 			XLine *x = this->xlm()->HasEntry(mask);
     89 
     90 			if (!x)
     91 			{
     92 				source.Reply(_("\002%s\002 not found on the %s list."), mask.c_str(), source.command.c_str());
     93 				return;
     94 			}
     95 
     96 			FOREACH_MOD(OnDelXLine, (source, x, this->xlm()));
     97 
     98 			SXLineDelCallback::DoDel(this->xlm(), source, x);
     99 			source.Reply(_("\002%s\002 deleted from the %s list."), mask.c_str(), source.command.c_str());
    100 			Log(LOG_ADMIN, source, this) << "to remove " << mask << " from the list";
    101 		}
    102 
    103 		if (Anope::ReadOnly)
    104 			source.Reply(READ_ONLY_MODE);
    105 
    106 		return;
    107 	}
    108 
    109 	void ProcessList(CommandSource &source, const std::vector<Anope::string> &params, ListFormatter &list)
    110 	{
    111 		if (!this->xlm() || this->xlm()->GetList().empty())
    112 		{
    113 			source.Reply(_("%s list is empty."), source.command.c_str());
    114 			return;
    115 		}
    116 
    117 		const Anope::string &mask = params.size() > 1 ? params[1] : "";
    118 
    119 		if (!mask.empty() && isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos)
    120 		{
    121 			class SXLineListCallback : public NumberList
    122 			{
    123 				XLineManager *xlm;
    124 				CommandSource &source;
    125 				ListFormatter &list;
    126 			 public:
    127 				SXLineListCallback(XLineManager *x, CommandSource &_source, ListFormatter &_list, const Anope::string &numlist) : NumberList(numlist, false), xlm(x), source(_source), list(_list)
    128 				{
    129 				}
    130 
    131 				void HandleNumber(unsigned number) anope_override
    132 				{
    133 					if (!number)
    134 						return;
    135 
    136 					const XLine *x = this->xlm->GetEntry(number - 1);
    137 
    138 					if (!x)
    139 						return;
    140 
    141 					ListFormatter::ListEntry entry;
    142 					entry["Number"] = stringify(number);
    143 					entry["Mask"] = x->mask;
    144 					entry["By"] = x->by;
    145 					entry["Created"] = Anope::strftime(x->created, NULL, true);
    146 					entry["Expires"] = Anope::Expires(x->expires, source.nc);
    147 					entry["ID"] = x->id;
    148 					entry["Reason"] = x->reason;
    149 					list.AddEntry(entry);
    150 				}
    151 			}
    152 			sl_list(this->xlm(), source, list, mask);
    153 			sl_list.Process();
    154 		}
    155 		else
    156 		{
    157 			for (unsigned i = 0, end = this->xlm()->GetCount(); i < end; ++i)
    158 			{
    159 				const XLine *x = this->xlm()->GetEntry(i);
    160 
    161 				if (mask.empty() || mask.equals_ci(x->mask) || mask == x->id || Anope::Match(x->mask, mask, false, true))
    162 				{
    163 					ListFormatter::ListEntry entry;
    164 					entry["Number"] = stringify(i + 1);
    165 					entry["Mask"] = x->mask;
    166 					entry["By"] = x->by;
    167 					entry["Created"] = Anope::strftime(x->created, NULL, true);
    168 					entry["Expires"] = Anope::Expires(x->expires, source.nc);
    169 					entry["ID"] = x->id;
    170 					entry["Reason"] = x->reason;
    171 					list.AddEntry(entry);
    172 				}
    173 			}
    174 		}
    175 
    176 		if (list.IsEmpty())
    177 			source.Reply(_("No matching entries on the %s list."), source.command.c_str());
    178 		else
    179 		{
    180 			source.Reply(_("Current %s list:"), source.command.c_str());
    181 
    182 			std::vector<Anope::string> replies;
    183 			list.Process(replies);
    184 
    185 			for (unsigned i = 0; i < replies.size(); ++i)
    186 				source.Reply(replies[i]);
    187 		}
    188 	}
    189 
    190 	void OnList(CommandSource &source, const std::vector<Anope::string> &params)
    191 	{
    192 		ListFormatter list(source.GetAccount());
    193 		list.AddColumn(_("Number")).AddColumn(_("Mask")).AddColumn(_("Reason"));
    194 
    195 		this->ProcessList(source, params, list);
    196 	}
    197 
    198 	void OnView(CommandSource &source, const std::vector<Anope::string> &params)
    199 	{
    200 		ListFormatter list(source.GetAccount());
    201 		list.AddColumn(_("Number")).AddColumn(_("Mask")).AddColumn(_("By")).AddColumn(_("Created")).AddColumn(_("Expires"));
    202 		if (Config->GetModule("operserv")->Get<bool>("akillids"))
    203 			list.AddColumn(_("ID"));
    204 		list.AddColumn(_("Reason"));
    205 
    206 		this->ProcessList(source, params, list);
    207 	}
    208 
    209 	void OnClear(CommandSource &source)
    210 	{
    211 		FOREACH_MOD(OnDelXLine, (source, NULL, this->xlm()));
    212 
    213 		for (unsigned i = this->xlm()->GetCount(); i > 0; --i)
    214 		{
    215 			XLine *x = this->xlm()->GetEntry(i - 1);
    216 			this->xlm()->DelXLine(x);
    217 		}
    218 
    219 		Log(LOG_ADMIN, source, this) << "to CLEAR the list";
    220 		source.Reply(_("The %s list has been cleared."), source.command.c_str());
    221 		if (Anope::ReadOnly)
    222 			source.Reply(READ_ONLY_MODE);
    223 
    224 		return;
    225 	}
    226  public:
    227 	CommandOSSXLineBase(Module *creator, const Anope::string &cmd) : Command(creator, cmd, 1, 4)
    228 	{
    229 	}
    230 
    231 	const Anope::string GetDesc(CommandSource &source) const anope_override
    232 	{
    233 		return Anope::printf(Language::Translate(source.GetAccount(), _("Manipulate the %s list")), source.command.upper().c_str());
    234 	}
    235 
    236 	void Execute(CommandSource &source, const std::vector<Anope::string> &params) anope_override
    237 	{
    238 		const Anope::string &cmd = params[0];
    239 
    240 		if (cmd.equals_ci("ADD"))
    241 			return this->OnAdd(source, params);
    242 		else if (cmd.equals_ci("DEL"))
    243 			return this->OnDel(source, params);
    244 		else if (cmd.equals_ci("LIST"))
    245 			return this->OnList(source, params);
    246 		else if (cmd.equals_ci("VIEW"))
    247 			return this->OnView(source, params);
    248 		else if (cmd.equals_ci("CLEAR"))
    249 			return this->OnClear(source);
    250 		else
    251 			this->OnSyntaxError(source, "");
    252 
    253 		return;
    254 	}
    255 
    256 	virtual bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override = 0;
    257 };
    258 
    259 class CommandOSSNLine : public CommandOSSXLineBase
    260 {
    261 	XLineManager *xlm() anope_override
    262 	{
    263 		return this->snlines;
    264 	}
    265 
    266 	void OnAdd(CommandSource &source, const std::vector<Anope::string> &params) anope_override
    267 	{
    268 		if (!this->xlm())
    269 			return;
    270 
    271 		unsigned last_param = 2;
    272 		Anope::string param, expiry;
    273 
    274 		param = params.size() > 1 ? params[1] : "";
    275 		if (!param.empty() && param[0] == '+')
    276 		{
    277 			expiry = param;
    278 			param = params.size() > 2 ? params[2] : "";
    279 			last_param = 3;
    280 		}
    281 
    282 		time_t expires = !expiry.empty() ? Anope::DoTime(expiry) : Config->GetModule("operserv")->Get<time_t>("snlineexpiry", "30d");
    283 		/* If the expiry given does not contain a final letter, it's in days,
    284 		 * said the doc. Ah well.
    285 		 */
    286 		if (!expiry.empty() && isdigit(expiry[expiry.length() - 1]))
    287 			expires *= 86400;
    288 		/* Do not allow less than a minute expiry time */
    289 		if (expires && expires < 60)
    290 		{
    291 			source.Reply(BAD_EXPIRY_TIME);
    292 			return;
    293 		}
    294 		else if (expires > 0)
    295 			expires += Anope::CurTime;
    296 
    297 		if (param.empty())
    298 		{
    299 			this->OnSyntaxError(source, "ADD");
    300 			return;
    301 		}
    302 
    303 		Anope::string rest = param;
    304 		if (params.size() > last_param)
    305 			rest += " " + params[last_param];
    306 
    307 		if (rest.find(':') == Anope::string::npos)
    308 		{
    309 			this->OnSyntaxError(source, "ADD");
    310 			return;
    311 		}
    312 
    313 		sepstream sep(rest, ':');
    314 		Anope::string mask;
    315 		sep.GetToken(mask);
    316 		Anope::string reason = sep.GetRemaining();
    317 
    318 		if (mask.empty() || reason.empty())
    319 		{
    320 			this->OnSyntaxError(source, "ADD");
    321 			return;
    322 		}
    323 
    324 		if (mask[0] == '/' && mask[mask.length() - 1] == '/')
    325 		{
    326 			const Anope::string &regexengine = Config->GetBlock("options")->Get<const Anope::string>("regexengine");
    327 
    328 			if (regexengine.empty())
    329 			{
    330 				source.Reply(_("Regex is disabled."));
    331 				return;
    332 			}
    333 
    334 			ServiceReference<RegexProvider> provider("Regex", regexengine);
    335 			if (!provider)
    336 			{
    337 				source.Reply(_("Unable to find regex engine %s."), regexengine.c_str());
    338 				return;
    339 			}
    340 
    341 			try
    342 			{
    343 				Anope::string stripped_mask = mask.substr(1, mask.length() - 2);
    344 				delete provider->Compile(stripped_mask);
    345 			}
    346 			catch (const RegexException &ex)
    347 			{
    348 				source.Reply("%s", ex.GetReason().c_str());
    349 				return;
    350 			}
    351 		}
    352 
    353 		/* Clean up the last character of the mask if it is a space
    354 		 * See bug #761
    355 		 */
    356 		unsigned masklen = mask.length();
    357 		if (mask[masklen - 1] == ' ')
    358 			mask.erase(masklen - 1);
    359 
    360 		if (Config->GetModule("operserv")->Get<bool>("addakiller", "yes") && !source.GetNick().empty())
    361 			reason = "[" + source.GetNick() + "] " + reason;
    362 
    363 		if (mask.find_first_not_of("/.*?") == Anope::string::npos)
    364 		{
    365 			source.Reply(USERHOST_MASK_TOO_WIDE, mask.c_str());
    366 			return;
    367 		}
    368 
    369 		XLine *x = new XLine(mask, source.GetNick(), expires, reason);
    370 		if (Config->GetModule("operserv")->Get<bool>("akillids"))
    371 			x->id = XLineManager::GenerateUID();
    372 
    373 		unsigned int affected = 0;
    374 		for (user_map::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it)
    375 			if (this->xlm()->Check(it->second, x))
    376 				++affected;
    377 		float percent = static_cast<float>(affected) / static_cast<float>(UserListByNick.size()) * 100.0;
    378 
    379 		if (percent > 95)
    380 		{
    381 			source.Reply(USERHOST_MASK_TOO_WIDE, mask.c_str());
    382 			Log(LOG_ADMIN, source, this) << "tried to " << source.command << " " << percent << "% of the network (" << affected << " users)";
    383 			delete x;
    384 			return;
    385 		}
    386 
    387 		if (!this->xlm()->CanAdd(source, mask, expires, reason))
    388 			return;
    389 
    390 		EventReturn MOD_RESULT;
    391 		FOREACH_RESULT(OnAddXLine, MOD_RESULT, (source, x, this->xlm()));
    392 		if (MOD_RESULT == EVENT_STOP)
    393 		{
    394 			delete x;
    395 			return;
    396 		}
    397 
    398 		this->xlm()->AddXLine(x);
    399 
    400 		if (Config->GetModule("operserv")->Get<bool>("killonsnline", "yes"))
    401 		{
    402 			Anope::string rreason = "G-Lined: " + reason;
    403 
    404 			for (user_map::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it)
    405 			{
    406 				User *user = it->second;
    407 
    408 				if (!user->HasMode("OPER") && user->server != Me && this->xlm()->Check(user, x))
    409 					user->Kill(Me, rreason);
    410 			}
    411 
    412 			this->xlm()->Send(NULL, x);
    413 		}
    414 
    415 		source.Reply(_("\002%s\002 added to the %s list."), mask.c_str(), source.command.c_str());
    416 		Log(LOG_ADMIN, source, this) << "on " << mask << " (" << reason << "), expires in " << (expires ? Anope::Duration(expires - Anope::CurTime) : "never") << " [affects " << affected << " user(s) (" << percent << "%)]";
    417 		if (Anope::ReadOnly)
    418 			source.Reply(READ_ONLY_MODE);
    419 	}
    420 
    421 	ServiceReference<XLineManager> snlines;
    422  public:
    423 	CommandOSSNLine(Module *creator) : CommandOSSXLineBase(creator, "operserv/snline"), snlines("XLineManager", "xlinemanager/snline")
    424 	{
    425 		this->SetSyntax(_("ADD [+\037expiry\037] \037mask\037:\037reason\037"));
    426 		this->SetSyntax(_("DEL {\037mask\037 | \037entry-num\037 | \037list\037 | \037id\037}"));
    427 		this->SetSyntax(_("LIST [\037mask\037 | \037list\037 | \037id\037]"));
    428 		this->SetSyntax(_("VIEW [\037mask\037 | \037list\037 | \037id\037]"));
    429 		this->SetSyntax("CLEAR");
    430 	}
    431 
    432 	bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override
    433 	{
    434 		this->SendSyntax(source);
    435 		source.Reply(" ");
    436 		source.Reply(_("Allows Services Operators to manipulate the SNLINE list.  If\n"
    437 				"a user with a realname matching an SNLINE mask attempts to\n"
    438 				"connect, Services will not allow it to pursue his IRC\n"
    439 				"session."));
    440 		source.Reply(_(" \n"
    441 				"\002SNLINE ADD\002 adds the given realname mask to the SNLINE\n"
    442 				"list for the given reason (which \002must\002 be given).\n"
    443 				"\037expiry\037 is specified as an integer followed by one of \037d\037\n"
    444 				"(days), \037h\037 (hours), or \037m\037 (minutes). Combinations (such as\n"
    445 				"\0371h30m\037) are not permitted. If a unit specifier is not\n"
    446 				"included, the default is days (so \037+30\037 by itself means 30\n"
    447 				"days). To add an SNLINE which does not expire, use \037+0\037.  If the\n"
    448 				"realname mask to be added starts with a \037+\037, an expiry time must\n"
    449 				"be given, even if it is the same as the default.  The\n"
    450 				"current SNLINE default expiry time can be found with the\n"
    451 				"\002STATS AKILL\002 command.\n"
    452 				" \n"
    453 				"\002Note\002: because the realname mask may contain spaces, the\n"
    454 				"separator between it and the reason is a colon."));
    455 		const Anope::string &regexengine = Config->GetBlock("options")->Get<const Anope::string>("regexengine");
    456 		if (!regexengine.empty())
    457 		{
    458 			source.Reply(" ");
    459 			source.Reply(_("Regex matches are also supported using the %s engine.\n"
    460 					"Enclose your mask in // if this is desired."), regexengine.c_str());
    461 		}
    462 		source.Reply(_(" \n"
    463 				"The \002SNLINE DEL\002 command removes the given mask from the\n"
    464 				"SNLINE list if it is present.  If a list of entry numbers is\n"
    465 				"given, those entries are deleted.  (See the example for LIST\n"
    466 				"below.)\n"
    467 				" \n"
    468 				"The \002SNLINE LIST\002 command displays the SNLINE list.\n"
    469 				"If a wildcard mask is given, only those entries matching the\n"
    470 				"mask are displayed.  If a list of entry numbers is given,\n"
    471 				"only those entries are shown; for example:\n"
    472 				"   \002SNLINE LIST 2-5,7-9\002\n"
    473 				"      Lists SNLINE entries numbered 2 through 5 and 7\n"
    474 				"      through 9.\n"
    475 				" \n"
    476 				"\002SNLINE VIEW\002 is a more verbose version of \002SNLINE LIST\002, and\n"
    477 				"will show who added an SNLINE, the date it was added, and when\n"
    478 				"it expires, as well as the realname mask and reason.\n"
    479 				" \n"
    480 				"\002SNLINE CLEAR\002 clears all entries of the SNLINE list."));
    481 		return true;
    482 	}
    483 };
    484 
    485 class CommandOSSQLine : public CommandOSSXLineBase
    486 {
    487 	XLineManager *xlm() anope_override
    488 	{
    489 		return this->sqlines;
    490 	}
    491 
    492 	void OnAdd(CommandSource &source, const std::vector<Anope::string> &params) anope_override
    493 	{
    494 		if (!this->xlm())
    495 			return;
    496 
    497 		unsigned last_param = 2;
    498 		Anope::string expiry, mask;
    499 
    500 		mask = params.size() > 1 ? params[1] : "";
    501 		if (!mask.empty() && mask[0] == '+')
    502 		{
    503 			expiry = mask;
    504 			mask = params.size() > 2 ? params[2] : "";
    505 			last_param = 3;
    506 		}
    507 
    508 		time_t expires = !expiry.empty() ? Anope::DoTime(expiry) : Config->GetModule("operserv")->Get<time_t>("sqlineexpiry", "30d");
    509 		/* If the expiry given does not contain a final letter, it's in days,
    510 		 * said the doc. Ah well.
    511 		 */
    512 		if (!expiry.empty() && isdigit(expiry[expiry.length() - 1]))
    513 			expires *= 86400;
    514 		/* Do not allow less than a minute expiry time */
    515 		if (expires && expires < 60)
    516 		{
    517 			source.Reply(BAD_EXPIRY_TIME);
    518 			return;
    519 		}
    520 		else if (expires > 0)
    521 			expires += Anope::CurTime;
    522 
    523 		if (params.size() <= last_param)
    524 		{
    525 			this->OnSyntaxError(source, "ADD");
    526 			return;
    527 		}
    528 
    529 		Anope::string reason = params[last_param];
    530 		if (last_param == 2 && params.size() > 3)
    531 			reason += " " + params[3];
    532 
    533 		if (mask.empty() || reason.empty())
    534 		{
    535 			this->OnSyntaxError(source, "ADD");
    536 			return;
    537 		}
    538 
    539 		if (mask[0] == '/' && mask[mask.length() - 1] == '/')
    540 		{
    541 			const Anope::string &regexengine = Config->GetBlock("options")->Get<const Anope::string>("regexengine");
    542 
    543 			if (regexengine.empty())
    544 			{
    545 				source.Reply(_("Regex is disabled."));
    546 				return;
    547 			}
    548 
    549 			ServiceReference<RegexProvider> provider("Regex", regexengine);
    550 			if (!provider)
    551 			{
    552 				source.Reply(_("Unable to find regex engine %s."), regexengine.c_str());
    553 				return;
    554 			}
    555 
    556 			try
    557 			{
    558 				Anope::string stripped_mask = mask.substr(1, mask.length() - 2);
    559 				delete provider->Compile(stripped_mask);
    560 			}
    561 			catch (const RegexException &ex)
    562 			{
    563 				source.Reply("%s", ex.GetReason().c_str());
    564 				return;
    565 			}
    566 		}
    567 
    568 		if (Config->GetModule("operserv")->Get<bool>("addakiller", "yes") && !source.GetNick().empty())
    569 			reason = "[" + source.GetNick() + "] " + reason;
    570 
    571 		if (mask.find_first_not_of("./?*") == Anope::string::npos)
    572 		{
    573 			source.Reply(USERHOST_MASK_TOO_WIDE, mask.c_str());
    574 			return;
    575 		}
    576 
    577 		XLine *x = new XLine(mask, source.GetNick(), expires, reason);
    578 		if (Config->GetModule("operserv")->Get<bool>("akillids"))
    579 			x->id = XLineManager::GenerateUID();
    580 
    581 		unsigned int affected = 0;
    582 		for (user_map::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it)
    583 			if (this->xlm()->Check(it->second, x))
    584 				++affected;
    585 		float percent = static_cast<float>(affected) / static_cast<float>(UserListByNick.size()) * 100.0;
    586 
    587 		if (percent > 95)
    588 		{
    589 			source.Reply(USERHOST_MASK_TOO_WIDE, mask.c_str());
    590 			Log(LOG_ADMIN, source, this) << "tried to SQLine " << percent << "% of the network (" << affected << " users)";
    591 			delete x;
    592 			return;
    593 		}
    594 
    595 		if (!this->sqlines->CanAdd(source, mask, expires, reason))
    596 			return;
    597 
    598 		EventReturn MOD_RESULT;
    599 		FOREACH_RESULT(OnAddXLine, MOD_RESULT, (source, x, this->xlm()));
    600 		if (MOD_RESULT == EVENT_STOP)
    601 		{
    602 			delete x;
    603 			return;
    604 		}
    605 
    606 		this->xlm()->AddXLine(x);
    607 
    608 		if (Config->GetModule("operserv")->Get<bool>("killonsqline", "yes"))
    609 		{
    610 			Anope::string rreason = "Q-Lined: " + reason;
    611 
    612 			if (mask[0] == '#')
    613 			{
    614 				for (channel_map::const_iterator cit = ChannelList.begin(), cit_end = ChannelList.end(); cit != cit_end; ++cit)
    615 				{
    616 					Channel *c = cit->second;
    617 
    618 					if (!Anope::Match(c->name, mask, false, true))
    619 						continue;
    620 
    621 					std::vector<User *> users;
    622 					for (Channel::ChanUserList::iterator it = c->users.begin(), it_end = c->users.end(); it != it_end; ++it)
    623 					{
    624 						ChanUserContainer *uc = it->second;
    625 						User *user = uc->user;
    626 
    627 						if (!user->HasMode("OPER") && user->server != Me)
    628 							users.push_back(user);
    629 					}
    630 
    631 					for (unsigned i = 0; i < users.size(); ++i)
    632 						c->Kick(NULL, users[i], "%s", reason.c_str());
    633 				}
    634 			}
    635 			else
    636 			{
    637 				for (user_map::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it)
    638 				{
    639 					User *user = it->second;
    640 
    641 					if (!user->HasMode("OPER") && user->server != Me && this->xlm()->Check(user, x))
    642 						user->Kill(Me, rreason);
    643 				}
    644 			}
    645 
    646 			this->xlm()->Send(NULL, x);
    647 		}
    648 
    649 		source.Reply(_("\002%s\002 added to the %s list."), mask.c_str(), source.command.c_str());
    650 		Log(LOG_ADMIN, source, this) << "on " << mask << " (" << reason << "), expires in " << (expires ? Anope::Duration(expires - Anope::CurTime) : "never") << " [affects " << affected << " user(s) (" << percent << "%)]";
    651 		if (Anope::ReadOnly)
    652 			source.Reply(READ_ONLY_MODE);
    653 	}
    654 
    655 	ServiceReference<XLineManager> sqlines;
    656  public:
    657 	CommandOSSQLine(Module *creator) : CommandOSSXLineBase(creator, "operserv/sqline"), sqlines("XLineManager", "xlinemanager/sqline")
    658 	{
    659 		this->SetSyntax(_("ADD [+\037expiry\037] \037mask\037 \037reason\037"));
    660 		this->SetSyntax(_("DEL {\037mask\037 | \037entry-num\037 | \037list\037 | \037id\037}"));
    661 		this->SetSyntax(_("LIST [\037mask\037 | \037list\037 | \037id\037]"));
    662 		this->SetSyntax(_("VIEW [\037mask\037 | \037list\037 | \037id\037]"));
    663 		this->SetSyntax("CLEAR");
    664 	}
    665 
    666 	bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override
    667 	{
    668 		this->SendSyntax(source);
    669 		source.Reply(" ");
    670 		source.Reply(_("Allows Services Operators to manipulate the SQLINE list.  If\n"
    671 				"a user with a nick matching an SQLINE mask attempts to\n"
    672 				"connect, Services will not allow it to pursue his IRC\n"
    673 				"session.\n"
    674 				"If the first character of the mask is #, services will\n"
    675 				"prevent the use of matching channels. If the mask is a\n"
    676 				"regular expression, the expression will be matched against\n"
    677 				"channels too."));
    678 		source.Reply(_(" \n"
    679 				"\002SQLINE ADD\002 adds the given (nick's) mask to the SQLINE\n"
    680 				"list for the given reason (which \002must\002 be given).\n"
    681 				"\037expiry\037 is specified as an integer followed by one of \037d\037\n"
    682 				"(days), \037h\037 (hours), or \037m\037 (minutes). Combinations (such as\n"
    683 				"\0371h30m\037) are not permitted. If a unit specifier is not\n"
    684 				"included, the default is days (so \037+30\037 by itself means 30\n"
    685 				"days). To add an SQLINE which does not expire, use \037+0\037.\n"
    686 				"If the mask to be added starts with a \037+\037, an expiry time\n"
    687 				"must be given, even if it is the same as the default. The\n"
    688 				"current SQLINE default expiry time can be found with the\n"
    689 				"\002STATS AKILL\002 command."));
    690 		const Anope::string &regexengine = Config->GetBlock("options")->Get<const Anope::string>("regexengine");
    691 		if (!regexengine.empty())
    692 		{
    693 			source.Reply(" ");
    694 			source.Reply(_("Regex matches are also supported using the %s engine.\n"
    695 					"Enclose your mask in // if this is desired."), regexengine.c_str());
    696 		}
    697 		source.Reply(_(" \n"
    698 				"The \002SQLINE DEL\002 command removes the given mask from the\n"
    699 				"SQLINE list if it is present. If a list of entry numbers is\n"
    700 				"given, those entries are deleted. (See the example for LIST\n"
    701 				"below.)\n"
    702 				" \n"
    703 				"The \002SQLINE LIST\002 command displays the SQLINE list.\n"
    704 				"If a wildcard mask is given, only those entries matching the\n"
    705 				"mask are displayed. If a list of entry numbers is given,\n"
    706 				"only those entries are shown; for example:\n"
    707 				"   \002SQLINE LIST 2-5,7-9\002\n"
    708 				"      Lists SQLINE entries numbered 2 through 5 and 7\n"
    709 				"      through 9.\n"
    710 				" \n"
    711 				"\002SQLINE VIEW\002 is a more verbose version of \002SQLINE LIST\002, and\n"
    712 				"will show who added an SQLINE, the date it was added, and when\n"
    713 				"it expires, as well as the mask and reason.\n"
    714 				" \n"
    715 				"\002SQLINE CLEAR\002 clears all entries of the SQLINE list."));
    716 		return true;
    717 	}
    718 };
    719 
    720 class OSSXLine : public Module
    721 {
    722 	CommandOSSNLine commandossnline;
    723 	CommandOSSQLine commandossqline;
    724 
    725  public:
    726 	OSSXLine(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR),
    727 		commandossnline(this), commandossqline(this)
    728 	{
    729 	}
    730 };
    731 
    732 MODULE_INIT(OSSXLine)