/* Services -- main source file. * * (C) 2003-2010 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. * * $Id$ * */ #include "services.h" #include "timers.h" #include "version.h" #include "modules.h" // getrlimit. #ifndef _WIN32 # include # include #endif /******** Global variables! ********/ /* Command-line options: (note that configuration variables are in config.c) */ std::string services_dir; /* -dir dirname */ std::string services_bin; /* Binary as specified by the user */ std::string orig_cwd; /* Original current working directory */ std::string log_filename = LOG_FILENAME; /* -log filename */ int debug = 0; /* -debug */ int readonly = 0; /* -readonly */ bool LogChan = false; /* -logchan */ int nofork = 0; /* -nofork */ int forceload = 0; /* -forceload */ int nothird = 0; /* -nothrid */ int noexpire = 0; /* -noexpire */ int protocoldebug = 0; /* -protocoldebug */ std::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 */ int quitting = 0; /* Set to 1 if we are to quit after saving databases */ int shutting_down = 0; /* Contains a message as to why services is terminating */ const char *quitmsg = NULL; /* Should we update the databases now? */ int save_data = 0; /* At what time were we started? */ time_t start_time; /* Parameters and environment */ char **my_av, **my_envp; /* Moved here from version.h */ const char version_number[] = VERSION_STRING; const char version_number_dotted[] = VERSION_STRING_DOTTED; const char version_build[] = "build #" BUILD ", compiled " __DATE__ " " __TIME__; /* the space is needed cause if you build with nothing it will complain */ const char version_flags[] = " " VER_OS; /******** Local variables! ********/ /* Set to 1 after we've set everything up */ static int started = 0; /*************************************************************************/ 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(); } }; Socket *UplinkSock = NULL; class UplinkSocket : public Socket { public: UplinkSocket(const std::string &nTargetHost, int nPort, const std::string &nBindHost = "", bool nIPv6 = false) : Socket(nTargetHost, nPort, nBindHost, nIPv6) { UplinkSock = this; } ~UplinkSocket() { UplinkSock = NULL; } bool Read(const std::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()); Alog(LOG_DEBUG) << "Running expire routines"; expire_nicks(); expire_chans(); expire_requests(); expire_akills(); if (ircd->sgline) { expire_sglines(); } if (ircd->sqline) { expire_sqlines(); } if (ircd->szline) { expire_szlines(); } expire_exceptions(); FOREACH_MOD(I_OnDatabaseExpire, OnDatabaseExpire()); } /*************************************************************************/ void save_databases() { if (readonly) return; EventReturn MOD_RESULT; FOREACH_RESULT(I_OnSaveDatabase, OnSaveDatabase()); Alog(LOG_DEBUG) << "Saving FFF databases"; } /*************************************************************************/ /* Restarts services */ void do_restart_services() { if (!readonly) { expire_all(); save_databases(); } Alog() << "Restarting"; FOREACH_MOD(I_OnPreRestart, OnPreRestart()); if (!quitmsg) quitmsg = "Restarting"; ircdproto->SendSquit(Config.ServerName, quitmsg); /* Process to send the last bits of information before disconnecting */ socketEngine.Process(); delete UplinkSock; close_log(); /* First don't unload protocol module, then do so */ modules_unload_all(false); modules_unload_all(true); chdir(binary_dir.c_str()); execve(services_bin.c_str(), my_av, my_envp); if (!readonly) { open_log(); log_perror("Restart failed"); close_log(); } FOREACH_MOD(I_OnRestart, OnRestart()); exit(1); } /*************************************************************************/ /* Terminates services */ static void services_shutdown() { User *u, *next; FOREACH_MOD(I_OnPreShutdown, OnPreShutdown()); if (!quitmsg) quitmsg = "Terminating, reason unknown"; Alog() << quitmsg; if (started && UplinkSock) { ircdproto->SendSquit(Config.ServerName, quitmsg); if (uplink) delete [] uplink; u = firstuser(); while (u) { next = nextuser(); delete u; u = next; } } /* Process to send the last bits of information before disconnecting */ socketEngine.Process(); delete UplinkSock; FOREACH_MOD(I_OnShutdown, OnShutdown()); /* First don't unload protocol module, then do so */ modules_unload_all(false); modules_unload_all(true); /* 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) { /* We set the quit message to something default, just to be sure it is * always set when we need it. It seems some signals slip through to the * QUIT code without having a valid quitmsg. -GD */ if (!quitmsg) quitmsg = "Services terminating via a signal."; if (started) { #ifndef _WIN32 if (signum == SIGHUP) { Alog() << "Received SIGHUP: Saving Databases & Rehash Configuration"; expire_all(); save_databases(); if (!read_config(1)) { quitmsg = "Error Reading Configuration File (Received SIGHUP)"; quitting = 1; } FOREACH_MOD(I_OnReload, OnReload(true)); return; } else #endif if (signum == SIGTERM) { signal(SIGTERM, SIG_IGN); #ifndef _WIN32 signal(SIGHUP, SIG_IGN); #endif Alog() << "Received SIGTERM, exiting."; expire_all(); save_databases(); quitmsg = "Shutting down on SIGTERM"; services_shutdown(); exit(0); } else if (signum == SIGINT) { if (nofork) { signal(SIGINT, SIG_IGN); Alog() << "Received SIGINT, exiting."; expire_all(); save_databases(); quitmsg = "Shutting down on SIGINT"; services_shutdown(); exit(0); } } } /* Should we send the signum here as well? -GD */ FOREACH_MOD(I_OnSignal, OnSignal(quitmsg)); if (started) { services_shutdown(); exit(0); } else { if (isatty(2)) { fprintf(stderr, "%s\n", quitmsg); } else { Alog() << quitmsg; } exit(1); } } /*************************************************************************/ /** The following comes from InspIRCd to get the full path of the Anope executable */ std::string GetFullProgDir(char *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)) { std::string fullpath = buffer; std::string::size_type n = fullpath.rfind("\\" SERVICES_BIN); services_bin = fullpath.substr(n + 1, fullpath.size()); return std::string(fullpath, 0, n); } #else // Get the current working directory if (getcwd(buffer, PATH_MAX)) { std::string remainder = argv0; /* Does argv[0] start with /? If so, it's a full path, use it */ if (remainder[0] == '/') { std::string::size_type n = remainder.rfind("/" SERVICES_BIN); services_bin = remainder.substr(n + 1, remainder.size()); return std::string(remainder, 0, n); } services_bin = remainder; if (services_bin.substr(0, 2) == "./") services_bin = services_bin.substr(2); std::string fullpath = std::string(buffer) + "/" + remainder; std::string::size_type n = fullpath.rfind("/" SERVICES_BIN); return std::string(fullpath, 0, n); } #endif return "/"; } /*************************************************************************/ static bool Connect() { /* Connect to the remote server */ std::list::iterator curr_uplink = Config.Uplinks.begin(), end_uplink = Config.Uplinks.end(); int servernum = 1; for (; curr_uplink != end_uplink; ++curr_uplink, ++servernum) { uplink_server = *curr_uplink; try { new UplinkSocket(uplink_server->host, uplink_server->port, Config.LocalHost ? Config.LocalHost : "", uplink_server->ipv6); } catch (SocketException& ex) { Alog() << "Unable to connect to server" << servernum << " (" << uplink_server->host << ":" << uplink_server->port << "), " << ex.GetReason(); continue; } Alog() << "Connected to Server " << servernum << " (" << uplink_server->host << ":" << uplink_server->port << ")"; return true; } return false; } /*************************************************************************/ /* Main routine. (What does it look like? :-) ) */ int main(int ac, char **av, char **envp) { int i; 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() == 0) && (getgid() == 0)) { 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.size() - 1] == '.') binary_dir = binary_dir.substr(0, binary_dir.size() - 2); #ifdef _WIN32 std::string::size_type n = binary_dir.rfind("\\"); services_dir = binary_dir.substr(0, n) + "\\data"; #else std::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 */ if ((i = init_primary(ac, av)) != 0) return i; Alog(LOG_TERMINAL) << "Anope " << version_number << ", " << version_build; #ifdef _WIN32 Alog(LOG_TERMINAL) << "Using configuration file " << services_dir << "\\" << services_conf; #else Alog(LOG_TERMINAL) << "Using configuration file " << services_dir << "/" << services_conf; #endif /* Initialization stuff. */ if ((i = init_secondary(ac, av)) != 0) return i; FOREACH_MOD(I_OnPreServerConnect, OnPreServerConnect()); /* If the first connect fails give up, don't sit endlessly trying to reconnect */ if (!Connect()) fatal_perror("Can't connect to any servers"); ircdproto->SendConnect(); FOREACH_MOD(I_OnServerConnect, OnServerConnect()); started = 1; #ifndef _WIN32 if (Config.DumpCore) { rlimit rl; if (getrlimit(RLIMIT_CORE, &rl) == -1) { Alog() << "Failed to getrlimit()!"; } else { rl.rlim_cur = rl.rlim_max; if (setrlimit(RLIMIT_CORE, &rl) == -1) { Alog() << "setrlimit() failed, cannot increase coredump size"; } } } #endif /* Set up timers */ time_t last_check = time(NULL); ExpireTimer expireTimer(Config.ExpireTimeout, last_check); UpdateTimer updateTimer(Config.UpdateTimeout, last_check); /*** Main loop. ***/ while (!quitting) { while (!quitting && UplinkSock) { time_t t = time(NULL); Alog(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 = 0; } if (shutting_down) { quitting = 1; break; } if (t - last_check >= Config.TimeoutCheck) { TimerManager::TickTimers(t); last_check = t; } /* Process any modes that need to be (un)set */ 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 */ User *u = firstuser(); while (u) { User *unext = nextuser(); delete u; u = unext; } unsigned j = 0; for (; j < (Config.MaxRetries ? Config.MaxRetries : j + 1); ++j) { Alog() << "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) { Alog() << "Max connection retry limit exceeded"; quitting = 1; } } } return 0; }