anope

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

os_session.cpp (21995B)

      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 #include "modules/os_session.h"
     14 
     15 namespace
     16 {
     17 	/* The default session limit */
     18 	unsigned session_limit;
     19 	/* How many times to kill before adding an AKILL */
     20 	unsigned max_session_kill;
     21 	/* How long session akills should last */
     22 	time_t session_autokill_expiry;
     23 	/* Reason to use for session kills */
     24 	Anope::string sle_reason;
     25 	/* Optional second reason */
     26 	Anope::string sle_detailsloc;
     27 
     28 	/* Max limit that can be used for exceptions */
     29 	unsigned max_exception_limit;
     30 	/* How long before exceptions expire by default */
     31 	time_t exception_expiry;
     32 
     33 	/* Number of bits to use when comparing session IPs */
     34 	unsigned ipv4_cidr;
     35 	unsigned ipv6_cidr;
     36 }
     37 
     38 class MySessionService : public SessionService
     39 {
     40 	SessionMap Sessions;
     41 	Serialize::Checker<ExceptionVector> Exceptions;
     42  public:
     43 	MySessionService(Module *m) : SessionService(m), Exceptions("Exception") { }
     44 
     45 	Exception *CreateException() anope_override
     46 	{
     47 		return new Exception();
     48 	}
     49 
     50 	void AddException(Exception *e) anope_override
     51 	{
     52 		this->Exceptions->push_back(e);
     53 	}
     54 
     55 	void DelException(Exception *e) anope_override
     56 	{
     57 		ExceptionVector::iterator it = std::find(this->Exceptions->begin(), this->Exceptions->end(), e);
     58 		if (it != this->Exceptions->end())
     59 			this->Exceptions->erase(it);
     60 	}
     61 
     62 	Exception *FindException(User *u) anope_override
     63 	{
     64 		for (std::vector<Exception *>::const_iterator it = this->Exceptions->begin(), it_end = this->Exceptions->end(); it != it_end; ++it)
     65 		{
     66 			Exception *e = *it;
     67 			if (Anope::Match(u->host, e->mask) || Anope::Match(u->ip.addr(), e->mask))
     68 				return e;
     69 
     70 			if (cidr(e->mask).match(u->ip))
     71 				return e;
     72 		}
     73 		return NULL;
     74 	}
     75 
     76 	Exception *FindException(const Anope::string &host) anope_override
     77 	{
     78 		for (std::vector<Exception *>::const_iterator it = this->Exceptions->begin(), it_end = this->Exceptions->end(); it != it_end; ++it)
     79 		{
     80 			Exception *e = *it;
     81 			if (Anope::Match(host, e->mask))
     82 				return e;
     83 
     84 			if (cidr(e->mask).match(sockaddrs(host)))
     85 				return e;
     86 		}
     87 
     88 		return NULL;
     89 	}
     90 
     91 	ExceptionVector &GetExceptions() anope_override
     92 	{
     93 		return this->Exceptions;
     94 	}
     95 
     96 	void DelSession(Session *s)
     97 	{
     98 		this->Sessions.erase(s->addr);
     99 	}
    100 
    101 	Session *FindSession(const Anope::string &ip) anope_override
    102 	{
    103 		cidr c(ip, ip.find(':') != Anope::string::npos ? ipv6_cidr : ipv4_cidr);
    104 		if (!c.valid())
    105 			return NULL;
    106 		SessionMap::iterator it = this->Sessions.find(c);
    107 		if (it != this->Sessions.end())
    108 			return it->second;
    109 		return NULL;
    110 	}
    111 
    112 	SessionMap::iterator FindSessionIterator(const sockaddrs &ip)
    113 	{
    114 		cidr c(ip, ip.ipv6() ? ipv6_cidr : ipv4_cidr);
    115 		if (!c.valid())
    116 			return this->Sessions.end();
    117 		return this->Sessions.find(c);
    118 	}
    119 
    120 	Session* &FindOrCreateSession(const cidr &ip)
    121 	{
    122 		return this->Sessions[ip];
    123 	}
    124 
    125 	SessionMap &GetSessions() anope_override
    126 	{
    127 		return this->Sessions;
    128 	}
    129 };
    130 
    131 class ExceptionDelCallback : public NumberList
    132 {
    133  protected:
    134 	CommandSource &source;
    135 	unsigned deleted;
    136 	Command *cmd;
    137  public:
    138 	ExceptionDelCallback(CommandSource &_source, const Anope::string &numlist, Command *c) : NumberList(numlist, true), source(_source), deleted(0), cmd(c)
    139 	{
    140 	}
    141 
    142 	~ExceptionDelCallback()
    143 	{
    144 		if (!deleted)
    145 			source.Reply(_("No matching entries on session-limit exception list."));
    146 		else if (deleted == 1)
    147 			source.Reply(_("Deleted 1 entry from session-limit exception list."));
    148 		else
    149 			source.Reply(_("Deleted %d entries from session-limit exception list."), deleted);
    150 	}
    151 
    152 	virtual void HandleNumber(unsigned number) anope_override
    153 	{
    154 		if (!number || number > session_service->GetExceptions().size())
    155 			return;
    156 
    157 		Log(LOG_ADMIN, source, cmd) << "to remove the session limit exception for " << session_service->GetExceptions()[number - 1]->mask;
    158 
    159 		++deleted;
    160 		DoDel(source, number - 1);
    161 	}
    162 
    163 	static void DoDel(CommandSource &source, unsigned index)
    164 	{
    165 		Exception *e = session_service->GetExceptions()[index];
    166 		FOREACH_MOD(OnExceptionDel, (source, e));
    167 
    168 		session_service->DelException(e);
    169 		delete e;
    170 	}
    171 };
    172 
    173 class CommandOSSession : public Command
    174 {
    175  private:
    176 	void DoList(CommandSource &source, const std::vector<Anope::string> &params)
    177 	{
    178 		Anope::string param = params[1];
    179 
    180 		unsigned mincount = 0;
    181 		try
    182 		{
    183 			mincount = convertTo<unsigned>(param);
    184 		}
    185 		catch (const ConvertException &) { }
    186 
    187 		if (mincount <= 1)
    188 			source.Reply(_("Invalid threshold value. It must be a valid integer greater than 1."));
    189 		else
    190 		{
    191 			ListFormatter list(source.GetAccount());
    192 			list.AddColumn(_("Session")).AddColumn(_("Host"));
    193 
    194 			for (SessionService::SessionMap::iterator it = session_service->GetSessions().begin(), it_end = session_service->GetSessions().end(); it != it_end; ++it)
    195 			{
    196 				Session *session = it->second;
    197 
    198 				if (session->count >= mincount)
    199 				{
    200 					ListFormatter::ListEntry entry;
    201 					entry["Session"] = stringify(session->count);
    202 					entry["Host"] = session->addr.mask();
    203 					list.AddEntry(entry);
    204 				}
    205 			}
    206 
    207 			source.Reply(_("Hosts with at least \002%d\002 sessions:"), mincount);
    208 
    209 			std::vector<Anope::string> replies;
    210 			list.Process(replies);
    211 
    212 
    213 			for (unsigned i = 0; i < replies.size(); ++i)
    214 				source.Reply(replies[i]);
    215 		}
    216 
    217 		return;
    218 	}
    219 
    220 	void DoView(CommandSource &source, const std::vector<Anope::string> &params)
    221 	{
    222 		Anope::string param = params[1];
    223 		Session *session = session_service->FindSession(param);
    224 
    225 		Exception *exception = session_service->FindException(param);
    226 		Anope::string entry = "no entry";
    227 		unsigned limit = session_limit;
    228 		if (exception)
    229 		{
    230 			if (!exception->limit)
    231 				limit = 0;
    232 			else if (exception->limit > limit)
    233 				limit = exception->limit;
    234 			entry = exception->mask;
    235 		}
    236 
    237 		if (!session)
    238 			source.Reply(_("\002%s\002 not found on session list, but has a limit of \002%d\002 because it matches entry: \002%s\002."), param.c_str(), limit, entry.c_str());
    239 		else
    240 			source.Reply(_("The host \002%s\002 currently has \002%d\002 sessions with a limit of \002%d\002 because it matches entry: \002%s\002."), session->addr.mask().c_str(), session->count, limit, entry.c_str());
    241 	}
    242  public:
    243 	CommandOSSession(Module *creator) : Command(creator, "operserv/session", 2, 2)
    244 	{
    245 		this->SetDesc(_("View the list of host sessions"));
    246 		this->SetSyntax(_("LIST \037threshold\037"));
    247 		this->SetSyntax(_("VIEW \037host\037"));
    248 	}
    249 
    250 	void Execute(CommandSource &source, const std::vector<Anope::string> &params) anope_override
    251 	{
    252 		const Anope::string &cmd = params[0];
    253 
    254 		Log(LOG_ADMIN, source, this) << cmd << " " << params[1];
    255 
    256 		if (!session_limit)
    257 			source.Reply(_("Session limiting is disabled."));
    258 		else if (cmd.equals_ci("LIST"))
    259 			return this->DoList(source, params);
    260 		else if (cmd.equals_ci("VIEW"))
    261 			return this->DoView(source, params);
    262 		else
    263 			this->OnSyntaxError(source, "");
    264 	}
    265 
    266 	bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override
    267 	{
    268 		this->SendSyntax(source);
    269 		source.Reply(" ");
    270 		source.Reply(_("Allows Services Operators to view the session list.\n"
    271 				" \n"
    272 				"\002SESSION LIST\002 lists hosts with at least \037threshold\037 sessions.\n"
    273 				"The threshold must be a number greater than 1. This is to\n"
    274 				"prevent accidental listing of the large number of single\n"
    275 				"session hosts.\n"
    276 				" \n"
    277 				"\002SESSION VIEW\002 displays detailed information about a specific\n"
    278 				"host - including the current session count and session limit.\n"
    279 				"The \037host\037 value may not include wildcards.\n"
    280 				" \n"
    281 				"See the \002EXCEPTION\002 help for more information about session\n"
    282 				"limiting and how to set session limits specific to certain\n"
    283 				"hosts and groups thereof."));
    284 		return true;
    285 	}
    286 };
    287 
    288 class CommandOSException : public Command
    289 {
    290  private:
    291 	void DoAdd(CommandSource &source, const std::vector<Anope::string> &params)
    292 	{
    293 		Anope::string mask, expiry, limitstr;
    294 		unsigned last_param = 3;
    295 
    296 		mask = params.size() > 1 ? params[1] : "";
    297 		if (!mask.empty() && mask[0] == '+')
    298 		{
    299 			expiry = mask;
    300 			mask = params.size() > 2 ? params[2] : "";
    301 			last_param = 4;
    302 		}
    303 
    304 		limitstr = params.size() > last_param - 1 ? params[last_param - 1] : "";
    305 
    306 		if (params.size() <= last_param)
    307 		{
    308 			this->OnSyntaxError(source, "ADD");
    309 			return;
    310 		}
    311 
    312 		Anope::string reason = params[last_param];
    313 		if (last_param == 3 && params.size() > 4)
    314 			reason += " " + params[4];
    315 		if (reason.empty())
    316 		{
    317 			this->OnSyntaxError(source, "ADD");
    318 			return;
    319 		}
    320 
    321 		time_t expires = !expiry.empty() ? Anope::DoTime(expiry) : exception_expiry;
    322 		if (expires < 0)
    323 		{
    324 			source.Reply(BAD_EXPIRY_TIME);
    325 			return;
    326 		}
    327 		else if (expires > 0)
    328 			expires += Anope::CurTime;
    329 
    330 		unsigned limit = -1;
    331 		try
    332 		{
    333 			limit = convertTo<unsigned>(limitstr);
    334 		}
    335 		catch (const ConvertException &) { }
    336 
    337 		if (limit > max_exception_limit)
    338 		{
    339 			source.Reply(_("Invalid session limit. It must be a valid integer greater than or equal to zero and less than \002%d\002."), max_exception_limit);
    340 			return;
    341 		}
    342 		else
    343 		{
    344 			if (mask.find('!') != Anope::string::npos || mask.find('@') != Anope::string::npos)
    345 			{
    346 				source.Reply(_("Invalid hostmask. Only real hostmasks are valid, as exceptions are not matched against nicks or usernames."));
    347 				return;
    348 			}
    349 
    350 			for (std::vector<Exception *>::iterator it = session_service->GetExceptions().begin(), it_end = session_service->GetExceptions().end(); it != it_end; ++it)
    351 			{
    352 				Exception *e = *it;
    353 				if (e->mask.equals_ci(mask))
    354 				{
    355 					if (e->limit != limit)
    356 					{
    357 						e->limit = limit;
    358 						source.Reply(_("Exception for \002%s\002 has been updated to %d."), mask.c_str(), e->limit);
    359 					}
    360 					else
    361 						source.Reply(_("\002%s\002 already exists on the EXCEPTION list."), mask.c_str());
    362 					return;
    363 				}
    364 			}
    365 
    366 			Exception *exception = new Exception();
    367 			exception->mask = mask;
    368 			exception->limit = limit;
    369 			exception->reason = reason;
    370 			exception->time = Anope::CurTime;
    371 			exception->who = source.GetNick();
    372 			exception->expires = expires;
    373 
    374 			EventReturn MOD_RESULT;
    375 			FOREACH_RESULT(OnExceptionAdd, MOD_RESULT, (exception));
    376 			if (MOD_RESULT == EVENT_STOP)
    377 				delete exception;
    378 			else
    379 			{
    380 				Log(LOG_ADMIN, source, this) << "to set the session limit for " << mask << " to " << limit;
    381 				session_service->AddException(exception);
    382 				source.Reply(_("Session limit for \002%s\002 set to \002%d\002."), mask.c_str(), limit);
    383 				if (Anope::ReadOnly)
    384 					source.Reply(READ_ONLY_MODE);
    385 			}
    386 		}
    387 
    388 		return;
    389 	}
    390 
    391 	void DoDel(CommandSource &source, const std::vector<Anope::string> &params)
    392 	{
    393 		const Anope::string &mask = params.size() > 1 ? params[1] : "";
    394 
    395 		if (mask.empty())
    396 		{
    397 			this->OnSyntaxError(source, "DEL");
    398 			return;
    399 		}
    400 
    401 		if (isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos)
    402 		{
    403 			ExceptionDelCallback list(source, mask, this);
    404 			list.Process();
    405 		}
    406 		else
    407 		{
    408 			unsigned i = 0, end = session_service->GetExceptions().size();
    409 			for (; i < end; ++i)
    410 				if (mask.equals_ci(session_service->GetExceptions()[i]->mask))
    411 				{
    412 					Log(LOG_ADMIN, source, this) << "to remove the session limit exception for " << mask;
    413 					ExceptionDelCallback::DoDel(source, i);
    414 					source.Reply(_("\002%s\002 deleted from session-limit exception list."), mask.c_str());
    415 					break;
    416 				}
    417 			if (i == end)
    418 				source.Reply(_("\002%s\002 not found on session-limit exception list."), mask.c_str());
    419 		}
    420 
    421 		if (Anope::ReadOnly)
    422 			source.Reply(READ_ONLY_MODE);
    423 
    424 		return;
    425 	}
    426 
    427 	void ProcessList(CommandSource &source, const std::vector<Anope::string> &params, ListFormatter &list)
    428 	{
    429 		const Anope::string &mask = params.size() > 1 ? params[1] : "";
    430 
    431 		if (session_service->GetExceptions().empty())
    432 		{
    433 			source.Reply(_("The session exception list is empty."));
    434 			return;
    435 		}
    436 
    437 		if (!mask.empty() && mask.find_first_not_of("1234567890,-") == Anope::string::npos)
    438 		{
    439 			class ExceptionListCallback : public NumberList
    440 			{
    441 				CommandSource &source;
    442 				ListFormatter &list;
    443 			 public:
    444 				ExceptionListCallback(CommandSource &_source, ListFormatter &_list, const Anope::string &numlist) : NumberList(numlist, false), source(_source), list(_list)
    445 				{
    446 				}
    447 
    448 				void HandleNumber(unsigned Number) anope_override
    449 				{
    450 					if (!Number || Number > session_service->GetExceptions().size())
    451 						return;
    452 
    453 					Exception *e = session_service->GetExceptions()[Number - 1];
    454 
    455 					ListFormatter::ListEntry entry;
    456 					entry["Number"] = stringify(Number);
    457 					entry["Mask"] = e->mask;
    458 					entry["By"] = e->who;
    459 					entry["Created"] = Anope::strftime(e->time, NULL, true);
    460 					entry["Expires"] = Anope::Expires(e->expires, source.GetAccount());
    461 					entry["Limit"] = stringify(e->limit);
    462 					entry["Reason"] = e->reason;
    463 					this->list.AddEntry(entry);
    464 				}
    465 			}
    466 			nl_list(source, list, mask);
    467 			nl_list.Process();
    468 		}
    469 		else
    470 		{
    471 			for (unsigned i = 0, end = session_service->GetExceptions().size(); i < end; ++i)
    472 			{
    473 				Exception *e = session_service->GetExceptions()[i];
    474 				if (mask.empty() || Anope::Match(e->mask, mask))
    475 				{
    476 					ListFormatter::ListEntry entry;
    477 					entry["Number"] = stringify(i + 1);
    478 					entry["Mask"] = e->mask;
    479 					entry["By"] = e->who;
    480 					entry["Created"] = Anope::strftime(e->time, NULL, true);
    481 					entry["Expires"] = Anope::Expires(e->expires, source.GetAccount());
    482 					entry["Limit"] = stringify(e->limit);
    483 					entry["Reason"] = e->reason;
    484 					list.AddEntry(entry);
    485 				}
    486 			}
    487 		}
    488 
    489 		if (list.IsEmpty())
    490 			source.Reply(_("No matching entries on session-limit exception list."));
    491 		else
    492 		{
    493 			source.Reply(_("Current Session Limit Exception list:"));
    494 
    495 			std::vector<Anope::string> replies;
    496 			list.Process(replies);
    497 
    498 			for (unsigned i = 0; i < replies.size(); ++i)
    499 				source.Reply(replies[i]);
    500 		}
    501 	}
    502 
    503 	void DoList(CommandSource &source, const std::vector<Anope::string> &params)
    504 	{
    505 		ListFormatter list(source.GetAccount());
    506 		list.AddColumn(_("Number")).AddColumn(_("Limit")).AddColumn(_("Mask"));
    507 
    508 		this->ProcessList(source, params, list);
    509 	}
    510 
    511 	void DoView(CommandSource &source, const std::vector<Anope::string> &params)
    512 	{
    513 		ListFormatter list(source.GetAccount());
    514 		list.AddColumn(_("Number")).AddColumn(_("Mask")).AddColumn(_("By")).AddColumn(_("Created")).AddColumn(_("Expires")).AddColumn(_("Limit")).AddColumn(_("Reason"));
    515 
    516 		this->ProcessList(source, params, list);
    517 	}
    518 
    519  public:
    520 	CommandOSException(Module *creator) : Command(creator, "operserv/exception", 1, 5)
    521 	{
    522 		this->SetDesc(_("Modify the session-limit exception list"));
    523 		this->SetSyntax(_("ADD [\037+expiry\037] \037mask\037 \037limit\037 \037reason\037"));
    524 		this->SetSyntax(_("DEL {\037mask\037 | \037entry-num\037 | \037list\037}"));
    525 		this->SetSyntax(_("LIST [\037mask\037 | \037list\037]"));
    526 		this->SetSyntax(_("VIEW [\037mask\037 | \037list\037]"));
    527 	}
    528 
    529 	void Execute(CommandSource &source, const std::vector<Anope::string> &params) anope_override
    530 	{
    531 		const Anope::string &cmd = params[0];
    532 
    533 		if (!session_limit)
    534 			source.Reply(_("Session limiting is disabled."));
    535 		else if (cmd.equals_ci("ADD"))
    536 			return this->DoAdd(source, params);
    537 		else if (cmd.equals_ci("DEL"))
    538 			return this->DoDel(source, params);
    539 		else if (cmd.equals_ci("LIST"))
    540 			return this->DoList(source, params);
    541 		else if (cmd.equals_ci("VIEW"))
    542 			return this->DoView(source, params);
    543 		else
    544 			this->OnSyntaxError(source, "");
    545 	}
    546 
    547 	bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override
    548 	{
    549 		this->SendSyntax(source);
    550 		source.Reply(" ");
    551 		source.Reply(_("Allows Services Operators to manipulate the list of hosts that\n"
    552 				"have specific session limits - allowing certain machines,\n"
    553 				"such as shell servers, to carry more than the default number\n"
    554 				"of clients at a time. Once a host reaches its session limit,\n"
    555 				"all clients attempting to connect from that host will be\n"
    556 				"killed. Before the user is killed, they are notified, of a\n"
    557 				"source of help regarding session limiting. The content of\n"
    558 				"this notice is a config setting."));
    559 		source.Reply(" ");
    560 		source.Reply(_("\002EXCEPTION ADD\002 adds the given host mask to the exception list.\n"
    561 				"Note that \002nick!user@host\002 and \002user@host\002 masks are invalid!\n"
    562 				"Only real host masks, such as \002box.host.dom\002 and \002*.host.dom\002,\n"
    563 				"are allowed because sessions limiting does not take nick or\n"
    564 				"user names into account. \037limit\037 must be a number greater than\n"
    565 				"or equal to zero. This determines how many sessions this host\n"
    566 				"may carry at a time. A value of zero means the host has an\n"
    567 				"unlimited session limit. See the \002AKILL\002 help for details about\n"
    568 				"the format of the optional \037expiry\037 parameter.\n"
    569 				" \n"
    570 				"\002EXCEPTION DEL\002 removes the given mask from the exception list.\n"
    571 				" \n"
    572 				"\002EXCEPTION LIST\002 and \002EXCEPTION VIEW\002 show all current\n"
    573 				"sessions if the optional mask is given, the list is limited\n"
    574 				"to those sessions matching the mask. The difference is that\n"
    575 				"\002EXCEPTION VIEW\002 is more verbose, displaying the name of the\n"
    576 				"person who added the exception, its session limit, reason,\n"
    577 				"host mask and the expiry date and time.\n"
    578 				" \n"
    579 				"Note that a connecting client will \"use\" the first exception\n"
    580 				"their host matches."));
    581 		return true;
    582 	}
    583 };
    584 
    585 class OSSession : public Module
    586 {
    587 	Serialize::Type exception_type;
    588 	MySessionService ss;
    589 	CommandOSSession commandossession;
    590 	CommandOSException commandosexception;
    591 	ServiceReference<XLineManager> akills;
    592 
    593  public:
    594 	OSSession(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR),
    595 		exception_type("Exception", Exception::Unserialize), ss(this), commandossession(this), commandosexception(this), akills("XLineManager", "xlinemanager/sgline")
    596 	{
    597 		this->SetPermanent(true);
    598 	}
    599 
    600 	void Prioritize() anope_override
    601 	{
    602 		ModuleManager::SetPriority(this, PRIORITY_FIRST);
    603 	}
    604 
    605 	void OnReload(Configuration::Conf *conf) anope_override
    606 	{
    607 		Configuration::Block *block = Config->GetModule(this);
    608 
    609 		session_limit = block->Get<int>("defaultsessionlimit");
    610 		max_session_kill = block->Get<int>("maxsessionkill");
    611 		session_autokill_expiry = block->Get<time_t>("sessionautokillexpiry");
    612 		sle_reason = block->Get<const Anope::string>("sessionlimitexceeded");
    613 		sle_detailsloc = block->Get<const Anope::string>("sessionlimitdetailsloc");
    614 
    615 		max_exception_limit = block->Get<int>("maxsessionlimit");
    616 		exception_expiry = block->Get<time_t>("exceptionexpiry");
    617 
    618 		ipv4_cidr = block->Get<unsigned>("session_ipv4_cidr", "32");
    619 		ipv6_cidr = block->Get<unsigned>("session_ipv6_cidr", "128");
    620 
    621 		if (ipv4_cidr > 32 || ipv6_cidr > 128)
    622 			throw ConfigException(this->name + ": session CIDR value out of range");
    623 	}
    624 
    625 	void OnUserConnect(User *u, bool &exempt) anope_override
    626 	{
    627 		if (u->Quitting() || !session_limit || exempt || !u->server || u->server->IsULined())
    628 			return;
    629 
    630 		cidr u_ip(u->ip, u->ip.ipv6() ? ipv6_cidr : ipv4_cidr);
    631 		if (!u_ip.valid())
    632 			return;
    633 
    634 		Session* &session = this->ss.FindOrCreateSession(u_ip);
    635 
    636 		if (session)
    637 		{
    638 			bool kill = false;
    639 			if (session->count >= session_limit)
    640 			{
    641 				kill = true;
    642 				Exception *exception = this->ss.FindException(u);
    643 				if (exception)
    644 				{
    645 					kill = false;
    646 					if (exception->limit && session->count >= exception->limit)
    647 						kill = true;
    648 				}
    649 			}
    650 
    651 			/* Previously on IRCds that send a QUIT (InspIRCD) when a user is killed, the session for a host was
    652 			 * decremented in do_quit, which caused problems and fixed here
    653 			 *
    654 			 * Now, we create the user struture before calling this to fix some user tracking issues,
    655 			 * so we must increment this here no matter what because it will either be
    656 			 * decremented when the user is killed or quits - Adam
    657 			 */
    658 			++session->count;
    659 
    660 			if (kill && !exempt)
    661 			{
    662 				BotInfo *OperServ = Config->GetClient("OperServ");
    663 				if (OperServ)
    664 				{
    665 					if (!sle_reason.empty())
    666 					{
    667 						Anope::string message = sle_reason.replace_all_cs("%IP%", u->ip.addr());
    668 						u->SendMessage(OperServ, message);
    669 					}
    670 					if (!sle_detailsloc.empty())
    671 						u->SendMessage(OperServ, sle_detailsloc);
    672 				}
    673 
    674 				++session->hits;
    675 
    676 				const Anope::string &akillmask = "*@" + session->addr.mask();
    677 				if (max_session_kill && session->hits >= max_session_kill && akills && !akills->HasEntry(akillmask))
    678 				{
    679 					XLine *x = new XLine(akillmask, OperServ ? OperServ->nick : "", Anope::CurTime + session_autokill_expiry, "Session limit exceeded", XLineManager::GenerateUID());
    680 					akills->AddXLine(x);
    681 					akills->Send(NULL, x);
    682 					Log(OperServ, "akill/session") << "Added a temporary AKILL for \002" << akillmask << "\002 due to excessive connections";
    683 				}
    684 				else
    685 				{
    686 					u->Kill(OperServ, "Session limit exceeded");
    687 				}
    688 			}
    689 		}
    690 		else
    691 		{
    692 			session = new Session(u->ip, u->ip.ipv6() ? ipv6_cidr : ipv4_cidr);
    693 		}
    694 	}
    695 
    696 	void OnUserQuit(User *u, const Anope::string &msg) anope_override
    697 	{
    698 		if (!session_limit || !u->server || u->server->IsULined())
    699 			return;
    700 
    701 		SessionService::SessionMap &sessions = this->ss.GetSessions();
    702 		SessionService::SessionMap::iterator sit = this->ss.FindSessionIterator(u->ip);
    703 
    704 		if (sit == sessions.end())
    705 			return;
    706 
    707 		Session *session = sit->second;
    708 
    709 		if (session->count > 1)
    710 		{
    711 			--session->count;
    712 			return;
    713 		}
    714 
    715 		delete session;
    716 		sessions.erase(sit);
    717 	}
    718 
    719 	void OnExpireTick() anope_override
    720 	{
    721 		if (Anope::NoExpire)
    722 			return;
    723 		for (unsigned i = this->ss.GetExceptions().size(); i > 0; --i)
    724 		{
    725 			Exception *e = this->ss.GetExceptions()[i - 1];
    726 
    727 			if (!e->expires || e->expires > Anope::CurTime)
    728 				continue;
    729 			BotInfo *OperServ = Config->GetClient("OperServ");
    730 			Log(OperServ, "expire/exception") << "Session exception for " << e->mask << " has expired.";
    731 			this->ss.DelException(e);
    732 			delete e;
    733 		}
    734 	}
    735 };
    736 
    737 MODULE_INIT(OSSession)