os_session.cpp

Go to the documentation of this file.
00001 /* OperServ core functions
00002  *
00003  * (C) 2003-2013 Anope Team
00004  * Contact us at team@anope.org
00005  *
00006  * Please read COPYING and README for further details.
00007  *
00008  * Based on the original code of Epona by Lara.
00009  * Based on the original code of Services by Andy Church.
00010  */
00011 
00012 /*************************************************************************/
00013 
00014 #include "module.h"
00015 #include "os_session.h"
00016 
00017 class MySessionService : public SessionService
00018 {
00019         SessionMap Sessions;
00020         Serialize::Checker<ExceptionVector> Exceptions;
00021  public:
00022         MySessionService(Module *m) : SessionService(m), Exceptions("Exception") { }
00023 
00024         void AddException(Exception *e) anope_override
00025         {
00026                 this->Exceptions->push_back(e);
00027         }
00028 
00029         void DelException(Exception *e) anope_override
00030         {
00031                 ExceptionVector::iterator it = std::find(this->Exceptions->begin(), this->Exceptions->end(), e);
00032                 if (it != this->Exceptions->end())
00033                         this->Exceptions->erase(it);
00034         }
00035 
00036         Exception *FindException(User *u) anope_override
00037         {
00038                 for (std::vector<Exception *>::const_iterator it = this->Exceptions->begin(), it_end = this->Exceptions->end(); it != it_end; ++it)
00039                 {
00040                         Exception *e = *it;
00041                         if (Anope::Match(u->host, e->mask) || Anope::Match(u->ip, e->mask))
00042                                 return e;
00043                 }
00044                 return NULL;
00045         }
00046 
00047         Exception *FindException(const Anope::string &host) anope_override
00048         {
00049                 for (std::vector<Exception *>::const_iterator it = this->Exceptions->begin(), it_end = this->Exceptions->end(); it != it_end; ++it)
00050                 {
00051                         Exception *e = *it;
00052                         if (Anope::Match(host, e->mask))
00053                                 return e;
00054                 }
00055 
00056                 return NULL;
00057         }
00058 
00059         ExceptionVector &GetExceptions() anope_override
00060         {
00061                 return this->Exceptions;
00062         }
00063 
00064         void AddSession(Session *s) anope_override
00065         {
00066                 this->Sessions[s->addr] = s;
00067         }
00068 
00069         void DelSession(Session *s) anope_override
00070         {
00071                 this->Sessions.erase(s->addr);
00072         }
00073 
00074         Session *FindSession(const Anope::string &ip) anope_override
00075         {
00076                 cidr c(ip, ip.find(':') != Anope::string::npos ? Config->SessionIPv6CIDR : Config->SessionIPv4CIDR);
00077                 SessionMap::iterator it = this->Sessions.find(c);
00078                 if (it != this->Sessions.end())
00079                         return it->second;
00080                 return NULL;
00081         }
00082 
00083         SessionMap &GetSessions() anope_override
00084         {
00085                 return this->Sessions;
00086         }
00087 };
00088 
00089 class ExpireTimer : public Timer
00090 {
00091  public:
00092         ExpireTimer() : Timer(Config->ExpireTimeout, Anope::CurTime, true) { }
00093 
00094         void Tick(time_t) anope_override
00095         {
00096                 if (!session_service || Anope::NoExpire)
00097                         return;
00098                 for (unsigned i = session_service->GetExceptions().size(); i > 0; --i)
00099                 {
00100                         Exception *e = session_service->GetExceptions()[i - 1];
00101 
00102                         if (!e->expires || e->expires > Anope::CurTime)
00103                                 continue;
00104                         Log(OperServ, "expire/exception") << "Session exception for " << e->mask << "has expired.";
00105                         session_service->DelException(e);
00106                         delete e;
00107                 }
00108         }
00109 };
00110 
00111 class ExceptionDelCallback : public NumberList
00112 {
00113  protected:
00114         CommandSource &source;
00115         unsigned deleted;
00116  public:
00117         ExceptionDelCallback(CommandSource &_source, const Anope::string &numlist) : NumberList(numlist, true), source(_source), deleted(0)
00118         {
00119         }
00120 
00121         ~ExceptionDelCallback()
00122         {
00123                 if (!deleted)
00124                         source.Reply(_("No matching entries on session-limit exception list."));
00125                 else if (deleted == 1)
00126                         source.Reply(_("Deleted 1 entry from session-limit exception list."));
00127                 else
00128                         source.Reply(_("Deleted %d entries from session-limit exception list."), deleted);
00129         }
00130 
00131         virtual void HandleNumber(unsigned number) anope_override
00132         {
00133                 if (!number || number > session_service->GetExceptions().size())
00134                         return;
00135 
00136                 ++deleted;
00137 
00138                 DoDel(source, number - 1);
00139         }
00140 
00141         static void DoDel(CommandSource &source, unsigned index)
00142         {
00143                 Exception *e = session_service->GetExceptions()[index];
00144                 FOREACH_MOD(I_OnExceptionDel, OnExceptionDel(source, e));
00145 
00146                 session_service->DelException(e);
00147                 delete e;
00148         }
00149 };
00150 
00151 class CommandOSSession : public Command
00152 {
00153  private:
00154         void DoList(CommandSource &source, const std::vector<Anope::string> &params)
00155         {
00156                 Anope::string param = params[1];
00157 
00158                 unsigned mincount = 0;
00159                 try
00160                 {
00161                         mincount = convertTo<unsigned>(param);
00162                 }
00163                 catch (const ConvertException &) { }
00164 
00165                 if (mincount <= 1)
00166                         source.Reply(_("Invalid threshold value. It must be a valid integer greater than 1."));
00167                 else
00168                 {
00169                         ListFormatter list;
00170                         list.AddColumn("Session").AddColumn("Host");
00171 
00172                         for (SessionService::SessionMap::iterator it = session_service->GetSessions().begin(), it_end = session_service->GetSessions().end(); it != it_end; ++it)
00173                         {
00174                                 Session *session = it->second;
00175 
00176                                 if (session->count >= mincount)
00177                                 {
00178                                         ListFormatter::ListEntry entry;
00179                                         entry["Session"] = stringify(session->count);
00180                                         entry["Host"] = session->addr.mask();
00181                                         list.AddEntry(entry);
00182                                 }
00183                         }
00184 
00185                         source.Reply(_("Hosts with at least \002%d\002 sessions:"), mincount);
00186 
00187                         std::vector<Anope::string> replies;
00188                         list.Process(replies);
00189 
00190         
00191                         for (unsigned i = 0; i < replies.size(); ++i)
00192                                 source.Reply(replies[i]);
00193                 }
00194 
00195                 return;
00196         }
00197 
00198         void DoView(CommandSource &source, const std::vector<Anope::string> &params)
00199         {
00200                 Anope::string param = params[1];
00201                 Session *session = NULL;
00202                 
00203                 try
00204                 {
00205                         session = session_service->FindSession(param);
00206                 }
00207                 catch (const SocketException &) { }
00208 
00209                 if (!session)
00210                         source.Reply(_("\002%s\002 not found on session list."), param.c_str());
00211                 else
00212                 {
00213                         Exception *exception = session_service->FindException(param);
00214                         source.Reply(_("The host \002%s\002 currently has \002%d\002 sessions with a limit of \002%d\002."), session->addr.mask().c_str(), session->count, exception && exception->limit > Config->DefSessionLimit ? exception->limit : Config->DefSessionLimit);
00215                 }
00216 
00217                 return;
00218         }
00219  public:
00220         CommandOSSession(Module *creator) : Command(creator, "operserv/session", 2, 2)
00221         {
00222                 this->SetDesc(_("View the list of host sessions"));
00223                 this->SetSyntax(_("LIST \037threshold\037"));
00224                 this->SetSyntax(_("VIEW \037host\037"));
00225         }
00226 
00227         void Execute(CommandSource &source, const std::vector<Anope::string> &params) anope_override
00228         {
00229                 const Anope::string &cmd = params[0];
00230 
00231                 if (!Config->LimitSessions)
00232                 {
00233                         source.Reply(_("Session limiting is disabled."));
00234                         return;
00235                 }
00236 
00237                 if (cmd.equals_ci("LIST"))
00238                         return this->DoList(source, params);
00239                 else if (cmd.equals_ci("VIEW"))
00240                         return this->DoView(source, params);
00241                 else
00242                         this->OnSyntaxError(source, "");
00243 
00244                 return;
00245         }
00246 
00247         bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override
00248         {
00249                 this->SendSyntax(source);
00250                 source.Reply(" ");
00251                 source.Reply(_("Allows Services Operators to view the session list.\n"
00252                                 "\002SESSION LIST\002 lists hosts with at least \037threshold\037 sessions.\n"
00253                                 "The threshold must be a number greater than 1. This is to\n"
00254                                 "prevent accidental listing of the large number of single\n"
00255                                 "session hosts.\n"
00256                                 "\002SESSION VIEW\002 displays detailed information about a specific\n"
00257                                 "host - including the current session count and session limit.\n"
00258                                 "The \037host\037 value may not include wildcards.\n"
00259                                 "See the \002EXCEPTION\002 help for more information about session\n"
00260                                 "limiting and how to set session limits specific to certain\n"
00261                                 "hosts and groups thereof."));
00262                 return true;
00263         }
00264 };
00265 
00266 class CommandOSException : public Command
00267 {
00268  private:
00269         void DoAdd(CommandSource &source, const std::vector<Anope::string> &params)
00270         {
00271                 Anope::string mask, expiry, limitstr;
00272                 unsigned last_param = 3;
00273 
00274                 mask = params.size() > 1 ? params[1] : "";
00275                 if (!mask.empty() && mask[0] == '+')
00276                 {
00277                         expiry = mask;
00278                         mask = params.size() > 2 ? params[2] : "";
00279                         last_param = 4;
00280                 }
00281 
00282                 limitstr = params.size() > last_param - 1 ? params[last_param - 1] : "";
00283 
00284                 if (params.size() <= last_param)
00285                 {
00286                         this->OnSyntaxError(source, "ADD");
00287                         return;
00288                 }
00289 
00290                 Anope::string reason = params[last_param];
00291                 if (last_param == 3 && params.size() > 4)
00292                         reason += " " + params[4];
00293                 if (reason.empty())
00294                 {
00295                         this->OnSyntaxError(source, "ADD");
00296                         return;
00297                 }
00298 
00299                 time_t expires = !expiry.empty() ? Anope::DoTime(expiry) : Config->ExceptionExpiry;
00300                 if (expires < 0)
00301                 {
00302                         source.Reply(BAD_EXPIRY_TIME);
00303                         return;
00304                 }
00305                 else if (expires > 0)
00306                         expires += Anope::CurTime;
00307 
00308                 unsigned limit = -1;
00309                 try
00310                 {
00311                         limit = convertTo<unsigned>(limitstr);
00312                 }
00313                 catch (const ConvertException &) { }
00314 
00315                 if (limit > Config->MaxSessionLimit)
00316                 {
00317                         source.Reply(_("Invalid session limit. It must be a valid integer greater than or equal to zero and less than \002%d\002."), Config->MaxSessionLimit);
00318                         return;
00319                 }
00320                 else
00321                 {
00322                         if (mask.find('!') != Anope::string::npos || mask.find('@') != Anope::string::npos)
00323                         {
00324                                 source.Reply(_("Invalid hostmask. Only real hostmasks are valid, as exceptions are not matched against nicks or usernames."));
00325                                 return;
00326                         }
00327 
00328                         for (std::vector<Exception *>::iterator it = session_service->GetExceptions().begin(), it_end = session_service->GetExceptions().end(); it != it_end; ++it)
00329                         {
00330                                 Exception *e = *it;
00331                                 if (e->mask.equals_ci(mask))
00332                                 {
00333                                         if (e->limit != limit)
00334                                         {
00335                                                 e->limit = limit;
00336                                                 source.Reply(_("Exception for \002%s\002 has been updated to %d."), mask.c_str(), e->limit);
00337                                         }
00338                                         else
00339                                                 source.Reply(_("\002%s\002 already exists on the EXCEPTION list."), mask.c_str());
00340                                         return;
00341                                 }
00342                         }
00343 
00344                         Exception *exception = new Exception();
00345                         exception->mask = mask;
00346                         exception->limit = limit;
00347                         exception->reason = reason;
00348                         exception->time = Anope::CurTime;
00349                         exception->who = source.GetNick();
00350                         exception->expires = expires;
00351 
00352                         EventReturn MOD_RESULT;
00353                         FOREACH_RESULT(I_OnExceptionAdd, OnExceptionAdd(exception));
00354                         if (MOD_RESULT == EVENT_STOP)
00355                                 delete exception;
00356                         else
00357                         {
00358                                 session_service->AddException(exception);
00359                                 source.Reply(_("Session limit for \002%s\002 set to \002%d\002."), mask.c_str(), limit);
00360                                 if (Anope::ReadOnly)
00361                                         source.Reply(READ_ONLY_MODE);
00362                         }
00363                 }
00364 
00365                 return;
00366         }
00367 
00368         void DoDel(CommandSource &source, const std::vector<Anope::string> &params)
00369         {
00370                 const Anope::string &mask = params.size() > 1 ? params[1] : "";
00371 
00372                 if (mask.empty())
00373                 {
00374                         this->OnSyntaxError(source, "DEL");
00375                         return;
00376                 }
00377 
00378                 if (isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos)
00379                 {
00380                         ExceptionDelCallback list(source, mask);
00381                         list.Process();
00382                 }
00383                 else
00384                 {
00385                         unsigned i = 0, end = session_service->GetExceptions().size();
00386                         for (; i < end; ++i)
00387                                 if (mask.equals_ci(session_service->GetExceptions()[i]->mask))
00388                                 {
00389                                         ExceptionDelCallback::DoDel(source, i);
00390                                         source.Reply(_("\002%s\002 deleted from session-limit exception list."), mask.c_str());
00391                                         break;
00392                                 }
00393                         if (i == end)
00394                                 source.Reply(_("\002%s\002 not found on session-limit exception list."), mask.c_str());
00395                 }
00396 
00397                 if (Anope::ReadOnly)
00398                         source.Reply(READ_ONLY_MODE);
00399 
00400                 return;
00401         }
00402 
00403         void DoMove(CommandSource &source, const std::vector<Anope::string> &params)
00404         {
00405                 const Anope::string &n1str = params.size() > 1 ? params[1] : ""; /* From position */
00406                 const Anope::string &n2str = params.size() > 2 ? params[2] : ""; /* To position */
00407                 int n1, n2;
00408 
00409                 if (n2str.empty())
00410                 {
00411                         this->OnSyntaxError(source, "MOVE");
00412                         return;
00413                 }
00414 
00415                 n1 = n2 = -1;
00416                 try
00417                 {
00418                         n1 = convertTo<int>(n1str);
00419                         n2 = convertTo<int>(n2str);
00420                 }
00421                 catch (const ConvertException &) { }
00422 
00423                 if (n1 >= 0 && static_cast<unsigned>(n1) < session_service->GetExceptions().size() && n2 >= 0 && static_cast<unsigned>(n2) < session_service->GetExceptions().size() && n1 != n2)
00424                 {
00425                         Exception *temp = session_service->GetExceptions()[n1];
00426                         session_service->GetExceptions()[n1] = session_service->GetExceptions()[n2];
00427                         session_service->GetExceptions()[n2] = temp;
00428 
00429                         source.Reply(_("Exception for \002%s\002 (#%d) moved to position \002%d\002."), session_service->GetExceptions()[n1]->mask.c_str(), n1 + 1, n2 + 1);
00430 
00431                         if (Anope::ReadOnly)
00432                                 source.Reply(READ_ONLY_MODE);
00433                 }
00434                 else
00435                         this->OnSyntaxError(source, "MOVE");
00436 
00437                 return;
00438         }
00439 
00440         void ProcessList(CommandSource &source, const std::vector<Anope::string> &params, ListFormatter &list)
00441         {
00442                 const Anope::string &mask = params.size() > 1 ? params[1] : "";
00443 
00444                 if (session_service->GetExceptions().empty())
00445                 {
00446                         source.Reply(_("The session exception list is empty."));
00447                         return;
00448                 }
00449 
00450                 if (!mask.empty() && mask.find_first_not_of("1234567890,-") == Anope::string::npos)
00451                 {
00452                         class ExceptionListCallback : public NumberList
00453                         {
00454                                 ListFormatter &list;
00455                          public:
00456                                 ExceptionListCallback(ListFormatter &_list, const Anope::string &numlist) : NumberList(numlist, false), list(_list)
00457                                 {
00458                                 }
00459 
00460                                 void HandleNumber(unsigned Number) anope_override
00461                                 {
00462                                         if (!Number || Number > session_service->GetExceptions().size())
00463                                                 return;
00464 
00465                                         Exception *e = session_service->GetExceptions()[Number - 1];
00466 
00467                                         ListFormatter::ListEntry entry;
00468                                         entry["Number"] = stringify(Number);
00469                                         entry["Mask"] = e->mask;
00470                                         entry["By"] = e->who;
00471                                         entry["Created"] = Anope::strftime(e->time);
00472                                         entry["Limit"] = stringify(e->limit);
00473                                         entry["Reason"] = e->reason;
00474                                         this->list.AddEntry(entry);
00475                                 }
00476                         }
00477                         nl_list(list, mask);
00478                         nl_list.Process();
00479                 }
00480                 else
00481                 {
00482                         for (unsigned i = 0, end = session_service->GetExceptions().size(); i < end; ++i)
00483                         {
00484                                 Exception *e = session_service->GetExceptions()[i];
00485                                 if (mask.empty() || Anope::Match(e->mask, mask))
00486                                 {
00487                                         ListFormatter::ListEntry entry;
00488                                         entry["Number"] = stringify(i + 1);
00489                                         entry["Mask"] = e->mask;
00490                                         entry["By"] = e->who;
00491                                         entry["Created"] = Anope::strftime(e->time);
00492                                         entry["Limit"] = stringify(e->limit);
00493                                         entry["Reason"] = e->reason;
00494                                         list.AddEntry(entry);
00495                                 }
00496                         }
00497                 }
00498 
00499                 if (list.IsEmpty())
00500                         source.Reply(_("No matching entries on session-limit exception list."));
00501                 else
00502                 {
00503                         source.Reply(_("Current Session Limit Exception list:"));
00504                 
00505                         std::vector<Anope::string> replies;
00506                         list.Process(replies);
00507 
00508                         for (unsigned i = 0; i < replies.size(); ++i)
00509                                 source.Reply(replies[i]);
00510                 }
00511         }
00512 
00513         void DoList(CommandSource &source, const std::vector<Anope::string> &params)
00514         {
00515                 ListFormatter list;
00516                 list.AddColumn("Number").AddColumn("Limit").AddColumn("Mask");
00517 
00518                 this->ProcessList(source, params, list);
00519         }
00520 
00521         void DoView(CommandSource &source, const std::vector<Anope::string> &params)
00522         {
00523                 ListFormatter list;
00524                 list.AddColumn("Number").AddColumn("Mask").AddColumn("By").AddColumn("Created").AddColumn("Limit").AddColumn("Reason");
00525 
00526                 this->ProcessList(source, params, list);
00527         }
00528 
00529  public:
00530         CommandOSException(Module *creator) : Command(creator, "operserv/exception", 1, 5)
00531         {
00532                 this->SetDesc(_("Modify the session-limit exception list"));
00533                 this->SetSyntax(_("ADD [\037+expiry\037] \037mask\037 \037limit\037 \037reason\037"));
00534                 this->SetSyntax(_("DEL {\037mask\037 | \037list\037}"));
00535                 this->SetSyntax(_("MOVE \037num\037 \037position\037"));
00536                 this->SetSyntax(_("LIST [\037mask\037 | \037list\037]"));
00537                 this->SetSyntax(_("VIEW [\037mask\037 | \037list\037]"));
00538         }
00539 
00540         void Execute(CommandSource &source, const std::vector<Anope::string> &params) anope_override
00541         {
00542                 const Anope::string &cmd = params[0];
00543 
00544                 if (!Config->LimitSessions)
00545                 {
00546                         source.Reply(_("Session limiting is disabled."));
00547                         return;
00548                 }
00549 
00550                 if (cmd.equals_ci("ADD"))
00551                         return this->DoAdd(source, params);
00552                 else if (cmd.equals_ci("DEL"))
00553                         return this->DoDel(source, params);
00554                 else if (cmd.equals_ci("MOVE"))
00555                         return this->DoMove(source, params);
00556                 else if (cmd.equals_ci("LIST"))
00557                         return this->DoList(source, params);
00558                 else if (cmd.equals_ci("VIEW"))
00559                         return this->DoView(source, params);
00560                 else
00561                         this->OnSyntaxError(source, "");
00562 
00563                 return;
00564         }
00565 
00566         bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override
00567         {
00568                 this->SendSyntax(source);
00569                 source.Reply(" ");
00570                 source.Reply(_("Allows Services Operators to manipulate the list of hosts that\n"
00571                                 "have specific session limits - allowing certain machines,\n"
00572                                 "such as shell servers, to carry more than the default number\n"
00573                                 "of clients at a time. Once a host reaches its session limit,\n"
00574                                 "all clients attempting to connect from that host will be\n"
00575                                 "killed. Before the user is killed, they are notified, via a\n"
00576                                 "/NOTICE from %s, of a source of help regarding session\n"
00577                                 "limiting. The content of this notice is a config setting.\n"),
00578                                 Config->OperServ.c_str());
00579                 source.Reply(" ");
00580                 source.Reply(_("\002EXCEPTION ADD\002 adds the given host mask to the exception list.\n"
00581                                 "Note that \002nick!user@host\002 and \002user@host\002 masks are invalid!\n"
00582                                 "Only real host masks, such as \002box.host.dom\002 and \002*.host.dom\002,\n"
00583                                 "are allowed because sessions limiting does not take nick or\n"
00584                                 "user names into account. \037limit\037 must be a number greater than\n"
00585                                 "or equal to zero. This determines how many sessions this host\n"
00586                                 "may carry at a time. A value of zero means the host has an\n"
00587                                 "unlimited session limit. See the \002AKILL\002 help for details about\n"
00588                                 "the format of the optional \037expiry\037 parameter.\n"
00589                                 "\002EXCEPTION DEL\002 removes the given mask from the exception list.\n"
00590                                 "\002EXCEPTION MOVE\002 moves exception \037num\037 to \037position\037. The\n"
00591                                 "sessions inbetween will be shifted up or down to fill the gap.\n"
00592                                 "\002EXCEPTION LIST\002 and \002EXCEPTION VIEW\002 show all current\n"
00593                                 "sessions if the optional mask is given, the list is limited\n"
00594                                 "to those sessions matching the mask. The difference is that\n"
00595                                 "\002EXCEPTION VIEW\002 is more verbose, displaying the name of the\n"
00596                                 "person who added the exception, its session limit, reason,\n"
00597                                 "host mask and the expiry date and time.\n"
00598                                 " \n"
00599                                 "Note that a connecting client will \"use\" the first exception\n"
00600                                 "their host matches."));
00601                 return true;
00602         }
00603 };
00604 
00605 class OSSession : public Module
00606 {
00607         Serialize::Type exception_type;
00608         MySessionService ss;
00609         ExpireTimer expiretimer;
00610         CommandOSSession commandossession;
00611         CommandOSException commandosexception;
00612         ServiceReference<XLineManager> akills;
00613 
00614         void AddSession(User *u, bool exempt)
00615         {
00616                 Session *session;
00617                 try
00618                 {
00619                         session = this->ss.FindSession(u->ip);
00620                 }
00621                 catch (const SocketException &)
00622                 {
00623                         return;
00624                 }
00625 
00626                 if (session)
00627                 {
00628                         bool kill = false;
00629                         if (Config->DefSessionLimit && session->count >= Config->DefSessionLimit)
00630                         {
00631                                 kill = true;
00632                                 Exception *exception = this->ss.FindException(u);
00633                                 if (exception)
00634                                 {
00635                                         kill = false;
00636                                         if (exception->limit && session->count >= exception->limit)
00637                                                 kill = true;
00638                                 }
00639                         }
00640 
00641                         /* Previously on IRCds that send a QUIT (InspIRCD) when a user is killed, the session for a host was
00642                          * decremented in do_quit, which caused problems and fixed here
00643                          *
00644                          * Now, we create the user struture before calling this to fix some user tracking issues,
00645                          * so we must increment this here no matter what because it will either be
00646                          * decremented in do_kill or in do_quit - Adam
00647                          */
00648                         ++session->count;
00649         
00650                         if (kill && !exempt)
00651                         {
00652                                 if (OperServ)
00653                                 {
00654                                         if (!Config->SessionLimitExceeded.empty())
00655                                                 u->SendMessage(OperServ, Config->SessionLimitExceeded.c_str(), u->ip.c_str());
00656                                         if (!Config->SessionLimitDetailsLoc.empty())
00657                                                 u->SendMessage(OperServ, "%s", Config->SessionLimitDetailsLoc.c_str());
00658                                 }
00659 
00660                                 ++session->hits;
00661                                 if (Config->MaxSessionKill && session->hits >= Config->MaxSessionKill && akills)
00662                                 {
00663                                         const Anope::string &akillmask = "*@" + u->ip;
00664                                         XLine *x = new XLine(akillmask, Config->OperServ, Anope::CurTime + Config->SessionAutoKillExpiry, "Session limit exceeded", XLineManager::GenerateUID());
00665                                         akills->AddXLine(x);
00666                                         akills->Send(NULL, x);
00667                                         Log(OperServ, "akill/session") << "Added a temporary AKILL for \002" << akillmask << "\002 due to excessive connections";
00668                                 }
00669                                 else
00670                                 {
00671                                         u->Kill(Config->OperServ, "Session limit exceeded");
00672                                         u = NULL; /* No guarentee u still exists */
00673                                 }
00674                         }
00675                 }
00676                 else
00677                 {
00678                         session = new Session(u->ip, u->ip.find(':') != Anope::string::npos ? Config->SessionIPv6CIDR : Config->SessionIPv4CIDR);
00679                         this->ss.AddSession(session);
00680                 }
00681         }
00682 
00683         void DelSession(User *u)
00684         {
00685                 Session *session;
00686                 try
00687                 {
00688                         session = this->ss.FindSession(u->ip);
00689                 }
00690                 catch (const SocketException &)
00691                 {
00692                         return;
00693                 }
00694                 if (!session)
00695                 {
00696                         Log(LOG_DEBUG) << "Tried to delete non-existant session: " << u->ip;
00697                         return;
00698                 }
00699 
00700                 if (session->count > 1)
00701                 {
00702                         --session->count;
00703                         return;
00704                 }
00705 
00706                 this->ss.DelSession(session);
00707                 delete session;
00708         }
00709 
00710  public:
00711         OSSession(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE),
00712                 exception_type("Exception", Exception::Unserialize), ss(this), commandossession(this), commandosexception(this), akills("XLineManager", "xlinemanager/sgline")
00713         {
00714                 this->SetAuthor("Anope");
00715                 this->SetPermanent(true);
00716 
00717                 Implementation i[] = { I_OnUserConnect, I_OnPreUserLogoff };
00718                 ModuleManager::Attach(i, this, sizeof(i) / sizeof(Implementation));
00719                 ModuleManager::SetPriority(this, PRIORITY_FIRST);
00720         }
00721 
00722         void OnUserConnect(User *user, bool &exempt) anope_override
00723         {
00724                 if (!user->Quitting() && Config->LimitSessions)
00725                         this->AddSession(user, exempt);
00726         }
00727 
00728         void OnPreUserLogoff(User *u) anope_override
00729         {
00730                 if (Config->LimitSessions && (!u->server || !u->server->IsULined()))
00731                         this->DelSession(u);
00732         }
00733 };
00734 
00735 MODULE_INIT(OSSession)