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> ¶ms) 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> ¶ms) 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> ¶ms) 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> ¶ms) 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> ¶ms) 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> ¶ms, 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> ¶ms) 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> ¶ms) 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> ¶ms) 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)