anope

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

inspircd20.cpp (39170B)

      1 /* InspIRCd 2.0 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_mode.h"
     14 
     15 static unsigned int spanningtree_proto_ver = 0;
     16 
     17 static ServiceReference<IRCDProto> insp12("IRCDProto", "inspircd12");
     18 
     19 class InspIRCd20Proto : public IRCDProto
     20 {
     21  public:
     22 	InspIRCd20Proto(Module *creator) : IRCDProto(creator, "InspIRCd 2.0")
     23 	{
     24 		DefaultPseudoclientModes = "+I";
     25 		CanSVSNick = true;
     26 		CanSVSJoin = true;
     27 		CanSetVHost = true;
     28 		CanSetVIdent = true;
     29 		CanSQLine = true;
     30 		CanSZLine = true;
     31 		CanSVSHold = true;
     32 		CanCertFP = true;
     33 		RequiresID = true;
     34 		MaxModes = 20;
     35 	}
     36 
     37 	void SendConnect() anope_override
     38 	{
     39 		UplinkSocket::Message() << "CAPAB START 1202";
     40 		UplinkSocket::Message() << "CAPAB CAPABILITIES :PROTOCOL=1202";
     41 		UplinkSocket::Message() << "CAPAB END";
     42 		insp12->SendConnect();
     43 	}
     44 
     45 	void SendSASLMechanisms(std::vector<Anope::string> &mechanisms) anope_override
     46 	{
     47 		Anope::string mechlist;
     48 		for (unsigned i = 0; i < mechanisms.size(); ++i)
     49 			mechlist += "," + mechanisms[i];
     50 
     51 		UplinkSocket::Message(Me) << "METADATA * saslmechlist :" << (mechanisms.empty() ? "" : mechlist.substr(1));
     52 	}
     53 
     54 	void SendSVSKillInternal(const MessageSource &source, User *user, const Anope::string &buf) anope_override { insp12->SendSVSKillInternal(source, user, buf); }
     55 	void SendGlobalNotice(BotInfo *bi, const Server *dest, const Anope::string &msg) anope_override { insp12->SendGlobalNotice(bi, dest, msg); }
     56 	void SendGlobalPrivmsg(BotInfo *bi, const Server *dest, const Anope::string &msg) anope_override { insp12->SendGlobalPrivmsg(bi, dest, msg); }
     57 	void SendAkillDel(const XLine *x) anope_override { insp12->SendAkillDel(x); }
     58 	void SendTopic(const MessageSource &whosets, Channel *c) anope_override { insp12->SendTopic(whosets, c); };
     59 	void SendVhostDel(User *u) anope_override { insp12->SendVhostDel(u); }
     60 	void SendAkill(User *u, XLine *x) anope_override { insp12->SendAkill(u, x); }
     61 	void SendNumericInternal(int numeric, const Anope::string &dest, const Anope::string &buf) anope_override { insp12->SendNumericInternal(numeric, dest, buf); }
     62 	void SendModeInternal(const MessageSource &source, const Channel *dest, const Anope::string &buf) anope_override { insp12->SendModeInternal(source, dest, buf); }
     63 	void SendClientIntroduction(User *u) anope_override { insp12->SendClientIntroduction(u); }
     64 	void SendServer(const Server *server) anope_override { insp12->SendServer(server); }
     65 	void SendSquit(Server *s, const Anope::string &message) anope_override { insp12->SendSquit(s, message); }
     66 	void SendJoin(User *user, Channel *c, const ChannelStatus *status) anope_override { insp12->SendJoin(user, c, status); }
     67 	void SendSQLineDel(const XLine *x) anope_override { insp12->SendSQLineDel(x); }
     68 	void SendSQLine(User *u, const XLine *x) anope_override { insp12->SendSQLine(u, x); }
     69 	void SendVhost(User *u, const Anope::string &vident, const Anope::string &vhost) anope_override { insp12->SendVhost(u, vident, vhost); }
     70 	void SendSVSHold(const Anope::string &nick, time_t t) anope_override { insp12->SendSVSHold(nick, t); }
     71 	void SendSVSHoldDel(const Anope::string &nick) anope_override { insp12->SendSVSHoldDel(nick); }
     72 	void SendSZLineDel(const XLine *x) anope_override { insp12->SendSZLineDel(x); }
     73 	void SendSZLine(User *u, const XLine *x) anope_override { insp12->SendSZLine(u, x); }
     74 	void SendSVSJoin(const MessageSource &source, User *u, const Anope::string &chan, const Anope::string &other) anope_override { insp12->SendSVSJoin(source, u, chan, other); }
     75 	void SendSVSPart(const MessageSource &source, User *u, const Anope::string &chan, const Anope::string &param) anope_override { insp12->SendSVSPart(source, u, chan, param); }
     76 	void SendSWhois(const MessageSource &bi, const Anope::string &who, const Anope::string &mask) anope_override { insp12->SendSWhois(bi, who, mask); }
     77 	void SendBOB() anope_override { insp12->SendBOB(); }
     78 	void SendEOB() anope_override { insp12->SendEOB(); }
     79 	void SendGlobopsInternal(const MessageSource &source, const Anope::string &buf) { insp12->SendGlobopsInternal(source, buf); }
     80 	void SendLogin(User *u, NickAlias *na) anope_override { insp12->SendLogin(u, na); }
     81 	void SendLogout(User *u) anope_override { insp12->SendLogout(u); }
     82 	void SendChannel(Channel *c) anope_override { insp12->SendChannel(c); }
     83 	void SendSASLMessage(const SASL::Message &message) anope_override { insp12->SendSASLMessage(message); }
     84 	void SendSVSLogin(const Anope::string &uid, const Anope::string &acc, const Anope::string &vident, const Anope::string &vhost) anope_override { insp12->SendSVSLogin(uid, acc, vident, vhost); }
     85 	bool IsExtbanValid(const Anope::string &mask) anope_override { return insp12->IsExtbanValid(mask); }
     86 	bool IsIdentValid(const Anope::string &ident) anope_override { return insp12->IsIdentValid(ident); }
     87 };
     88 
     89 class InspIRCdAutoOpMode : public ChannelModeList
     90 {
     91  public:
     92 	InspIRCdAutoOpMode(char mode) : ChannelModeList("AUTOOP", mode)
     93 	{
     94 	}
     95 
     96 	bool IsValid(Anope::string &mask) const anope_override
     97 	{
     98 		// We can not validate this because we don't know about the
     99 		// privileges of the setter so just reject attempts to set it.
    100 		return false;
    101 	}
    102 };
    103 
    104 class InspIRCdExtBan : public ChannelModeVirtual<ChannelModeList>
    105 {
    106 	char ext;
    107 
    108  public:
    109 	InspIRCdExtBan(const Anope::string &mname, const Anope::string &basename, char extban) : ChannelModeVirtual<ChannelModeList>(mname, basename)
    110 		, ext(extban)
    111 	{
    112 	}
    113 
    114 	ChannelMode *Wrap(Anope::string &param) anope_override
    115 	{
    116 		param = Anope::string(ext) + ":" + param;
    117 		return ChannelModeVirtual<ChannelModeList>::Wrap(param);
    118 	}
    119 
    120 	ChannelMode *Unwrap(ChannelMode *cm, Anope::string &param) anope_override
    121 	{
    122 		if (cm->type != MODE_LIST || param.length() < 3 || param[0] != ext || param[1] != ':')
    123 			return cm;
    124 
    125 		param = param.substr(2);
    126 		return this;
    127 	}
    128 };
    129 
    130 namespace InspIRCdExtban
    131 {
    132 	class EntryMatcher : public InspIRCdExtBan
    133 	{
    134 	 public:
    135 		EntryMatcher(const Anope::string &mname, const Anope::string &mbase, char c) : InspIRCdExtBan(mname, mbase, c)
    136 		{
    137 		}
    138 
    139 		bool Matches(User *u, const Entry *e) anope_override
    140 		{
    141 			const Anope::string &mask = e->GetMask();
    142 			Anope::string real_mask = mask.substr(3);
    143 
    144 			return Entry(this->name, real_mask).Matches(u);
    145 		}
    146 	};
    147 
    148 	class ChannelMatcher : public InspIRCdExtBan
    149 	{
    150 	 public:
    151 		ChannelMatcher(const Anope::string &mname, const Anope::string &mbase, char c) : InspIRCdExtBan(mname, mbase, c)
    152 		{
    153 		}
    154 
    155 		bool Matches(User *u, const Entry *e) anope_override
    156 		{
    157 			const Anope::string &mask = e->GetMask();
    158 
    159 			Anope::string channel = mask.substr(3);
    160 
    161 			ChannelMode *cm = NULL;
    162 			if (channel[0] != '#')
    163 			{
    164 				char modeChar = ModeManager::GetStatusChar(channel[0]);
    165 				channel.erase(channel.begin());
    166 				cm = ModeManager::FindChannelModeByChar(modeChar);
    167 				if (cm != NULL && cm->type != MODE_STATUS)
    168 					cm = NULL;
    169 			}
    170 
    171 			Channel *c = Channel::Find(channel);
    172 			if (c != NULL)
    173 			{
    174 				ChanUserContainer *uc = c->FindUser(u);
    175 				if (uc != NULL)
    176 					if (cm == NULL || uc->status.HasMode(cm->mchar))
    177 						return true;
    178 			}
    179 
    180 			return false;
    181 		}
    182 	};
    183 
    184 	class AccountMatcher : public InspIRCdExtBan
    185 	{
    186 	 public:
    187 		AccountMatcher(const Anope::string &mname, const Anope::string &mbase, char c) : InspIRCdExtBan(mname, mbase, c)
    188 		{
    189 		}
    190 
    191 		bool Matches(User *u, const Entry *e) anope_override
    192 		{
    193 			const Anope::string &mask = e->GetMask();
    194 			Anope::string real_mask = mask.substr(2);
    195 
    196 			return u->IsIdentified() && real_mask.equals_ci(u->Account()->display);
    197 		}
    198 	};
    199 
    200 	class RealnameMatcher : public InspIRCdExtBan
    201 	{
    202 	 public:
    203 		RealnameMatcher(const Anope::string &mname, const Anope::string &mbase, char c) : InspIRCdExtBan(mname, mbase, c)
    204 		{
    205 		}
    206 
    207 		bool Matches(User *u, const Entry *e) anope_override
    208 		{
    209 			const Anope::string &mask = e->GetMask();
    210 			Anope::string real_mask = mask.substr(2);
    211 			return Anope::Match(u->realname, real_mask);
    212 		}
    213 	};
    214 
    215 	class ServerMatcher : public InspIRCdExtBan
    216 	{
    217 	 public:
    218 		ServerMatcher(const Anope::string &mname, const Anope::string &mbase, char c) : InspIRCdExtBan(mname, mbase, c)
    219 		{
    220 		}
    221 
    222 		bool Matches(User *u, const Entry *e) anope_override
    223 		{
    224 			const Anope::string &mask = e->GetMask();
    225 			Anope::string real_mask = mask.substr(2);
    226 			return Anope::Match(u->server->GetName(), real_mask);
    227 		}
    228 	};
    229 
    230 	class FingerprintMatcher : public InspIRCdExtBan
    231 	{
    232 	 public:
    233 		FingerprintMatcher(const Anope::string &mname, const Anope::string &mbase, char c) : InspIRCdExtBan(mname, mbase, c)
    234 		{
    235 		}
    236 
    237 		bool Matches(User *u, const Entry *e) anope_override
    238 		{
    239 			const Anope::string &mask = e->GetMask();
    240 			Anope::string real_mask = mask.substr(2);
    241 			return !u->fingerprint.empty() && Anope::Match(u->fingerprint, real_mask);
    242 		}
    243 	};
    244 
    245 	class UnidentifiedMatcher : public InspIRCdExtBan
    246 	{
    247 	 public:
    248 		UnidentifiedMatcher(const Anope::string &mname, const Anope::string &mbase, char c) : InspIRCdExtBan(mname, mbase, c)
    249 		{
    250 		}
    251 
    252 		bool Matches(User *u, const Entry *e) anope_override
    253 		{
    254 			const Anope::string &mask = e->GetMask();
    255 			Anope::string real_mask = mask.substr(2);
    256 			return !u->Account() && Entry("BAN", real_mask).Matches(u);
    257 		}
    258 	};
    259 }
    260 
    261 class ColonDelimitedParamMode : public ChannelModeParam
    262 {
    263  public:
    264 	ColonDelimitedParamMode(const Anope::string &modename, char modeChar) : ChannelModeParam(modename, modeChar, true) { }
    265 
    266 	bool IsValid(Anope::string &value) const anope_override
    267 	{
    268 		return IsValid(value, false);
    269 	}
    270 
    271 	bool IsValid(const Anope::string &value, bool historymode) const
    272 	{
    273 		if (value.empty())
    274 			return false; // empty param is never valid
    275 
    276 		Anope::string::size_type pos = value.find(':');
    277 		if ((pos == Anope::string::npos) || (pos == 0))
    278 			return false; // no ':' or it's the first char, both are invalid
    279 
    280 		Anope::string rest;
    281 		try
    282 		{
    283 			if (convertTo<int>(value, rest, false) <= 0)
    284 				return false; // negative numbers and zero are invalid
    285 
    286 			rest = rest.substr(1);
    287 			int n;
    288 			if (historymode)
    289 			{
    290 				// For the history mode, the part after the ':' is a duration and it
    291 				// can be in the user friendly "1d3h20m" format, make sure we accept that
    292 				n = Anope::DoTime(rest);
    293 			}
    294 			else
    295 				n = convertTo<int>(rest);
    296 
    297 			if (n <= 0)
    298 				return false;
    299 		}
    300 		catch (const ConvertException &e)
    301 		{
    302 			// conversion error, invalid
    303 			return false;
    304 		}
    305 
    306 		return true;
    307 	}
    308 };
    309 
    310 class SimpleNumberParamMode : public ChannelModeParam
    311 {
    312  public:
    313 	SimpleNumberParamMode(const Anope::string &modename, char modeChar) : ChannelModeParam(modename, modeChar, true) { }
    314 
    315 	bool IsValid(Anope::string &value) const anope_override
    316 	{
    317 		if (value.empty())
    318 			return false; // empty param is never valid
    319 
    320 		try
    321 		{
    322 			if (convertTo<int>(value) <= 0)
    323 				return false;
    324 		}
    325 		catch (const ConvertException &e)
    326 		{
    327 			// conversion error, invalid
    328 			return false;
    329 		}
    330 
    331 		return true;
    332 	}
    333 };
    334 
    335 class ChannelModeFlood : public ColonDelimitedParamMode
    336 {
    337  public:
    338 	ChannelModeFlood(char modeChar) : ColonDelimitedParamMode("FLOOD", modeChar) { }
    339 
    340 	bool IsValid(Anope::string &value) const anope_override
    341 	{
    342 		// The parameter of this mode is a bit different, it may begin with a '*',
    343 		// ignore it if that's the case
    344 		Anope::string v = value[0] == '*' ? value.substr(1) : value;
    345 		return ((!value.empty()) && (ColonDelimitedParamMode::IsValid(v)));
    346 	}
    347 };
    348 
    349 class ChannelModeHistory : public ColonDelimitedParamMode
    350 {
    351  public:
    352 	ChannelModeHistory(char modeChar) : ColonDelimitedParamMode("HISTORY", modeChar) { }
    353 
    354 	bool IsValid(Anope::string &value) const anope_override
    355 	{
    356 		return (ColonDelimitedParamMode::IsValid(value, true));
    357 	}
    358 };
    359 
    360 class ChannelModeRedirect : public ChannelModeParam
    361 {
    362  public:
    363 	ChannelModeRedirect(char modeChar) : ChannelModeParam("REDIRECT", modeChar, true) { }
    364 
    365 	bool IsValid(Anope::string &value) const anope_override
    366 	{
    367 		// The parameter of this mode is a channel, and channel names start with '#'
    368 		return ((!value.empty()) && (value[0] == '#'));
    369 	}
    370 };
    371 
    372 struct IRCDMessageAway : Message::Away
    373 {
    374 	IRCDMessageAway(Module *creator) : Message::Away(creator, "AWAY") { SetFlag(IRCDMESSAGE_REQUIRE_USER); }
    375 
    376 	void Run(MessageSource &source, const std::vector<Anope::string> &params) anope_override
    377 	{
    378 		std::vector<Anope::string> newparams(params);
    379 		if (newparams.size() > 1)
    380 			newparams.erase(newparams.begin());
    381 
    382 		Message::Away::Run(source, newparams);
    383 	}
    384 };
    385 
    386 struct IRCDMessageCapab : Message::Capab
    387 {
    388 	std::map<char, Anope::string> chmodes, umodes;
    389 
    390 	IRCDMessageCapab(Module *creator) : Message::Capab(creator, "CAPAB") { SetFlag(IRCDMESSAGE_SOFT_LIMIT); }
    391 
    392 	void Run(MessageSource &source, const std::vector<Anope::string> &params) anope_override
    393 	{
    394 		if (params[0].equals_cs("START"))
    395 		{
    396 			if (params.size() >= 2)
    397 				spanningtree_proto_ver = (Anope::string(params[1]).is_pos_number_only() ? convertTo<unsigned>(params[1]) : 0);
    398 
    399 			if (spanningtree_proto_ver < 1202)
    400 			{
    401 				UplinkSocket::Message() << "ERROR :Protocol mismatch, no or invalid protocol version given in CAPAB START";
    402 				Anope::QuitReason = "Protocol mismatch, no or invalid protocol version given in CAPAB START";
    403 				Anope::Quitting = true;
    404 				return;
    405 			}
    406 
    407 			/* reset CAPAB */
    408 			chmodes.clear();
    409 			umodes.clear();
    410 			Servers::Capab.insert("SERVERS");
    411 			Servers::Capab.insert("TOPICLOCK");
    412 			IRCD->CanSVSHold = false;
    413 			IRCD->DefaultPseudoclientModes = "+I";
    414 		}
    415 		else if (params[0].equals_cs("CHANMODES") && params.size() > 1)
    416 		{
    417 			spacesepstream ssep(params[1]);
    418 			Anope::string capab;
    419 
    420 			while (ssep.GetToken(capab))
    421 			{
    422 				Anope::string modename = capab.substr(0, capab.find('='));
    423 				Anope::string modechar = capab.substr(capab.find('=') + 1);
    424 				ChannelMode *cm = NULL;
    425 
    426 				if (modename.equals_cs("admin"))
    427 					cm = new ChannelModeStatus("PROTECT", modechar.length() > 1 ? modechar[1] : modechar[0], modechar.length() > 1 ? modechar[0] : 0, 3);
    428 				else if (modename.equals_cs("allowinvite"))
    429 				{
    430 					cm = new ChannelMode("ALLINVITE", modechar[0]);
    431 					ModeManager::AddChannelMode(new InspIRCdExtban::EntryMatcher("INVITEBAN", "BAN", 'A'));
    432 				}
    433 				else if (modename.equals_cs("auditorium"))
    434 					cm = new ChannelMode("AUDITORIUM", modechar[0]);
    435 				else if (modename.equals_cs("autoop"))
    436 					cm = new InspIRCdAutoOpMode(modechar[0]);
    437 				else if (modename.equals_cs("ban"))
    438 					cm = new ChannelModeList("BAN", modechar[0]);
    439 				else if (modename.equals_cs("banexception"))
    440 					cm = new ChannelModeList("EXCEPT", modechar[0]);
    441 				else if (modename.equals_cs("blockcaps"))
    442 				{
    443 					cm = new ChannelMode("BLOCKCAPS", modechar[0]);
    444 					ModeManager::AddChannelMode(new InspIRCdExtban::EntryMatcher("BLOCKCAPSBAN", "BAN", 'B'));
    445 				}
    446 				else if (modename.equals_cs("blockcolor"))
    447 				{
    448 					cm = new ChannelMode("BLOCKCOLOR", modechar[0]);
    449 					ModeManager::AddChannelMode(new InspIRCdExtban::EntryMatcher("BLOCKCOLORBAN", "BAN", 'c'));
    450 				}
    451 				else if (modename.equals_cs("c_registered"))
    452 					cm = new ChannelModeNoone("REGISTERED", modechar[0]);
    453 				else if (modename.equals_cs("censor"))
    454 					cm = new ChannelMode("CENSOR", modechar[0]);
    455 				else if (modename.equals_cs("delayjoin"))
    456 					cm = new ChannelMode("DELAYEDJOIN", modechar[0]);
    457 				else if (modename.equals_cs("delaymsg"))
    458 					cm = new SimpleNumberParamMode("DELAYMSG", modechar[0]);
    459 				else if (modename.equals_cs("filter"))
    460 					cm = new ChannelModeList("FILTER", modechar[0]);
    461 				else if (modename.equals_cs("flood"))
    462 					cm = new ChannelModeFlood(modechar[0]);
    463 				else if (modename.equals_cs("founder"))
    464 					cm = new ChannelModeStatus("OWNER", modechar.length() > 1 ? modechar[1] : modechar[0], modechar.length() > 1 ? modechar[0] : 0, 4);
    465 				else if (modename.equals_cs("halfop"))
    466 					cm = new ChannelModeStatus("HALFOP", modechar.length() > 1 ? modechar[1] : modechar[0], modechar.length() > 1 ? modechar[0] : 0, 1);
    467 				else if (modename.equals_cs("history"))
    468 					cm = new ChannelModeHistory(modechar[0]);
    469 				else if (modename.equals_cs("invex"))
    470 					cm = new ChannelModeList("INVITEOVERRIDE", modechar[0]);
    471 				else if (modename.equals_cs("inviteonly"))
    472 					cm = new ChannelMode("INVITE", modechar[0]);
    473 				else if (modename.equals_cs("joinflood"))
    474 					cm = new ColonDelimitedParamMode("JOINFLOOD", modechar[0]);
    475 				else if (modename.equals_cs("key"))
    476 					cm = new ChannelModeKey(modechar[0]);
    477 				else if (modename.equals_cs("kicknorejoin"))
    478 					cm = new SimpleNumberParamMode("NOREJOIN", modechar[0]);
    479 				else if (modename.equals_cs("limit"))
    480 					cm = new ChannelModeParam("LIMIT", modechar[0], true);
    481 				else if (modename.equals_cs("moderated"))
    482 					cm = new ChannelMode("MODERATED", modechar[0]);
    483 				else if (modename.equals_cs("nickflood"))
    484 					cm = new ColonDelimitedParamMode("NICKFLOOD", modechar[0]);
    485 				else if (modename.equals_cs("noctcp"))
    486 				{
    487 					cm = new ChannelMode("NOCTCP", modechar[0]);
    488 					ModeManager::AddChannelMode(new InspIRCdExtban::EntryMatcher("NOCTCPBAN", "BAN", 'C'));
    489 				}
    490 				else if (modename.equals_cs("noextmsg"))
    491 					cm = new ChannelMode("NOEXTERNAL", modechar[0]);
    492 				else if (modename.equals_cs("nokick"))
    493 				{
    494 					cm = new ChannelMode("NOKICK", modechar[0]);
    495 					ModeManager::AddChannelMode(new InspIRCdExtban::EntryMatcher("NOKICKBAN", "BAN", 'Q'));
    496 				}
    497 				else if (modename.equals_cs("noknock"))
    498 					cm = new ChannelMode("NOKNOCK", modechar[0]);
    499 				else if (modename.equals_cs("nonick"))
    500 				{
    501 					cm = new ChannelMode("NONICK", modechar[0]);
    502 					ModeManager::AddChannelMode(new InspIRCdExtban::EntryMatcher("NONICKBAN", "BAN", 'N'));
    503 				}
    504 				else if (modename.equals_cs("nonotice"))
    505 				{
    506 					cm = new ChannelMode("NONOTICE", modechar[0]);
    507 					ModeManager::AddChannelMode(new InspIRCdExtban::EntryMatcher("NONOTICEBAN", "BAN", 'T'));
    508 				}
    509 				else if (modename.equals_cs("official-join"))
    510 					cm = new ChannelModeStatus("OFFICIALJOIN", modechar.length() > 1 ? modechar[1] : modechar[0], modechar.length() > 1 ? modechar[0] : 0, 2);
    511 				else if (modename.equals_cs("op"))
    512 					cm = new ChannelModeStatus("OP", modechar.length() > 1 ? modechar[1] : modechar[0], modechar.length() > 1 ? modechar[0] : 0, 2);
    513 				else if (modename.equals_cs("operonly"))
    514 					cm = new ChannelModeOperOnly("OPERONLY", modechar[0]);
    515 				else if (modename.equals_cs("operprefix"))
    516 					cm = new ChannelModeStatus("OPERPREFIX", modechar.length() > 1 ? modechar[1] : modechar[0], modechar.length() > 1 ? modechar[0] : 0, 2);
    517 				else if (modename.equals_cs("permanent"))
    518 					cm = new ChannelMode("PERM", modechar[0]);
    519 				else if (modename.equals_cs("private"))
    520 					cm = new ChannelMode("PRIVATE", modechar[0]);
    521 				else if (modename.equals_cs("redirect"))
    522 					cm = new ChannelModeRedirect(modechar[0]);
    523 				else if (modename.equals_cs("reginvite"))
    524 					cm = new ChannelMode("REGISTEREDONLY", modechar[0]);
    525 				else if (modename.equals_cs("regmoderated"))
    526 					cm = new ChannelMode("REGMODERATED", modechar[0]);
    527 				else if (modename.equals_cs("secret"))
    528 					cm = new ChannelMode("SECRET", modechar[0]);
    529 				else if (modename.equals_cs("sslonly"))
    530 				{
    531 					cm = new ChannelMode("SSL", modechar[0]);
    532 					ModeManager::AddChannelMode(new InspIRCdExtban::FingerprintMatcher("SSLBAN", "BAN", 'z'));
    533 				}
    534 				else if (modename.equals_cs("stripcolor"))
    535 				{
    536 					cm = new ChannelMode("STRIPCOLOR", modechar[0]);
    537 					ModeManager::AddChannelMode(new InspIRCdExtban::EntryMatcher("STRIPCOLORBAN", "BAN", 'S'));
    538 				}
    539 				else if (modename.equals_cs("topiclock"))
    540 					cm = new ChannelMode("TOPIC", modechar[0]);
    541 				else if (modename.equals_cs("voice"))
    542 					cm = new ChannelModeStatus("VOICE", modechar.length() > 1 ? modechar[1] : modechar[0], modechar.length() > 1 ? modechar[0] : 0, 0);
    543 				/* Unknown status mode, (customprefix) - add it */
    544 				else if (modechar.length() == 2)
    545 					cm = new ChannelModeStatus(modename.upper(), modechar[1], modechar[0], -1);
    546 				/* Unknown non status mode, add it to our list for later */
    547 				else
    548 					chmodes[modechar[0]] = modename.upper();
    549 
    550 				if (cm)
    551 					ModeManager::AddChannelMode(cm);
    552 			}
    553 		}
    554 		if (params[0].equals_cs("USERMODES") && params.size() > 1)
    555 		{
    556 			spacesepstream ssep(params[1]);
    557 			Anope::string capab;
    558 
    559 			while (ssep.GetToken(capab))
    560 			{
    561 				Anope::string modename = capab.substr(0, capab.find('='));
    562 				Anope::string modechar = capab.substr(capab.find('=') + 1);
    563 				UserMode *um = NULL;
    564 
    565 				if (modename.equals_cs("bot"))
    566 				{
    567 					um = new UserMode("BOT", modechar[0]);
    568 					IRCD->DefaultPseudoclientModes += modechar;
    569 				}
    570 				else if (modename.equals_cs("callerid"))
    571 					um = new UserMode("CALLERID", modechar[0]);
    572 				else if (modename.equals_cs("cloak"))
    573 					um = new UserMode("CLOAK", modechar[0]);
    574 				else if (modename.equals_cs("deaf"))
    575 					um = new UserMode("DEAF", modechar[0]);
    576 				else if (modename.equals_cs("deaf_commonchan"))
    577 					um = new UserMode("COMMONCHANS", modechar[0]);
    578 				else if (modename.equals_cs("helpop"))
    579 					um = new UserModeOperOnly("HELPOP", modechar[0]);
    580 				else if (modename.equals_cs("hidechans"))
    581 					um = new UserMode("PRIV", modechar[0]);
    582 				else if (modename.equals_cs("hideoper"))
    583 					um = new UserModeOperOnly("HIDEOPER", modechar[0]);
    584 				else if (modename.equals_cs("invisible"))
    585 					um = new UserMode("INVIS", modechar[0]);
    586 				else if (modename.equals_cs("invis-oper"))
    587 					um = new UserModeOperOnly("INVISIBLE_OPER", modechar[0]);
    588 				else if (modename.equals_cs("oper"))
    589 					um = new UserModeOperOnly("OPER", modechar[0]);
    590 				else if (modename.equals_cs("regdeaf"))
    591 					um = new UserMode("REGPRIV", modechar[0]);
    592 				else if (modename.equals_cs("servprotect"))
    593 				{
    594 					um = new UserModeNoone("PROTECTED", modechar[0]);
    595 					IRCD->DefaultPseudoclientModes += modechar;
    596 				}
    597 				else if (modename.equals_cs("showwhois"))
    598 					um = new UserMode("WHOIS", modechar[0]);
    599 				else if (modename.equals_cs("u_censor"))
    600 					um = new UserMode("CENSOR", modechar[0]);
    601 				else if (modename.equals_cs("u_registered"))
    602 					um = new UserModeNoone("REGISTERED", modechar[0]);
    603 				else if (modename.equals_cs("u_stripcolor"))
    604 					um = new UserMode("STRIPCOLOR", modechar[0]);
    605 				else if (modename.equals_cs("wallops"))
    606 					um = new UserMode("WALLOPS", modechar[0]);
    607 				else
    608 					umodes[modechar[0]] = modename.upper();
    609 
    610 				if (um)
    611 					ModeManager::AddUserMode(um);
    612 			}
    613 		}
    614 		else if (params[0].equals_cs("MODULES") && params.size() > 1)
    615 		{
    616 			spacesepstream ssep(params[1]);
    617 			Anope::string module;
    618 
    619 			while (ssep.GetToken(module))
    620 			{
    621 				if (module.equals_cs("m_svshold.so"))
    622 					IRCD->CanSVSHold = true;
    623 				else if (module.find("m_rline.so") == 0)
    624 				{
    625 					Servers::Capab.insert("RLINE");
    626 					const Anope::string &regexengine = Config->GetBlock("options")->Get<const Anope::string>("regexengine");
    627 					if (!regexengine.empty() && module.length() > 11 && regexengine != module.substr(11))
    628 						Log() << "Warning: InspIRCd is using regex engine " << module.substr(11) << ", but we have " << regexengine << ". This may cause inconsistencies.";
    629 				}
    630 				else if (module.equals_cs("m_topiclock.so"))
    631 					Servers::Capab.insert("TOPICLOCK");
    632 			}
    633 		}
    634 		else if (params[0].equals_cs("MODSUPPORT") && params.size() > 1)
    635 		{
    636 			spacesepstream ssep(params[1]);
    637 			Anope::string module;
    638 
    639 			while (ssep.GetToken(module))
    640 			{
    641 				if (module.equals_cs("m_services_account.so"))
    642 				{
    643 					Servers::Capab.insert("SERVICES");
    644 					ModeManager::AddChannelMode(new InspIRCdExtban::AccountMatcher("ACCOUNTBAN", "BAN", 'R'));
    645 					ModeManager::AddChannelMode(new InspIRCdExtban::UnidentifiedMatcher("UNREGISTEREDBAN", "BAN", 'U'));
    646 				}
    647 				else if (module.equals_cs("m_chghost.so"))
    648 					Servers::Capab.insert("CHGHOST");
    649 				else if (module.equals_cs("m_chgident.so"))
    650 					Servers::Capab.insert("CHGIDENT");
    651 				else if (module == "m_channelban.so")
    652 					ModeManager::AddChannelMode(new InspIRCdExtban::ChannelMatcher("CHANNELBAN", "BAN", 'j'));
    653 				else if (module == "m_gecosban.so")
    654 					ModeManager::AddChannelMode(new InspIRCdExtban::RealnameMatcher("REALNAMEBAN", "BAN", 'r'));
    655 				else if (module == "m_nopartmessage.so")
    656 					ModeManager::AddChannelMode(new InspIRCdExtban::EntryMatcher("PARTMESSAGEBAN", "BAN", 'p'));
    657 				else if (module == "m_serverban.so")
    658 					ModeManager::AddChannelMode(new InspIRCdExtban::ServerMatcher("SERVERBAN", "BAN", 's'));
    659 				else if (module == "m_muteban.so")
    660 					ModeManager::AddChannelMode(new InspIRCdExtban::EntryMatcher("QUIET", "BAN", 'm'));
    661 			}
    662 		}
    663 		else if (params[0].equals_cs("CAPABILITIES") && params.size() > 1)
    664 		{
    665 			spacesepstream ssep(params[1]);
    666 			Anope::string capab;
    667 			while (ssep.GetToken(capab))
    668 			{
    669 				if (capab.find("CHANMODES") != Anope::string::npos)
    670 				{
    671 					Anope::string modes(capab.begin() + 10, capab.end());
    672 					commasepstream sep(modes, true);
    673 					Anope::string modebuf;
    674 
    675 					sep.GetToken(modebuf);
    676 					for (size_t t = 0, end = modebuf.length(); t < end; ++t)
    677 					{
    678 						if (ModeManager::FindChannelModeByChar(modebuf[t]))
    679 							continue;
    680 						ModeManager::AddChannelMode(new ChannelModeList(chmodes[modebuf[t]], modebuf[t]));
    681 					}
    682 
    683 					sep.GetToken(modebuf);
    684 					for (size_t t = 0, end = modebuf.length(); t < end; ++t)
    685 					{
    686 						if (ModeManager::FindChannelModeByChar(modebuf[t]))
    687 							continue;
    688 						ModeManager::AddChannelMode(new ChannelModeParam(chmodes[modebuf[t]], modebuf[t]));
    689 					}
    690 
    691 					sep.GetToken(modebuf);
    692 					for (size_t t = 0, end = modebuf.length(); t < end; ++t)
    693 					{
    694 						if (ModeManager::FindChannelModeByChar(modebuf[t]))
    695 							continue;
    696 						ModeManager::AddChannelMode(new ChannelModeParam(chmodes[modebuf[t]], modebuf[t], true));
    697 					}
    698 
    699 					sep.GetToken(modebuf);
    700 					for (size_t t = 0, end = modebuf.length(); t < end; ++t)
    701 					{
    702 						if (ModeManager::FindChannelModeByChar(modebuf[t]))
    703 							continue;
    704 						ModeManager::AddChannelMode(new ChannelMode(chmodes[modebuf[t]], modebuf[t]));
    705 					}
    706 				}
    707 				else if (capab.find("USERMODES") != Anope::string::npos)
    708 				{
    709 					Anope::string modes(capab.begin() + 10, capab.end());
    710 					commasepstream sep(modes, true);
    711 					Anope::string modebuf;
    712 
    713 					sep.GetToken(modebuf);
    714 					sep.GetToken(modebuf);
    715 
    716 					if (sep.GetToken(modebuf))
    717 						for (size_t t = 0, end = modebuf.length(); t < end; ++t)
    718 							ModeManager::AddUserMode(new UserModeParam(umodes[modebuf[t]], modebuf[t]));
    719 
    720 					if (sep.GetToken(modebuf))
    721 						for (size_t t = 0, end = modebuf.length(); t < end; ++t)
    722 							ModeManager::AddUserMode(new UserMode(umodes[modebuf[t]], modebuf[t]));
    723 				}
    724 				else if (capab.find("MAXMODES=") != Anope::string::npos)
    725 				{
    726 					Anope::string maxmodes(capab.begin() + 9, capab.end());
    727 					IRCD->MaxModes = maxmodes.is_pos_number_only() ? convertTo<unsigned>(maxmodes) : 3;
    728 				}
    729 				else if (capab.find("PREFIX=") != Anope::string::npos)
    730 				{
    731 					Anope::string modes(capab.begin() + 8, capab.begin() + capab.find(')'));
    732 					Anope::string chars(capab.begin() + capab.find(')') + 1, capab.end());
    733 					short level = modes.length() - 1;
    734 
    735 					for (size_t t = 0, end = modes.length(); t < end; ++t)
    736 					{
    737 						ChannelMode *cm = ModeManager::FindChannelModeByChar(modes[t]);
    738 						if (cm == NULL || cm->type != MODE_STATUS)
    739 						{
    740 							Log() << "CAPAB PREFIX gave unknown channel status mode " << modes[t];
    741 							continue;
    742 						}
    743 
    744 						ChannelModeStatus *cms = anope_dynamic_static_cast<ChannelModeStatus *>(cm);
    745 						cms->level = level--;
    746 
    747 						Log(LOG_DEBUG) << cms->name << " is now level " << cms->level;
    748 					}
    749 
    750 					ModeManager::RebuildStatusModes();
    751 				}
    752 				else if (capab == "GLOBOPS=1")
    753 					Servers::Capab.insert("GLOBOPS");
    754 			}
    755 		}
    756 		else if (params[0].equals_cs("END"))
    757 		{
    758 			if (!Servers::Capab.count("SERVICES"))
    759 			{
    760 				UplinkSocket::Message() << "ERROR :m_services_account.so is not loaded. This is required by Anope";
    761 				Anope::QuitReason = "ERROR: Remote server does not have the m_services_account module loaded, and this is required.";
    762 				Anope::Quitting = true;
    763 				return;
    764 			}
    765 			if (!ModeManager::FindUserModeByName("PRIV"))
    766 			{
    767 				UplinkSocket::Message() << "ERROR :m_hidechans.so is not loaded. This is required by Anope";
    768 				Anope::QuitReason = "ERROR: Remote server does not have the m_hidechans module loaded, and this is required.";
    769 				Anope::Quitting = true;
    770 				return;
    771 			}
    772 			if (!IRCD->CanSVSHold)
    773 				Log() << "SVSHOLD missing, Usage disabled until module is loaded.";
    774 			if (!Servers::Capab.count("CHGHOST"))
    775 				Log() << "CHGHOST missing, Usage disabled until module is loaded.";
    776 			if (!Servers::Capab.count("CHGIDENT"))
    777 				Log() << "CHGIDENT missing, Usage disabled until module is loaded.";
    778 
    779 			chmodes.clear();
    780 			umodes.clear();
    781 		}
    782 
    783 		Message::Capab::Run(source, params);
    784 	}
    785 };
    786 
    787 struct IRCDMessageEncap : IRCDMessage
    788 {
    789 	ServiceReference<IRCDMessage> insp12_encap;
    790 
    791 	IRCDMessageEncap(Module *creator) : IRCDMessage(creator, "ENCAP", 4), insp12_encap("IRCDMessage", "inspircd12/encap") { SetFlag(IRCDMESSAGE_SOFT_LIMIT); }
    792 
    793 	void Run(MessageSource &source, const std::vector<Anope::string> &params) anope_override
    794 	{
    795 		if (!Anope::Match(Me->GetSID(), params[0]) && !Anope::Match(Me->GetName(), params[0]))
    796 			return;
    797 
    798 		if (params[1] == "CHGIDENT")
    799 		{
    800 			User *u = User::Find(params[2]);
    801 			if (!u || u->server != Me)
    802 				return;
    803 
    804 			u->SetIdent(params[3]);
    805 			UplinkSocket::Message(u) << "FIDENT :" << params[3];
    806 		}
    807 		else if (params[1] == "CHGHOST")
    808 		{
    809 			User *u = User::Find(params[2]);
    810 			if (!u || u->server != Me)
    811 				return;
    812 
    813 			u->SetDisplayedHost(params[3]);
    814 			UplinkSocket::Message(u) << "FHOST :" << params[3];
    815 		}
    816 		else if (params[1] == "CHGNAME")
    817 		{
    818 			User *u = User::Find(params[2]);
    819 			if (!u || u->server != Me)
    820 				return;
    821 
    822 			u->SetRealname(params[3]);
    823 			UplinkSocket::Message(u) << "FNAME :" << params[3];
    824 		}
    825 
    826 		if (insp12_encap)
    827 			insp12_encap->Run(source, params);
    828 	}
    829 };
    830 
    831 struct IRCDMessageFHost : IRCDMessage
    832 {
    833 	IRCDMessageFHost(Module *creator) : IRCDMessage(creator, "FHOST", 1) { SetFlag(IRCDMESSAGE_REQUIRE_USER); }
    834 
    835 	void Run(MessageSource &source, const std::vector<Anope::string> &params) anope_override
    836 	{
    837 		User *u = source.GetUser();
    838 		if (u->HasMode("CLOAK"))
    839 			u->RemoveModeInternal(source, ModeManager::FindUserModeByName("CLOAK"));
    840 		u->SetDisplayedHost(params[0]);
    841 	}
    842 };
    843 
    844 struct IRCDMessageFIdent : IRCDMessage
    845 {
    846 	IRCDMessageFIdent(Module *creator) : IRCDMessage(creator, "FIDENT", 1) { SetFlag(IRCDMESSAGE_REQUIRE_USER); }
    847 
    848 	void Run(MessageSource &source, const std::vector<Anope::string> &params) anope_override
    849 	{
    850 		source.GetUser()->SetIdent(params[0]);
    851 	}
    852 };
    853 
    854 struct IRCDMessageSave : IRCDMessage
    855 {
    856 	time_t last_collide;
    857 
    858 	IRCDMessageSave(Module *creator) : IRCDMessage(creator, "SAVE", 2), last_collide(0) { }
    859 
    860 	void Run(MessageSource &source, const std::vector<Anope::string> &params) anope_override
    861 	{
    862 		User *targ = User::Find(params[0]);
    863 		time_t ts;
    864 
    865 		try
    866 		{
    867 			ts = convertTo<time_t>(params[1]);
    868 		}
    869 		catch (const ConvertException &)
    870 		{
    871 			return;
    872 		}
    873 
    874 		if (!targ || targ->timestamp != ts)
    875 			return;
    876 
    877 		BotInfo *bi;
    878 		if (targ->server == Me && (bi = dynamic_cast<BotInfo *>(targ)))
    879 		{
    880 			if (last_collide == Anope::CurTime)
    881 			{
    882 				Anope::QuitReason = "Nick collision fight on " + targ->nick;
    883 				Anope::Quitting = true;
    884 				return;
    885 			}
    886 
    887 			IRCD->SendKill(Me, targ->nick, "Nick collision");
    888 			IRCD->SendNickChange(targ, targ->nick);
    889 			last_collide = Anope::CurTime;
    890 		}
    891 		else
    892 			targ->ChangeNick(targ->GetUID());
    893 	}
    894 };
    895 
    896 class IRCDMessageMetadata : IRCDMessage
    897 {
    898 	ServiceReference<IRCDMessage> insp12_metadata;
    899 	const bool &do_topiclock;
    900 	const bool &do_mlock;
    901 
    902  public:
    903 	IRCDMessageMetadata(Module *creator, const bool &handle_topiclock, const bool &handle_mlock) : IRCDMessage(creator, "METADATA", 3), insp12_metadata("IRCDMessage", "inspircd12/metadata"), do_topiclock(handle_topiclock), do_mlock(handle_mlock) { SetFlag(IRCDMESSAGE_REQUIRE_SERVER); }
    904 
    905 	void Run(MessageSource &source, const std::vector<Anope::string> &params) anope_override
    906 	{
    907 		// We deliberately ignore non-bursting servers to avoid pseudoserver fights
    908 		if ((params[0][0] == '#') && (!source.GetServer()->IsSynced()))
    909 		{
    910 			Channel *c = Channel::Find(params[0]);
    911 			if (c && c->ci)
    912 			{
    913 				if ((do_mlock) && (params[1] == "mlock"))
    914 				{
    915 					ModeLocks *modelocks = c->ci->GetExt<ModeLocks>("modelocks");
    916 					Anope::string modes;
    917 					if (modelocks)
    918 						modes = modelocks->GetMLockAsString(false).replace_all_cs("+", "").replace_all_cs("-", "");
    919 
    920 					// Mode lock string is not what we say it is?
    921 					if (modes != params[2])
    922 						UplinkSocket::Message(Me) << "METADATA " << c->name << " mlock :" << modes;
    923 				}
    924 				else if ((do_topiclock) && (params[1] == "topiclock"))
    925 				{
    926 					bool mystate = c->ci->HasExt("TOPICLOCK");
    927 					bool serverstate = (params[2] == "1");
    928 					if (mystate != serverstate)
    929 						UplinkSocket::Message(Me) << "METADATA " << c->name << " topiclock :" << (mystate ? "1" : "");
    930 				}
    931 			}
    932 		}
    933 
    934 		if (insp12_metadata)
    935 			insp12_metadata->Run(source, params);
    936 	}
    937 };
    938 
    939 class ProtoInspIRCd20 : public Module
    940 {
    941 	Module *m_insp12;
    942 
    943 	InspIRCd20Proto ircd_proto;
    944 
    945 	/* Core message handlers */
    946 	Message::Error message_error;
    947 	Message::Invite message_invite;
    948 	Message::Join message_join;
    949 	Message::Kick message_kick;
    950 	Message::Kill message_kill;
    951 	Message::MOTD message_motd;
    952 	Message::Notice message_notice;
    953 	Message::Part message_part;
    954 	Message::Ping message_ping;
    955 	Message::Privmsg message_privmsg;
    956 	Message::Quit message_quit;
    957 	Message::Stats message_stats;
    958 	Message::Topic message_topic;
    959 
    960 	/* InspIRCd 1.2 message handlers */
    961 	ServiceAlias message_endburst, message_fjoin, message_fmode,
    962 				message_ftopic, message_idle, message_mode,
    963 				message_nick, message_opertype, message_rsquit, message_server,
    964 				message_squit, message_time, message_uid;
    965 
    966 	/* Our message handlers */
    967 	IRCDMessageAway message_away;
    968 	IRCDMessageCapab message_capab;
    969 	IRCDMessageEncap message_encap;
    970 	IRCDMessageFHost message_fhost;
    971 	IRCDMessageFIdent message_fident;
    972 	IRCDMessageMetadata message_metadata;
    973 	IRCDMessageSave message_save;
    974 
    975 	bool use_server_side_topiclock, use_server_side_mlock;
    976 
    977 	void SendChannelMetadata(Channel *c, const Anope::string &metadataname, const Anope::string &value)
    978 	{
    979 		UplinkSocket::Message(Me) << "METADATA " << c->name << " " << metadataname << " :" << value;
    980 	}
    981 
    982  public:
    983 	ProtoInspIRCd20(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, PROTOCOL | VENDOR),
    984 		ircd_proto(this),
    985 		message_error(this), message_invite(this), message_join(this), message_kick(this), message_kill(this),
    986 		message_motd(this), message_notice(this), message_part(this), message_ping(this), message_privmsg(this),
    987 		message_quit(this), message_stats(this), message_topic(this),
    988 
    989 		message_endburst("IRCDMessage", "inspircd20/endburst", "inspircd12/endburst"),
    990 		message_fjoin("IRCDMessage", "inspircd20/fjoin", "inspircd12/fjoin"),
    991 		message_fmode("IRCDMessage", "inspircd20/fmode", "inspircd12/fmode"),
    992 		message_ftopic("IRCDMessage", "inspircd20/ftopic", "inspircd12/ftopic"),
    993 		message_idle("IRCDMessage", "inspircd20/idle", "inspircd12/idle"),
    994 		message_mode("IRCDMessage", "inspircd20/mode", "inspircd12/mode"),
    995 		message_nick("IRCDMessage", "inspircd20/nick", "inspircd12/nick"),
    996 		message_opertype("IRCDMessage", "inspircd20/opertype", "inspircd12/opertype"),
    997 		message_rsquit("IRCDMessage", "inspircd20/rsquit", "inspircd12/rsquit"),
    998 		message_server("IRCDMessage", "inspircd20/server", "inspircd12/server"),
    999 		message_squit("IRCDMessage", "inspircd20/squit", "inspircd12/squit"),
   1000 		message_time("IRCDMessage", "inspircd20/time", "inspircd12/time"),
   1001 		message_uid("IRCDMessage", "inspircd20/uid", "inspircd12/uid"),
   1002 
   1003 		message_away(this), message_capab(this), message_encap(this), message_fhost(this), message_fident(this),
   1004 		message_metadata(this, use_server_side_topiclock, use_server_side_mlock), message_save(this)
   1005 	{
   1006 
   1007 		if (ModuleManager::LoadModule("inspircd12", User::Find(creator)) != MOD_ERR_OK)
   1008 			throw ModuleException("Unable to load inspircd12");
   1009 		m_insp12 = ModuleManager::FindModule("inspircd12");
   1010 		if (!m_insp12)
   1011 			throw ModuleException("Unable to find inspircd12");
   1012 		if (!insp12)
   1013 			throw ModuleException("No protocol interface for insp12");
   1014 		ModuleManager::DetachAll(m_insp12);
   1015 
   1016 	}
   1017 
   1018 	~ProtoInspIRCd20()
   1019 	{
   1020 		m_insp12 = ModuleManager::FindModule("inspircd12");
   1021 		ModuleManager::UnloadModule(m_insp12, NULL);
   1022 	}
   1023 
   1024 	void OnReload(Configuration::Conf *conf) anope_override
   1025 	{
   1026 		use_server_side_topiclock = conf->GetModule(this)->Get<bool>("use_server_side_topiclock");
   1027 		use_server_side_mlock = conf->GetModule(this)->Get<bool>("use_server_side_mlock");
   1028 	}
   1029 
   1030 	void OnUserNickChange(User *u, const Anope::string &) anope_override
   1031 	{
   1032 		u->RemoveModeInternal(Me, ModeManager::FindUserModeByName("REGISTERED"));
   1033 	}
   1034 
   1035 	void OnChannelSync(Channel *c) anope_override
   1036 	{
   1037 		if (c->ci)
   1038 			this->OnChanRegistered(c->ci);
   1039 	}
   1040 
   1041 	void OnChanRegistered(ChannelInfo *ci) anope_override
   1042 	{
   1043 		ModeLocks *modelocks = ci->GetExt<ModeLocks>("modelocks");
   1044 		if (use_server_side_mlock && ci->c && modelocks && !modelocks->GetMLockAsString(false).empty())
   1045 		{
   1046 			Anope::string modes = modelocks->GetMLockAsString(false).replace_all_cs("+", "").replace_all_cs("-", "");
   1047 			SendChannelMetadata(ci->c, "mlock", modes);
   1048 		}
   1049 
   1050 		if (use_server_side_topiclock && Servers::Capab.count("TOPICLOCK") && ci->c)
   1051 		{
   1052 			if (ci->HasExt("TOPICLOCK"))
   1053 				SendChannelMetadata(ci->c, "topiclock", "1");
   1054 		}
   1055 	}
   1056 
   1057 	void OnDelChan(ChannelInfo *ci) anope_override
   1058 	{
   1059 		if (use_server_side_mlock && ci->c)
   1060 			SendChannelMetadata(ci->c, "mlock", "");
   1061 
   1062 		if (use_server_side_topiclock && Servers::Capab.count("TOPICLOCK") && ci->c)
   1063 			SendChannelMetadata(ci->c, "topiclock", "");
   1064 	}
   1065 
   1066 	EventReturn OnMLock(ChannelInfo *ci, ModeLock *lock) anope_override
   1067 	{
   1068 		ModeLocks *modelocks = ci->GetExt<ModeLocks>("modelocks");
   1069 		ChannelMode *cm = ModeManager::FindChannelModeByName(lock->name);
   1070 		if (use_server_side_mlock && cm && ci->c && modelocks && (cm->type == MODE_REGULAR || cm->type == MODE_PARAM))
   1071 		{
   1072 			Anope::string modes = modelocks->GetMLockAsString(false).replace_all_cs("+", "").replace_all_cs("-", "") + cm->mchar;
   1073 			SendChannelMetadata(ci->c, "mlock", modes);
   1074 		}
   1075 
   1076 		return EVENT_CONTINUE;
   1077 	}
   1078 
   1079 	EventReturn OnUnMLock(ChannelInfo *ci, ModeLock *lock) anope_override
   1080 	{
   1081 		ModeLocks *modelocks = ci->GetExt<ModeLocks>("modelocks");
   1082 		ChannelMode *cm = ModeManager::FindChannelModeByName(lock->name);
   1083 		if (use_server_side_mlock && cm && ci->c && modelocks && (cm->type == MODE_REGULAR || cm->type == MODE_PARAM))
   1084 		{
   1085 			Anope::string modes = modelocks->GetMLockAsString(false).replace_all_cs("+", "").replace_all_cs("-", "").replace_all_cs(cm->mchar, "");
   1086 			SendChannelMetadata(ci->c, "mlock", modes);
   1087 		}
   1088 
   1089 		return EVENT_CONTINUE;
   1090 	}
   1091 
   1092 	EventReturn OnSetChannelOption(CommandSource &source, Command *cmd, ChannelInfo *ci, const Anope::string &setting) anope_override
   1093 	{
   1094 		if (cmd->name == "chanserv/topic" && ci->c)
   1095 		{
   1096 			if (setting == "topiclock on")
   1097 				SendChannelMetadata(ci->c, "topiclock", "1");
   1098 			else if (setting == "topiclock off")
   1099 				SendChannelMetadata(ci->c, "topiclock", "0");
   1100 		}
   1101 
   1102 		return EVENT_CONTINUE;
   1103 	}
   1104 };
   1105 
   1106 MODULE_INIT(ProtoInspIRCd20)