X-Git-Url: https://git.netwichtig.de/gitweb/?a=blobdiff_plain;f=src%2Finspircd.cpp;h=6d82723612631b1796e6d5f39bb6a1bd4787c8ee;hb=9ad873886e518bf3621a88e8c48607ab79020c0a;hp=553e09b73f189a944719336ad2f6fe4c7e907390;hpb=a3d2b4475708520207d95d66a6ecd5a35e176ab9;p=user%2Fhenk%2Fcode%2Finspircd.git diff --git a/src/inspircd.cpp b/src/inspircd.cpp index 553e09b73..6d8272361 100644 --- a/src/inspircd.cpp +++ b/src/inspircd.cpp @@ -30,17 +30,10 @@ #include #ifndef _WIN32 - #include #include #include #include #include - - /* Some systems don't define RUSAGE_SELF. This should fix them. */ - #ifndef RUSAGE_SELF - #define RUSAGE_SELF 0 - #endif - #include // setuid #include // setgid #else @@ -53,7 +46,6 @@ #include #include "xline.h" #include "exitcodes.h" -#include "testsuite.h" InspIRCd* ServerInstance = NULL; @@ -84,18 +76,151 @@ const char* ExitCodes[] = "Received SIGTERM" // 10 }; -#ifdef INSPIRCD_ENABLE_TESTSUITE -/** True if we have been told to run the testsuite from the commandline, - * rather than entering the mainloop. - */ -static int do_testsuite = 0; +namespace +{ + void VoidSignalHandler(int); + + // Deletes a pointer and then zeroes it. + template + void DeleteZero(T*& pr) + { + T* p = pr; + pr = NULL; + delete p; + } + + // Drops to the unprivileged user/group specified in . + void DropRoot() + { +#ifndef _WIN32 + ConfigTag* security = ServerInstance->Config->ConfValue("security"); + + const std::string SetGroup = security->getString("runasgroup"); + if (!SetGroup.empty()) + { + errno = 0; + if (setgroups(0, NULL) == -1) + { + ServerInstance->Logs->Log("STARTUP", LOG_DEFAULT, "setgroups() failed (wtf?): %s", strerror(errno)); + exit(EXIT_STATUS_CONFIG); + } + + struct group* g = getgrnam(SetGroup.c_str()); + if (!g) + { + ServerInstance->Logs->Log("STARTUP", LOG_DEFAULT, "getgrnam(%s) failed (wrong group?): %s", SetGroup.c_str(), strerror(errno)); + exit(EXIT_STATUS_CONFIG); + } + + if (setgid(g->gr_gid) == -1) + { + ServerInstance->Logs->Log("STARTUP", LOG_DEFAULT, "setgid(%d) failed (wrong group?): %s", g->gr_gid, strerror(errno)); + exit(EXIT_STATUS_CONFIG); + } + } + + const std::string SetUser = security->getString("runasuser"); + if (!SetUser.empty()) + { + errno = 0; + struct passwd* u = getpwnam(SetUser.c_str()); + if (!u) + { + ServerInstance->Logs->Log("STARTUP", LOG_DEFAULT, "getpwnam(%s) failed (wrong user?): %s", SetUser.c_str(), strerror(errno)); + exit(EXIT_STATUS_CONFIG); + } + + if (setuid(u->pw_uid) == -1) + { + ServerInstance->Logs->Log("STARTUP", LOG_DEFAULT, "setuid(%d) failed (wrong user?): %s", u->pw_uid, strerror(errno)); + exit(EXIT_STATUS_CONFIG); + } + } #endif + } -template static void DeleteZero(T*&n) -{ - T* t = n; - n = NULL; - delete t; + // Attempts to fork into the background. + bool ForkIntoBackground() + { +#ifndef _WIN32 + // We use VoidSignalHandler whilst forking to avoid breaking daemon scripts + // if the parent process exits with SIGTERM (15) instead of EXIT_STATUS_NOERROR (0). + signal(SIGTERM, VoidSignalHandler); + + errno = 0; + int childpid = fork(); + if (childpid < 0) + { + ServerInstance->Logs->Log("STARTUP", LOG_DEFAULT, "fork() failed: %s", strerror(errno)); + return false; + } + else if (childpid > 0) + { + // Wait until the child process kills the parent so that the shell prompt + // doesnt display over the output. Sending a kill with a signal of 0 just + // checks that the child pid is still running. If it is not then an error + // happened and the parent should exit. + while (kill(childpid, 0) != -1) + sleep(1); + exit(EXIT_STATUS_NOERROR); + } + else + { + setsid(); + signal(SIGTERM, InspIRCd::SetSignal); + } +#endif + return true; + } + + // Increase the size of a core dump file to improve debugging problems. + void IncreaseCoreDumpSize() + { +#ifndef _WIN32 + errno = 0; + rlimit rl; + if (getrlimit(RLIMIT_CORE, &rl) == -1) + { + ServerInstance->Logs->Log("STARTUP", LOG_DEFAULT, "Unable to increase core dump size: getrlimit(RLIMIT_CORE) failed: %s", strerror(errno)); + return; + } + + rl.rlim_cur = rl.rlim_max; + if (setrlimit(RLIMIT_CORE, &rl) == -1) + ServerInstance->Logs->Log("STARTUP", LOG_DEFAULT, "Unable to increase core dump size: setrlimit(RLIMIT_CORE) failed: %s", strerror(errno)); +#endif + } + + // Seeds the random number generator if applicable. + void SeedRng(timespec ts) + { +#if defined _WIN32 + srand(ts.tv_nsec ^ ts.tv_sec); +#elif !defined HAS_ARC4RANDOM_BUF + srandom(ts.tv_nsec ^ ts.tv_sec); +#endif + } + + // Sets handlers for various process signals. + void SetSignals() + { +#ifndef _WIN32 + signal(SIGALRM, SIG_IGN); + signal(SIGCHLD, SIG_IGN); + signal(SIGHUP, InspIRCd::SetSignal); + signal(SIGPIPE, SIG_IGN); + signal(SIGUSR1, SIG_IGN); + signal(SIGUSR2, SIG_IGN); + signal(SIGXFSZ, SIG_IGN); +#endif + signal(SIGTERM, InspIRCd::SetSignal); + } + + // Required for returning the proper value of EXIT_SUCCESS for the parent process. + void VoidSignalHandler(int) + { + exit(EXIT_STATUS_NOERROR); + } } void InspIRCd::Cleanup() @@ -108,10 +233,14 @@ void InspIRCd::Cleanup() } ports.clear(); - /* Close all client sockets, or the new process inherits them */ - LocalUserList& list = Users->local_users; - for (LocalUserList::iterator i = list.begin(); i != list.end(); ++i) - Users->QuitUser(*i, "Server shutdown"); + // Tell modules that we're shutting down. + const std::string quitmsg = "Server shutting down"; + FOREACH_MOD(OnShutdown, (quitmsg)); + + // Disconnect all local users + const UserManager::LocalList& list = Users.GetLocalUsers(); + while (!list.empty()) + ServerInstance->Users.QuitUser(list.front(), quitmsg); GlobalCulls.Apply(); Modules->UnloadAll(); @@ -124,89 +253,22 @@ void InspIRCd::Cleanup() FakeClient->cull(); } DeleteZero(this->FakeClient); - DeleteZero(this->Users); DeleteZero(this->XLines); - DeleteZero(this->SNO); DeleteZero(this->Config); SocketEngine::Deinit(); Logs->CloseLogs(); } -void InspIRCd::SetSignals() +void InspIRCd::WritePID(const std::string& filename, bool exitonfail) { #ifndef _WIN32 - signal(SIGALRM, SIG_IGN); - signal(SIGHUP, InspIRCd::SetSignal); - signal(SIGPIPE, SIG_IGN); - signal(SIGCHLD, SIG_IGN); - /* We want E2BIG not a signal! */ - signal(SIGXFSZ, SIG_IGN); -#endif - signal(SIGTERM, InspIRCd::SetSignal); -} - -void InspIRCd::QuickExit(int status) -{ - exit(status); -} - -// Required for returning the proper value of EXIT_SUCCESS for the parent process -static void VoidSignalHandler(int signalreceived) -{ - exit(0); -} - -bool InspIRCd::DaemonSeed() -{ -#ifdef _WIN32 - std::cout << "InspIRCd Process ID: " << con_green << GetCurrentProcessId() << con_reset << std::endl; - return true; -#else - // Do not use QuickExit here: It will exit with status SIGTERM which would break e.g. daemon scripts - signal(SIGTERM, VoidSignalHandler); - - int childpid = fork(); - if (childpid < 0) - return false; - else if (childpid > 0) - { - /* We wait here for the child process to kill us, - * so that the shell prompt doesnt come back over - * the output. - * Sending a kill with a signal of 0 just checks - * if the child pid is still around. If theyre not, - * they threw an error and we should give up. - */ - while (kill(childpid, 0) != -1) - sleep(1); - exit(0); - } - setsid (); - std::cout << "InspIRCd Process ID: " << con_green << getpid() << con_reset << std::endl; - - signal(SIGTERM, InspIRCd::SetSignal); - - rlimit rl; - if (getrlimit(RLIMIT_CORE, &rl) == -1) + if (!ServerInstance->Config->cmdline.writepid) { - this->Logs->Log("STARTUP", LOG_DEFAULT, "Failed to getrlimit()!"); - return false; + this->Logs->Log("STARTUP", LOG_DEFAULT, "--nopid specified on command line; PID file not written."); + return; } - rl.rlim_cur = rl.rlim_max; - - if (setrlimit(RLIMIT_CORE, &rl) == -1) - this->Logs->Log("STARTUP", LOG_DEFAULT, "setrlimit() failed, cannot increase coredump size."); - return true; -#endif -} - -void InspIRCd::WritePID(const std::string &filename) -{ -#ifndef _WIN32 - std::string fname(filename); - if (fname.empty()) - fname = ServerInstance->Config->Paths.PrependData("inspircd.pid"); + std::string fname = ServerInstance->Config->Paths.PrependData(filename.empty() ? "inspircd.pid" : filename); std::ofstream outfile(fname.c_str()); if (outfile.is_open()) { @@ -215,56 +277,36 @@ void InspIRCd::WritePID(const std::string &filename) } else { - std::cout << "Failed to write PID-file '" << fname << "', exiting." << std::endl; - this->Logs->Log("STARTUP", LOG_DEFAULT, "Failed to write PID-file '%s', exiting.",fname.c_str()); - Exit(EXIT_STATUS_PID); + if (exitonfail) + std::cout << "Failed to write PID-file '" << fname << "', exiting." << std::endl; + this->Logs->Log("STARTUP", LOG_DEFAULT, "Failed to write PID-file '%s'%s", fname.c_str(), (exitonfail ? ", exiting." : "")); + if (exitonfail) + Exit(EXIT_STATUS_PID); } #endif } -InspIRCd::InspIRCd(int argc, char** argv) : - ConfigFileName(INSPIRCD_CONFIG_PATH "/inspircd.conf"), - PI(&DefaultProtocolInterface), - - /* Functor pointer initialisation. - * - * THIS MUST MATCH THE ORDER OF DECLARATION OF THE FUNCTORS, e.g. the methods - * themselves within the class. - */ - OperQuit("operquit", NULL), - GenRandom(&HandleGenRandom), - IsChannel(&HandleIsChannel), - IsNick(&HandleIsNick), - IsIdent(&HandleIsIdent), - OnCheckExemption(&HandleOnCheckExemption) +InspIRCd::InspIRCd(int argc, char** argv) + : FakeClient(NULL) + , ConfigFileName(INSPIRCD_CONFIG_PATH "/inspircd.conf") + , ConfigThread(NULL) + , Config(NULL) + , XLines(NULL) + , PI(&DefaultProtocolInterface) + , GenRandom(&DefaultGenRandom) + , IsChannel(&DefaultIsChannel) + , IsNick(&DefaultIsNick) + , IsIdent(&DefaultIsIdent) { ServerInstance = this; - Extensions.Register(&OperQuit); - - FailedPortList pl; - // Flag variables passed to getopt_long() later - int do_version = 0, do_nofork = 0, do_debug = 0, - do_nolog = 0, do_root = 0; - - // Initialize so that if we exit before proper initialization they're not deleted - this->Users = 0; - this->Config = 0; - this->SNO = 0; - this->XLines = 0; - this->ConfigThread = NULL; - this->FakeClient = NULL; - UpdateTime(); this->startup_time = TIME.tv_sec; + SeedRng(TIME); SocketEngine::Init(); - // Create base manager classes early, so nothing breaks - this->Users = new UserManager; - this->Config = new ServerConfig; - this->SNO = new SnomaskManager; dynamic_reference_base::reset_all(); this->XLines = new XLineManager; @@ -272,8 +314,6 @@ InspIRCd::InspIRCd(int argc, char** argv) : this->Config->cmdline.argc = argc; #ifdef _WIN32 - srand(TIME.tv_nsec ^ TIME.tv_sec); - // Initialize the console values g_hStdout = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_SCREEN_BUFFER_INFO bufinf; @@ -287,21 +327,30 @@ InspIRCd::InspIRCd(int argc, char** argv) : g_wOriginalColors = FOREGROUND_RED|FOREGROUND_BLUE|FOREGROUND_GREEN; g_wBackgroundColor = 0; } -#else - srandom(TIME.tv_nsec ^ TIME.tv_sec); #endif + { + ServiceProvider* provs[] = + { + &rfcevents.numeric, &rfcevents.join, &rfcevents.part, &rfcevents.kick, &rfcevents.quit, &rfcevents.nick, + &rfcevents.mode, &rfcevents.topic, &rfcevents.privmsg, &rfcevents.invite, &rfcevents.ping, &rfcevents.pong, + &rfcevents.error + }; + Modules.AddServices(provs, sizeof(provs)/sizeof(provs[0])); + } + + // Flag variables passed to getopt_long() later + int do_version = 0, do_nofork = 0, do_debug = 0, + do_nolog = 0, do_nopid = 0, do_root = 0; struct option longopts[] = { { "nofork", no_argument, &do_nofork, 1 }, { "config", required_argument, NULL, 'c' }, { "debug", no_argument, &do_debug, 1 }, { "nolog", no_argument, &do_nolog, 1 }, + { "nopid", no_argument, &do_nopid, 1 }, { "runasroot", no_argument, &do_root, 1 }, { "version", no_argument, &do_version, 1 }, -#ifdef INSPIRCD_ENABLE_TESTSUITE - { "testsuite", no_argument, &do_testsuite, 1 }, -#endif { 0, 0, 0, 0 } }; @@ -313,7 +362,16 @@ InspIRCd::InspIRCd(int argc, char** argv) : { case 'c': /* Config filename was set */ - ConfigFileName = ServerInstance->Config->Paths.PrependConfig(optarg); + ConfigFileName = optarg; +#ifdef _WIN32 + TCHAR configPath[MAX_PATH + 1]; + if (GetFullPathName(optarg, MAX_PATH, configPath, NULL) > 0) + ConfigFileName = configPath; +#else + char configPath[PATH_MAX + 1]; + if (realpath(optarg, configPath)) + ConfigFileName = configPath; +#endif break; case 0: /* getopt_long_only() set an int variable, just keep going */ @@ -323,21 +381,16 @@ InspIRCd::InspIRCd(int argc, char** argv) : default: /* Fall through to handle other weird values too */ std::cout << "Unknown parameter '" << argv[optind-1] << "'" << std::endl; - std::cout << "Usage: " << argv[0] << " [--nofork] [--nolog] [--debug] [--config ]" << std::endl << - std::string(static_cast(8+strlen(argv[0])), ' ') << "[--runasroot] [--version]" << std::endl; + std::cout << "Usage: " << argv[0] << " [--nofork] [--nolog] [--nopid] [--debug] [--config ]" << std::endl << + std::string(static_cast(8+strlen(argv[0])), ' ') << "[--runasroot] [--version]" << std::endl; Exit(EXIT_STATUS_ARGV); break; } } -#ifdef INSPIRCD_ENABLE_TESTSUITE - if (do_testsuite) - do_nofork = do_debug = true; -#endif - if (do_version) { - std::cout << std::endl << INSPIRCD_VERSION << " " << INSPIRCD_REVISION << std::endl; + std::cout << std::endl << INSPIRCD_VERSION << std::endl; Exit(EXIT_STATUS_NOERROR); } @@ -351,10 +404,11 @@ InspIRCd::InspIRCd(int argc, char** argv) : Config->cmdline.nofork = (do_nofork != 0); Config->cmdline.forcedebug = (do_debug != 0); Config->cmdline.writelog = !do_nolog; + Config->cmdline.writepid = !do_nopid; if (do_debug) { - FileWriter* fw = new FileWriter(stdout); + FileWriter* fw = new FileWriter(stdout, 1); FileLogStream* fls = new FileLogStream(LOG_RAWIO, fw); Logs->AddLogTypes("*", fls, true); } @@ -379,13 +433,8 @@ InspIRCd::InspIRCd(int argc, char** argv) : } } - std::cout << con_green << "Inspire Internet Relay Chat Server" << con_reset << ", compiled on " __DATE__ " at " __TIME__ << std::endl; - std::cout << con_green << "(C) InspIRCd Development Team." << con_reset << std::endl << std::endl; - std::cout << "Developers:" << std::endl; - std::cout << con_green << "\tBrain, FrostyCoolSlug, w00t, Om, Special, peavey" << std::endl; - std::cout << "\taquanight, psychon, dz, danieldg, jackmcbarn" << std::endl; - std::cout << "\tAttila" << con_reset << std::endl << std::endl; - std::cout << "Others:\t\t\t" << con_green << "See /INFO Output" << con_reset << std::endl; + std::cout << con_green << "InspIRCd - Internet Relay Chat Daemon" << con_reset << std::endl; + std::cout << "For contributors & authors: " << con_green << "See /INFO Output" << con_reset << std::endl; #ifndef _WIN32 if (!do_root) @@ -404,18 +453,18 @@ InspIRCd::InspIRCd(int argc, char** argv) : } #endif - this->SetSignals(); + SetSignals(); - if (!Config->cmdline.nofork) + if (!Config->cmdline.nofork && !ForkIntoBackground()) { - if (!this->DaemonSeed()) - { - std::cout << "ERROR: could not go into daemon mode. Shutting down." << std::endl; - Logs->Log("STARTUP", LOG_DEFAULT, "ERROR: could not go into daemon mode. Shutting down."); - Exit(EXIT_STATUS_FORK); - } + std::cout << "ERROR: could not go into daemon mode. Shutting down." << std::endl; + Logs->Log("STARTUP", LOG_DEFAULT, "ERROR: could not go into daemon mode. Shutting down."); + Exit(EXIT_STATUS_FORK); } + std::cout << "InspIRCd Process ID: " << con_green << getpid() << con_reset << std::endl; + + IncreaseCoreDumpSize(); SocketEngine::RecoverFromFork(); /* During startup we read the configuration now, not in @@ -424,7 +473,6 @@ InspIRCd::InspIRCd(int argc, char** argv) : this->Config->Read(); this->Config->Apply(NULL, ""); Logs->OpenFileLogs(); - ModeParser::InitBuiltinModes(); // If we don't have a SID, generate one based on the server name and the server description if (Config->sid.empty()) @@ -439,6 +487,7 @@ InspIRCd::InspIRCd(int argc, char** argv) : // This is needed as all new XLines are marked pending until ApplyLines() is called this->XLines->ApplyLines(); + FailedPortList pl; int bounditems = BindPorts(pl); std::cout << std::endl; @@ -447,7 +496,6 @@ InspIRCd::InspIRCd(int argc, char** argv) : // Build ISupport as ModuleManager::LoadAll() does not do it this->ISupport.Build(); - Config->ApplyDisabledCommands(Config->DisabledCommands); if (!pl.empty()) { @@ -457,7 +505,7 @@ InspIRCd::InspIRCd(int argc, char** argv) : int j = 1; for (FailedPortList::iterator i = pl.begin(); i != pl.end(); i++, j++) { - std::cout << j << ".\tAddress: " << (i->first.empty() ? "" : i->first) << " \tReason: " << i->second << std::endl; + std::cout << j << ".\tAddress: " << i->first.str() << " \tReason: " << strerror(i->second) << std::endl; } std::cout << std::endl << "Hint: Try using a public IP instead of blank or *" << std::endl; @@ -518,104 +566,33 @@ InspIRCd::InspIRCd(int argc, char** argv) : QueryPerformanceFrequency(&stats.QPFrequency); #endif - Logs->Log("STARTUP", LOG_DEFAULT, "Startup complete as '%s'[%s], %d max open sockets", Config->ServerName.c_str(),Config->GetSID().c_str(), SocketEngine::GetMaxFds()); - -#ifndef _WIN32 - std::string SetUser = Config->ConfValue("security")->getString("runasuser"); - std::string SetGroup = Config->ConfValue("security")->getString("runasgroup"); - if (!SetGroup.empty()) - { - int ret; + WritePID(Config->PID); + DropRoot(); - // setgroups - ret = setgroups(0, NULL); - - if (ret == -1) - { - this->Logs->Log("STARTUP", LOG_DEFAULT, "setgroups() failed (wtf?): %s", strerror(errno)); - this->QuickExit(0); - } - - // setgid - struct group *g; - - errno = 0; - g = getgrnam(SetGroup.c_str()); - - if (!g) - { - this->Logs->Log("STARTUP", LOG_DEFAULT, "getgrnam(%s) failed (wrong group?): %s", SetGroup.c_str(), strerror(errno)); - this->QuickExit(0); - } - - ret = setgid(g->gr_gid); - - if (ret == -1) - { - this->Logs->Log("STARTUP", LOG_DEFAULT, "setgid() failed (wrong group?): %s", strerror(errno)); - this->QuickExit(0); - } - } - - if (!SetUser.empty()) - { - // setuid - struct passwd *u; - - errno = 0; - u = getpwnam(SetUser.c_str()); - - if (!u) - { - this->Logs->Log("STARTUP", LOG_DEFAULT, "getpwnam(%s) failed (wrong user?): %s", SetUser.c_str(), strerror(errno)); - this->QuickExit(0); - } - - int ret = setuid(u->pw_uid); - - if (ret == -1) - { - this->Logs->Log("STARTUP", LOG_DEFAULT, "setuid() failed (wrong user?): %s", strerror(errno)); - this->QuickExit(0); - } - } - - this->WritePID(Config->PID); -#endif + Logs->Log("STARTUP", LOG_DEFAULT, "Startup complete as '%s'[%s], %lu max open sockets", Config->ServerName.c_str(),Config->GetSID().c_str(), SocketEngine::GetMaxFds()); } void InspIRCd::UpdateTime() { -#ifdef _WIN32 +#if defined HAS_CLOCK_GETTIME + clock_gettime(CLOCK_REALTIME, &TIME); +#elif defined _WIN32 SYSTEMTIME st; GetSystemTime(&st); TIME.tv_sec = time(NULL); TIME.tv_nsec = st.wMilliseconds; #else - #ifdef HAS_CLOCK_GETTIME - clock_gettime(CLOCK_REALTIME, &TIME); - #else - struct timeval tv; - gettimeofday(&tv, NULL); - TIME.tv_sec = tv.tv_sec; - TIME.tv_nsec = tv.tv_usec * 1000; - #endif + struct timeval tv; + gettimeofday(&tv, NULL); + + TIME.tv_sec = tv.tv_sec; + TIME.tv_nsec = tv.tv_usec * 1000; #endif } void InspIRCd::Run() { -#ifdef INSPIRCD_ENABLE_TESTSUITE - /* See if we're supposed to be running the test suite rather than entering the mainloop */ - if (do_testsuite) - { - TestSuite* ts = new TestSuite; - delete ts; - return; - } -#endif - UpdateTime(); time_t OLDTIME = TIME.tv_sec; @@ -664,22 +641,27 @@ void InspIRCd::Run() } #endif - /* Allow a buffer of two seconds drift on this so that ntpdate etc dont harass admins */ - if (TIME.tv_sec < OLDTIME - 2) + if (Config->TimeSkipWarn) { - SNO->WriteToSnoMask('d', "\002EH?!\002 -- Time is flowing BACKWARDS in this dimension! Clock drifted backwards %lu secs.", (unsigned long)OLDTIME-TIME.tv_sec); - } - else if (TIME.tv_sec > OLDTIME + 2) - { - SNO->WriteToSnoMask('d', "\002EH?!\002 -- Time is jumping FORWARDS! Clock skipped %lu secs.", (unsigned long)TIME.tv_sec - OLDTIME); + time_t timediff = TIME.tv_sec - OLDTIME; + + if (timediff > Config->TimeSkipWarn) + SNO->WriteToSnoMask('a', "\002Performance warning!\002 Server clock jumped forwards by %lu seconds!", timediff); + + else if (timediff < -Config->TimeSkipWarn) + SNO->WriteToSnoMask('a', "\002Performance warning!\002 Server clock jumped backwards by %lu seconds!", labs(timediff)); } OLDTIME = TIME.tv_sec; if ((TIME.tv_sec % 3600) == 0) { - Users->GarbageCollect(); FOREACH_MOD(OnGarbageCollect, ()); + + // HACK: ELines are not expired properly at the moment but it can't be fixed as + // the 2.0 XLine system is a spaghetti nightmare. Instead we skip over expired + // ELines in XLineManager::CheckELines() and expire them here instead. + XLines->GetAll("E"); } Timers.TickTimers(TIME.tv_sec);