/* Services -- main source file. * * (C) 2003-2011 Anope Team * Contact us at team@anope.org * * Please read COPYING and README for further details. * * Based on the original code of Epona by Lara. * Based on the original code of Services by Andy Church. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program (see the file COPYING); if not, write to the * Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "services.h" #include "timers.h" #include "modules.h" // getrlimit. #ifndef _WIN32 # include # include #endif /******** Global variables! ********/ /* Command-line options: (note that configuration variables are in config.c) */ Anope::string services_dir; /* -dir dirname */ Anope::string services_bin; /* Binary as specified by the user */ Anope::string orig_cwd; /* Original current working directory */ int debug = 0; /* -debug */ bool readonly = false; /* -readonly */ bool nofork = false; /* -nofork */ bool nothird = false; /* -nothrid */ bool noexpire = false; /* -noexpire */ bool protocoldebug = false; /* -protocoldebug */ Anope::string binary_dir; /* Used to store base path for Anope */ #ifdef _WIN32 # include # define execve _execve #endif /* Set to 1 if we are to quit */ bool quitting = false; /* Set to 1 if we are to quit after saving databases */ bool shutting_down = false; /* Contains a message as to why services is terminating */ Anope::string quitmsg; /* Should we update the databases now? */ bool save_data = false; /* At what time were we started? */ time_t start_time; /* Parameters and environment */ char **my_av, **my_envp; time_t Anope::CurTime = time(NULL); /******** Local variables! ********/ /* Set to 1 after we've set everything up */ static bool started = false; /*************************************************************************/ class ExpireTimer : public Timer { public: ExpireTimer(time_t timeout, time_t now) : Timer(timeout, now, true) { } void Tick(time_t) { if (!readonly && !noexpire) expire_all(); } }; class UpdateTimer : public Timer { public: UpdateTimer(time_t timeout, time_t now) : Timer(timeout, now, true) { } void Tick(time_t) { if (!readonly) save_databases(); } }; ConnectionSocket *UplinkSock = NULL; UplinkSocket::UplinkSocket(bool ipv6) : ConnectionSocket(ipv6) { UplinkSock = this; } UplinkSocket::~UplinkSocket() { UplinkSock = NULL; } bool UplinkSocket::Read(const Anope::string &buf) { process(buf); return true; } /*************************************************************************/ /* Run expiration routines */ extern void expire_all() { if (noexpire || readonly) // Definitely *do not* want. return; FOREACH_MOD(I_OnPreDatabaseExpire, OnPreDatabaseExpire()); Log(LOG_DEBUG) << "Running expire routines"; expire_nicks(); expire_chans(); expire_exceptions(); FOREACH_MOD(I_OnDatabaseExpire, OnDatabaseExpire()); } /*************************************************************************/ void save_databases() { if (readonly) return; EventReturn MOD_RESULT; FOREACH_RESULT(I_OnSaveDatabase, OnSaveDatabase()); Log(LOG_DEBUG) << "Saving databases"; } /*************************************************************************/ /* Restarts services */ void do_restart_services() { if (!readonly) { expire_all(); save_databases(); } Log() << "Restarting"; FOREACH_MOD(I_OnPreRestart, OnPreRestart()); if (quitmsg.empty()) quitmsg = "Restarting"; /* Send a quit for all of our bots */ for (Anope::insensitive_map::const_iterator it = BotListByNick.begin(), it_end = BotListByNick.end(); it != it_end; ++it) { BotInfo *bi = it->second; /* Don't use quitmsg here, it may contain information you don't want people to see */ ircdproto->SendQuit(bi, "Restarting"); /* Erase bots from the user list so they don't get nuked later on */ UserListByNick.erase(bi->nick); if (!bi->GetUID().empty()) UserListByUID.erase(bi->GetUID()); } ircdproto->SendSquit(Config->ServerName, quitmsg); SocketEngine->Process(); delete UplinkSock; ModuleManager::UnloadAll(); chdir(binary_dir.c_str()); my_av[0] = const_cast(("./" + services_bin).c_str()); execve(services_bin.c_str(), my_av, my_envp); if (!readonly) { throw FatalException("Restart failed"); } FOREACH_MOD(I_OnRestart, OnRestart()); exit(1); } /*************************************************************************/ /* Terminates services */ static void services_shutdown() { FOREACH_MOD(I_OnPreShutdown, OnPreShutdown()); if (quitmsg.empty()) quitmsg = "Terminating, reason unknown"; Log() << quitmsg; if (started && UplinkSock) { /* Send a quit for all of our bots */ for (Anope::insensitive_map::const_iterator it = BotListByNick.begin(), it_end = BotListByNick.end(); it != it_end; ++it) { BotInfo *bi = it->second; /* Don't use quitmsg here, it may contain information you don't want people to see */ ircdproto->SendQuit(bi, "Shutting down"); /* Erase bots from the user list so they don't get nuked later on */ UserListByNick.erase(bi->nick); if (!bi->GetUID().empty()) UserListByUID.erase(bi->GetUID()); } ircdproto->SendSquit(Config->ServerName, quitmsg); for (Anope::insensitive_map::const_iterator it = UserListByNick.begin(); it != UserListByNick.end();) { User *u = it->second; ++it; delete u; } } SocketEngine->Process(); delete UplinkSock; FOREACH_MOD(I_OnShutdown, OnShutdown()); ModuleManager::UnloadAll(); /* just in case they weren't all removed at least run once */ ModuleRunTimeDirCleanUp(); } /*************************************************************************/ /* If we get a weird signal, come here. */ void sighandler(int signum) { if (quitmsg.empty()) #ifndef _WIN32 quitmsg = Anope::string("Services terminating via signal ") + strsignal(signum) + " (" + stringify(signum) + ")"; #else quitmsg = Anope::string("Services terminating via signal ") + stringify(signum); #endif bool fatal = false; if (started) { switch (signum) { #ifndef _WIN32 case SIGHUP: Log() << "Received SIGHUP: Saving databases & rehashing configuration"; expire_all(); save_databases(); try { ServerConfig *newconfig = new ServerConfig(); delete Config; Config = newconfig; FOREACH_MOD(I_OnReload, OnReload(true)); } catch (const ConfigException &ex) { Log() << "Error reloading configuration file: " << ex.GetReason(); } break; #endif case SIGINT: case SIGTERM: signal(signum, SIG_IGN); #ifndef _WIN32 signal(SIGHUP, SIG_IGN); #endif #ifndef _WIN32 Log() << "Received " << strsignal(signum) << " signal (" << signum << "), exiting."; #else Log() << "Received signal " << signum << ", exiting."; #endif if (Config->GlobalOnCycle) oper_global("", "%s", Config->GlobalOnCycleMessage.c_str()); expire_all(); save_databases(); default: fatal = true; break; } } FOREACH_MOD(I_OnSignal, OnSignal(signum, quitmsg)); if (fatal) throw FatalException(quitmsg); } /*************************************************************************/ /** The following comes from InspIRCd to get the full path of the Anope executable */ Anope::string GetFullProgDir(const Anope::string &argv0) { char buffer[PATH_MAX]; #ifdef _WIN32 /* Windows has specific API calls to get the EXE path that never fail. * For once, Windows has something of use, compared to the POSIX code * for this, this is positively neato. */ if (GetModuleFileName(NULL, buffer, PATH_MAX)) { Anope::string fullpath = buffer; Anope::string::size_type n = fullpath.rfind("\\"); services_bin = fullpath.substr(n + 1, fullpath.length()); return fullpath.substr(0, n); } #else // Get the current working directory if (getcwd(buffer, PATH_MAX)) { Anope::string remainder = argv0; services_bin = remainder; Anope::string::size_type n = services_bin.rfind("/"); Anope::string fullpath; if (services_bin[0] == '/') fullpath = services_bin.substr(0, n); else fullpath = Anope::string(buffer) + "/" + services_bin.substr(0, n); services_bin = services_bin.substr(n + 1, remainder.length()); return fullpath; } #endif return "/"; } /*************************************************************************/ static bool Connect() { /* Connect to the remote server */ int servernum = 1; for (std::list::iterator curr_uplink = Config->Uplinks.begin(), end_uplink = Config->Uplinks.end(); curr_uplink != end_uplink; ++curr_uplink, ++servernum) { uplink_server = *curr_uplink; EventReturn MOD_RESULT; FOREACH_RESULT(I_OnPreServerConnect, OnPreServerConnect(*curr_uplink, servernum)); if (MOD_RESULT != EVENT_CONTINUE) { if (MOD_RESULT == EVENT_STOP) continue; return true; } DNSRecord req = DNSManager::BlockingQuery(uplink_server->host, uplink_server->ipv6 ? DNS_QUERY_AAAA : DNS_QUERY_A); try { new UplinkSocket(uplink_server->ipv6); UplinkSock->Connect(req.result, uplink_server->port, Config->LocalHost); } catch (const SocketException &ex) { Log() << "Unable to connect to server" << servernum << " (" << uplink_server->host << ":" << uplink_server->port << "), " << ex.GetReason(); continue; } Log() << "Connected to server " << servernum << " (" << uplink_server->host << ":" << uplink_server->port << ")"; return true; } uplink_server = NULL; return false; } /*************************************************************************/ /* Main routine. (What does it look like? :-) ) */ int main(int ac, char **av, char **envp) { try { my_av = av; my_envp = envp; char cwd[PATH_MAX] = ""; #ifdef _WIN32 GetCurrentDirectory(PATH_MAX, cwd); #else getcwd(cwd, PATH_MAX); #endif orig_cwd = cwd; #ifndef _WIN32 /* If we're root, issue a warning now */ if (!getuid() && !getgid()) { fprintf(stderr, "WARNING: You are currently running Anope as the root superuser. Anope does not\n"); fprintf(stderr, " require root privileges to run, and it is discouraged that you run Anope\n"); fprintf(stderr, " as the root superuser.\n"); } #endif binary_dir = GetFullProgDir(av[0]); if (binary_dir[binary_dir.length() - 1] == '.') binary_dir = binary_dir.substr(0, binary_dir.length() - 2); #ifdef _WIN32 Anope::string::size_type n = binary_dir.rfind('\\'); services_dir = binary_dir.substr(0, n) + "\\data"; #else Anope::string::size_type n = binary_dir.rfind('/'); services_dir = binary_dir.substr(0, n) + "/data"; #endif /* Clean out the module runtime directory prior to running, just in case files were left behind during a previous run */ ModuleRunTimeDirCleanUp(); /* General initialization first */ Init(ac, av); /* If the first connect fails give up, don't sit endlessly trying to reconnect */ if (!Connect()) { Log() << "Can't connect to any servers"; return 0; } ircdproto->SendConnect(); FOREACH_MOD(I_OnServerConnect, OnServerConnect()); started = true; /* Set up timers */ time_t last_check = Anope::CurTime; ExpireTimer expireTimer(Config->ExpireTimeout, Anope::CurTime); UpdateTimer updateTimer(Config->UpdateTimeout, Anope::CurTime); /*** Main loop. ***/ while (!quitting) { while (!quitting && UplinkSock) { Log(LOG_DEBUG_2) << "Top of main loop"; if (!readonly && (save_data || shutting_down)) { if (!noexpire) expire_all(); if (shutting_down) ircdproto->SendGlobops(NULL, "Updating databases on shutdown, please wait."); save_databases(); save_data = false; } if (shutting_down) { quitting = true; break; } if (Anope::CurTime - last_check >= Config->TimeoutCheck) { TimerManager::TickTimers(Anope::CurTime); last_check = Anope::CurTime; } /* Free up any finished threads */ threadEngine.Process(); /* Process any modes that need to be (un)set */ if (Me != NULL && Me->IsSynced()) ModeManager::ProcessModes(); /* Process the socket engine */ SocketEngine->Process(); } if (quitting) /* Disconnect and exit */ services_shutdown(); else { FOREACH_MOD(I_OnServerDisconnect, OnServerDisconnect()); /* Clear all of our users, but not our bots */ for (Anope::insensitive_map::const_iterator it = UserListByNick.begin(); it != UserListByNick.end();) { User *u = it->second; ++it; if (u->server != Me) delete u; } Me->SetFlag(SERVER_SYNCING); for (unsigned i = Me->GetLinks().size(); i > 0; --i) if (!Me->GetLinks()[i - 1]->HasFlag(SERVER_JUPED)) delete Me->GetLinks()[i - 1]; unsigned j = 0; for (; j < (Config->MaxRetries ? Config->MaxRetries : j + 1); ++j) { Log() << "Disconnected from the server, retrying in " << Config->RetryWait << " seconds"; sleep(Config->RetryWait); if (Connect()) { ircdproto->SendConnect(); FOREACH_MOD(I_OnServerConnect, OnServerConnect()); break; } } if (Config->MaxRetries && j == Config->MaxRetries) { Log() << "Max connection retry limit exceeded"; quitting = true; } } } } catch (const FatalException &ex) { if (!ex.GetReason().empty()) Log(LOG_TERMINAL) << ex.GetReason(); if (started) services_shutdown(); return -1; } return 0; }