diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:32:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:32:39 +0000 |
commit | 56ae875861ab260b80a030f50c4aff9f9dc8fff0 (patch) | |
tree | 531412110fc901a5918c7f7442202804a83cada9 /icinga-app/icinga.cpp | |
parent | Initial commit. (diff) | |
download | icinga2-56ae875861ab260b80a030f50c4aff9f9dc8fff0.tar.xz icinga2-56ae875861ab260b80a030f50c4aff9f9dc8fff0.zip |
Adding upstream version 2.14.2.upstream/2.14.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | icinga-app/icinga.cpp | 949 |
1 files changed, 949 insertions, 0 deletions
diff --git a/icinga-app/icinga.cpp b/icinga-app/icinga.cpp new file mode 100644 index 0000000..1811c8e --- /dev/null +++ b/icinga-app/icinga.cpp @@ -0,0 +1,949 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "cli/clicommand.hpp" +#include "config/configcompilercontext.hpp" +#include "config/configcompiler.hpp" +#include "config/configitembuilder.hpp" +#include "config/expression.hpp" +#include "base/application.hpp" +#include "base/configuration.hpp" +#include "base/logger.hpp" +#include "base/timer.hpp" +#include "base/utility.hpp" +#include "base/loader.hpp" +#include "base/exception.hpp" +#include "base/convert.hpp" +#include "base/scriptglobal.hpp" +#include "base/context.hpp" +#include "base/console.hpp" +#include "base/process.hpp" +#include "config.h" +#include <boost/program_options.hpp> +#include <boost/algorithm/string/split.hpp> +#include <thread> + +#ifndef _WIN32 +# include <sys/types.h> +# include <pwd.h> +# include <grp.h> +#else +# include <windows.h> +# include <Lmcons.h> +# include <Shellapi.h> +# include <tchar.h> +#endif /* _WIN32 */ + +using namespace icinga; +namespace po = boost::program_options; + +#ifdef _WIN32 +static SERVICE_STATUS l_SvcStatus; +static SERVICE_STATUS_HANDLE l_SvcStatusHandle; +static HANDLE l_Job; +#endif /* _WIN32 */ + +static std::vector<String> GetLogLevelCompletionSuggestions(const String& arg) +{ + std::vector<String> result; + + String debugLevel = "debug"; + if (debugLevel.Find(arg) == 0) + result.push_back(debugLevel); + + String noticeLevel = "notice"; + if (noticeLevel.Find(arg) == 0) + result.push_back(noticeLevel); + + String informationLevel = "information"; + if (informationLevel.Find(arg) == 0) + result.push_back(informationLevel); + + String warningLevel = "warning"; + if (warningLevel.Find(arg) == 0) + result.push_back(warningLevel); + + String criticalLevel = "critical"; + if (criticalLevel.Find(arg) == 0) + result.push_back(criticalLevel); + + return result; +} + +static std::vector<String> GlobalArgumentCompletion(const String& argument, const String& word) +{ + if (argument == "include") + return GetBashCompletionSuggestions("directory", word); + else if (argument == "log-level") + return GetLogLevelCompletionSuggestions(word); + else + return std::vector<String>(); +} + +static void HandleLegacyDefines() +{ +#ifdef _WIN32 + String dataPrefix = Utility::GetIcingaDataPath(); +#endif /* _WIN32 */ + + Value localStateDir = Configuration::LocalStateDir; + + if (!localStateDir.IsEmpty()) { + Log(LogWarning, "icinga-app") + << "Please do not set the deprecated 'LocalStateDir' constant," + << " use the 'DataDir', 'LogDir', 'CacheDir' and 'SpoolDir' constants instead!" + << " For compatibility reasons, these are now set based on the 'LocalStateDir' constant."; + +#ifdef _WIN32 + Configuration::DataDir = localStateDir + "\\lib\\icinga2"; + Configuration::LogDir = localStateDir + "\\log\\icinga2"; + Configuration::CacheDir = localStateDir + "\\cache\\icinga2"; + Configuration::SpoolDir = localStateDir + "\\spool\\icinga2"; + } else { + Configuration::LocalStateDir = dataPrefix + "\\var"; +#else /* _WIN32 */ + Configuration::DataDir = localStateDir + "/lib/icinga2"; + Configuration::LogDir = localStateDir + "/log/icinga2"; + Configuration::CacheDir = localStateDir + "/cache/icinga2"; + Configuration::SpoolDir = localStateDir + "/spool/icinga2"; + } else { + Configuration::LocalStateDir = ICINGA_LOCALSTATEDIR; +#endif /* _WIN32 */ + } + + Value sysconfDir = Configuration::SysconfDir; + if (!sysconfDir.IsEmpty()) { + Log(LogWarning, "icinga-app") + << "Please do not set the deprecated 'Sysconfdir' constant, use the 'ConfigDir' constant instead! For compatibility reasons, their value is set based on the 'SysconfDir' constant."; + +#ifdef _WIN32 + Configuration::ConfigDir = sysconfDir + "\\icinga2"; + } else { + Configuration::SysconfDir = dataPrefix + "\\etc"; +#else /* _WIN32 */ + Configuration::ConfigDir = sysconfDir + "/icinga2"; + } else { + Configuration::SysconfDir = ICINGA_SYSCONFDIR; +#endif /* _WIN32 */ + } + + Value runDir = Configuration::RunDir; + if (!runDir.IsEmpty()) { + Log(LogWarning, "icinga-app") + << "Please do not set the deprecated 'RunDir' constant, use the 'InitRunDir' constant instead! For compatibility reasons, their value is set based on the 'RunDir' constant."; + +#ifdef _WIN32 + Configuration::InitRunDir = runDir + "\\icinga2"; + } else { + Configuration::RunDir = dataPrefix + "\\var\\run"; +#else /* _WIN32 */ + Configuration::InitRunDir = runDir + "/icinga2"; + } else { + Configuration::RunDir = ICINGA_RUNDIR; +#endif /* _WIN32 */ + } +} + +static int Main() +{ + int argc = Application::GetArgC(); + char **argv = Application::GetArgV(); + + bool autocomplete = false; + int autoindex = 0; + + if (argc >= 4 && strcmp(argv[1], "--autocomplete") == 0) { + autocomplete = true; + + try { + autoindex = Convert::ToLong(argv[2]); + } catch (const std::invalid_argument&) { + Log(LogCritical, "icinga-app") + << "Invalid index for --autocomplete: " << argv[2]; + return EXIT_FAILURE; + } + + argc -= 3; + argv += 3; + } + + /* Set thread title. */ + Utility::SetThreadName("Main Thread", false); + + /* Install exception handlers to make debugging easier. */ + Application::InstallExceptionHandlers(); + +#ifdef _WIN32 + bool builtinPaths = true; + + /* Programm install location, C:/Program Files/Icinga2 */ + String binaryPrefix = Utility::GetIcingaInstallPath(); + /* Returns the datapath for daemons, %PROGRAMDATA%/icinga2 */ + String dataPrefix = Utility::GetIcingaDataPath(); + + if (!binaryPrefix.IsEmpty() && !dataPrefix.IsEmpty()) { + Configuration::ProgramData = dataPrefix; + + Configuration::ConfigDir = dataPrefix + "\\etc\\icinga2"; + + Configuration::DataDir = dataPrefix + "\\var\\lib\\icinga2"; + Configuration::LogDir = dataPrefix + "\\var\\log\\icinga2"; + Configuration::CacheDir = dataPrefix + "\\var\\cache\\icinga2"; + Configuration::SpoolDir = dataPrefix + "\\var\\spool\\icinga2"; + + Configuration::PrefixDir = binaryPrefix; + + /* Internal constants. */ + Configuration::PkgDataDir = binaryPrefix + "\\share\\icinga2"; + Configuration::IncludeConfDir = binaryPrefix + "\\share\\icinga2\\include"; + + Configuration::InitRunDir = dataPrefix + "\\var\\run\\icinga2"; + } else { + Log(LogWarning, "icinga-app", "Registry key could not be read. Falling back to built-in paths."); + +#endif /* _WIN32 */ + Configuration::ConfigDir = ICINGA_CONFIGDIR; + + Configuration::DataDir = ICINGA_DATADIR; + Configuration::LogDir = ICINGA_LOGDIR; + Configuration::CacheDir = ICINGA_CACHEDIR; + Configuration::SpoolDir = ICINGA_SPOOLDIR; + + Configuration::PrefixDir = ICINGA_PREFIX; + + /* Internal constants. */ + Configuration::PkgDataDir = ICINGA_PKGDATADIR; + Configuration::IncludeConfDir = ICINGA_INCLUDECONFDIR; + + Configuration::InitRunDir = ICINGA_INITRUNDIR; + +#ifdef _WIN32 + } +#endif /* _WIN32 */ + + Configuration::ZonesDir = Configuration::ConfigDir + "/zones.d"; + + String icingaUser = Utility::GetFromEnvironment("ICINGA2_USER"); + if (icingaUser.IsEmpty()) + icingaUser = ICINGA_USER; + + String icingaGroup = Utility::GetFromEnvironment("ICINGA2_GROUP"); + if (icingaGroup.IsEmpty()) + icingaGroup = ICINGA_GROUP; + + Configuration::RunAsUser = icingaUser; + Configuration::RunAsGroup = icingaGroup; + + if (!autocomplete) { +#ifdef RLIMIT_NOFILE + String rLimitFiles = Utility::GetFromEnvironment("ICINGA2_RLIMIT_FILES"); + if (rLimitFiles.IsEmpty()) + Configuration::RLimitFiles = Application::GetDefaultRLimitFiles(); + else { + try { + Configuration::RLimitFiles = Convert::ToLong(rLimitFiles); + } catch (const std::invalid_argument& ex) { + std::cout + << "Error setting \"ICINGA2_RLIMIT_FILES\": " << ex.what() << '\n'; + return EXIT_FAILURE; + } + } +#endif /* RLIMIT_NOFILE */ + +#ifdef RLIMIT_NPROC + String rLimitProcesses = Utility::GetFromEnvironment("ICINGA2_RLIMIT_PROCESSES"); + if (rLimitProcesses.IsEmpty()) + Configuration::RLimitProcesses = Application::GetDefaultRLimitProcesses(); + else { + try { + Configuration::RLimitProcesses = Convert::ToLong(rLimitProcesses); + } catch (const std::invalid_argument& ex) { + std::cout + << "Error setting \"ICINGA2_RLIMIT_PROCESSES\": " << ex.what() << '\n'; + return EXIT_FAILURE; + } + } +#endif /* RLIMIT_NPROC */ + +#ifdef RLIMIT_STACK + String rLimitStack = Utility::GetFromEnvironment("ICINGA2_RLIMIT_STACK"); + if (rLimitStack.IsEmpty()) + Configuration::RLimitStack = Application::GetDefaultRLimitStack(); + else { + try { + Configuration::RLimitStack = Convert::ToLong(rLimitStack); + } catch (const std::invalid_argument& ex) { + std::cout + << "Error setting \"ICINGA2_RLIMIT_STACK\": " << ex.what() << '\n'; + return EXIT_FAILURE; + } + } +#endif /* RLIMIT_STACK */ + } + + if (!autocomplete) + Application::SetResourceLimits(); + + LogSeverity logLevel = Logger::GetConsoleLogSeverity(); + Logger::SetConsoleLogSeverity(LogWarning); + + po::options_description visibleDesc("Global options"); + + visibleDesc.add_options() + ("help,h", "show this help message") + ("version,V", "show version information") +#ifndef _WIN32 + ("color", "use VT100 color codes even when stdout is not a terminal") +#endif /* _WIN32 */ + ("define,D", po::value<std::vector<std::string> >(), "define a constant") + ("include,I", po::value<std::vector<std::string> >(), "add include search directory") + ("log-level,x", po::value<std::string>(), "specify the log level for the console log.\n" + "The valid value is either debug, notice, information (default), warning, or critical") + ("script-debugger,X", "whether to enable the script debugger"); + + po::options_description hiddenDesc("Hidden options"); + + hiddenDesc.add_options() + ("no-stack-rlimit", "used internally, do not specify manually") + ("arg", po::value<std::vector<std::string> >(), "positional argument"); + + po::positional_options_description positionalDesc; + positionalDesc.add("arg", -1); + + String cmdname; + CLICommand::Ptr command; + po::variables_map vm; + + try { + CLICommand::ParseCommand(argc, argv, visibleDesc, hiddenDesc, positionalDesc, + vm, cmdname, command, autocomplete); + } catch (const std::exception& ex) { + Log(LogCritical, "icinga-app") + << "Error while parsing command-line options: " << ex.what(); + return EXIT_FAILURE; + } + +#ifdef _WIN32 + char username[UNLEN + 1]; + DWORD usernameLen = UNLEN + 1; + GetUserName(username, &usernameLen); + + std::ifstream userFile; + + /* The implicit string assignment is needed for Windows builds. */ + String configDir = Configuration::ConfigDir; + userFile.open(configDir + "/user"); + + if (userFile && command && !Application::IsProcessElevated()) { + std::string userLine; + if (std::getline(userFile, userLine)) { + userFile.close(); + + std::vector<std::string> strs; + boost::split(strs, userLine, boost::is_any_of("\\")); + + if (username != strs[1] && command->GetImpersonationLevel() == ImpersonationLevel::ImpersonateIcinga + || command->GetImpersonationLevel() == ImpersonationLevel::ImpersonateRoot) { + TCHAR szPath[MAX_PATH]; + + if (GetModuleFileName(nullptr, szPath, ARRAYSIZE(szPath))) { + SHELLEXECUTEINFO sei = { sizeof(sei) }; + sei.lpVerb = _T("runas"); + sei.lpFile = "cmd.exe"; + sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NOASYNC | SEE_MASK_FLAG_NO_UI; + sei.nShow = SW_SHOW; + + std::stringstream parameters; + + parameters << "/C " << "\"" << szPath << "\"" << " "; + + for (int i = 1; i < argc; i++) { + if (i != 1) + parameters << " "; + parameters << argv[i]; + } + + parameters << " & SET exitcode=%errorlevel%"; + parameters << " & pause"; + parameters << " & EXIT /B %exitcode%"; + + std::string str = parameters.str(); + LPCSTR cstr = str.c_str(); + + sei.lpParameters = cstr; + + if (!ShellExecuteEx(&sei)) { + DWORD dwError = GetLastError(); + if (dwError == ERROR_CANCELLED) + Application::Exit(0); + } else { + WaitForSingleObject(sei.hProcess, INFINITE); + + DWORD exitCode; + GetExitCodeProcess(sei.hProcess, &exitCode); + + CloseHandle(sei.hProcess); + + Application::Exit(exitCode); + } + } + } + } else { + userFile.close(); + } + } +#endif /* _WIN32 */ + +#ifndef _WIN32 + if (vm.count("color")) { + Console::SetType(std::cout, Console_VT100); + Console::SetType(std::cerr, Console_VT100); + } +#endif /* _WIN32 */ + + if (vm.count("define")) { + for (const String& define : vm["define"].as<std::vector<std::string> >()) { + String key, value; + size_t pos = define.FindFirstOf('='); + if (pos != String::NPos) { + key = define.SubStr(0, pos); + value = define.SubStr(pos + 1); + } else { + key = define; + value = "1"; + } + + std::vector<String> keyTokens = key.Split("."); + + std::unique_ptr<Expression> expr; + std::unique_ptr<VariableExpression> varExpr{new VariableExpression(keyTokens[0], {}, DebugInfo())}; + expr = std::move(varExpr); + + for (size_t i = 1; i < keyTokens.size(); i++) { + std::unique_ptr<IndexerExpression> indexerExpr{new IndexerExpression(std::move(expr), MakeLiteral(keyTokens[i]))}; + indexerExpr->SetOverrideFrozen(); + expr = std::move(indexerExpr); + } + + std::unique_ptr<SetExpression> setExpr{new SetExpression(std::move(expr), OpSetLiteral, MakeLiteral(value))}; + setExpr->SetOverrideFrozen(); + + try { + ScriptFrame frame(true); + setExpr->Evaluate(frame); + } catch (const ScriptError& e) { + Log(LogCritical, "icinga-app") << "cannot set '" << key << "': " << e.what(); + return EXIT_FAILURE; + } + } + } + + Configuration::SetReadOnly(true); + + if (!Configuration::ConcurrencyWasModified) { + Configuration::Concurrency = std::thread::hardware_concurrency(); + } + + Application::GetTP().Restart(); + + /* Ensure that all defined constants work in the way we expect them. */ + HandleLegacyDefines(); + + if (vm.count("script-debugger")) + Application::SetScriptDebuggerEnabled(true); + + Configuration::StatePath = Configuration::DataDir + "/icinga2.state"; + Configuration::ModAttrPath = Configuration::DataDir + "/modified-attributes.conf"; + Configuration::ObjectsPath = Configuration::CacheDir + "/icinga2.debug"; + Configuration::VarsPath = Configuration::CacheDir + "/icinga2.vars"; + Configuration::PidPath = Configuration::InitRunDir + "/icinga2.pid"; + + ConfigCompiler::AddIncludeSearchDir(Configuration::IncludeConfDir); + + if (!autocomplete && vm.count("include")) { + for (const String& includePath : vm["include"].as<std::vector<std::string> >()) { + ConfigCompiler::AddIncludeSearchDir(includePath); + } + } + + if (!autocomplete) { + Logger::SetConsoleLogSeverity(logLevel); + + if (vm.count("log-level")) { + String severity = vm["log-level"].as<std::string>(); + + LogSeverity logLevel = LogInformation; + try { + logLevel = Logger::StringToSeverity(severity); + } catch (std::exception&) { + /* Inform user and exit */ + Log(LogCritical, "icinga-app", "Invalid log level set. Default is 'information'."); + return EXIT_FAILURE; + } + + Logger::SetConsoleLogSeverity(logLevel); + } + + if (!command || vm.count("help") || vm.count("version")) { + String appName; + + try { + appName = Utility::BaseName(Application::GetArgV()[0]); + } catch (const std::bad_alloc&) { + Log(LogCritical, "icinga-app", "Allocation failed."); + return EXIT_FAILURE; + } + + if (appName.GetLength() > 3 && appName.SubStr(0, 3) == "lt-") + appName = appName.SubStr(3, appName.GetLength() - 3); + + std::cout << appName << " " << "- The Icinga 2 network monitoring daemon (version: " + << ConsoleColorTag(vm.count("version") ? Console_ForegroundRed : Console_Normal) + << Application::GetAppVersion() +#ifdef I2_DEBUG + << "; debug" +#endif /* I2_DEBUG */ + << ConsoleColorTag(Console_Normal) + << ")" << std::endl << std::endl; + + if ((!command || vm.count("help")) && !vm.count("version")) { + std::cout << "Usage:" << std::endl + << " " << Utility::BaseName(argv[0]) << " "; + + if (cmdname.IsEmpty()) + std::cout << "<command>"; + else + std::cout << cmdname; + + std::cout << " [<arguments>]" << std::endl; + + if (command) { + std::cout << std::endl + << command->GetDescription() << std::endl; + } + } + + if (vm.count("version")) { + std::cout << "Copyright (c) 2012-" << Utility::FormatDateTime("%Y", Utility::GetTime()) + << " Icinga GmbH (https://icinga.com/)" << std::endl + << "License GPLv2+: GNU GPL version 2 or later <https://gnu.org/licenses/gpl2.html>" << std::endl + << "This is free software: you are free to change and redistribute it." << std::endl + << "There is NO WARRANTY, to the extent permitted by law."; + } + + std::cout << std::endl; + + if (vm.count("version")) { + std::cout << std::endl; + + Application::DisplayInfoMessage(std::cout, true); + + return EXIT_SUCCESS; + } + } + + if (!command || vm.count("help")) { + if (!command) + CLICommand::ShowCommands(argc, argv, nullptr); + + std::cout << visibleDesc << std::endl + << "Report bugs at <https://github.com/Icinga/icinga2>" << std::endl + << "Get support: <https://icinga.com/support/>" << std::endl + << "Documentation: <https://icinga.com/docs/>" << std::endl + << "Icinga home page: <https://icinga.com/>" << std::endl; + return EXIT_SUCCESS; + } + } + + int rc = 1; + + if (autocomplete) { + CLICommand::ShowCommands(argc, argv, &visibleDesc, &hiddenDesc, + &GlobalArgumentCompletion, true, autoindex); + rc = 0; + } else if (command) { + Logger::DisableTimestamp(); +#ifndef _WIN32 + if (command->GetImpersonationLevel() == ImpersonateRoot) { + if (getuid() != 0) { + Log(LogCritical, "cli", "This command must be run as root."); + return 0; + } + } else if (command && command->GetImpersonationLevel() == ImpersonateIcinga) { + String group = Configuration::RunAsGroup; + String user = Configuration::RunAsUser; + + errno = 0; + struct group *gr = getgrnam(group.CStr()); + + if (!gr) { + if (errno == 0) { + Log(LogCritical, "cli") + << "Invalid group specified: " << group; + return EXIT_FAILURE; + } else { + Log(LogCritical, "cli") + << "getgrnam() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + return EXIT_FAILURE; + } + } + + if (getgid() != gr->gr_gid) { + if (!vm.count("reload-internal") && setgroups(0, nullptr) < 0) { + Log(LogCritical, "cli") + << "setgroups() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + Log(LogCritical, "cli") + << "Please re-run this command as a privileged user or using the \"" << user << "\" account."; + return EXIT_FAILURE; + } + + if (setgid(gr->gr_gid) < 0) { + Log(LogCritical, "cli") + << "setgid() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + return EXIT_FAILURE; + } + } + + errno = 0; + struct passwd *pw = getpwnam(user.CStr()); + + if (!pw) { + if (errno == 0) { + Log(LogCritical, "cli") + << "Invalid user specified: " << user; + return EXIT_FAILURE; + } else { + Log(LogCritical, "cli") + << "getpwnam() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + return EXIT_FAILURE; + } + } + + // also activate the additional groups the configured user is member of + if (getuid() != pw->pw_uid) { + if (!vm.count("reload-internal") && initgroups(user.CStr(), pw->pw_gid) < 0) { + Log(LogCritical, "cli") + << "initgroups() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + Log(LogCritical, "cli") + << "Please re-run this command as a privileged user or using the \"" << user << "\" account."; + return EXIT_FAILURE; + } + + if (setuid(pw->pw_uid) < 0) { + Log(LogCritical, "cli") + << "setuid() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + Log(LogCritical, "cli") + << "Please re-run this command as a privileged user or using the \"" << user << "\" account."; + return EXIT_FAILURE; + } + } + } +#endif /* _WIN32 */ + + std::vector<std::string> args; + if (vm.count("arg")) + args = vm["arg"].as<std::vector<std::string> >(); + + if (static_cast<int>(args.size()) < command->GetMinArguments()) { + Log(LogCritical, "cli") + << "Too few arguments. Command needs at least " << command->GetMinArguments() + << " argument" << (command->GetMinArguments() != 1 ? "s" : "") << "."; + return EXIT_FAILURE; + } + + if (command->GetMaxArguments() >= 0 && static_cast<int>(args.size()) > command->GetMaxArguments()) { + Log(LogCritical, "cli") + << "Too many arguments. At most " << command->GetMaxArguments() + << " argument" << (command->GetMaxArguments() != 1 ? "s" : "") << " may be specified."; + return EXIT_FAILURE; + } + + rc = command->Run(vm, args); + } + + return rc; +} + +#ifdef _WIN32 +static int SetupService(bool install, int argc, char **argv) +{ + SC_HANDLE schSCManager = OpenSCManager(nullptr, nullptr, SC_MANAGER_ALL_ACCESS); + + if (!schSCManager) { + printf("OpenSCManager failed (%d)\n", GetLastError()); + return 1; + } + + TCHAR szPath[MAX_PATH]; + + if (!GetModuleFileName(nullptr, szPath, MAX_PATH)) { + printf("Cannot install service (%d)\n", GetLastError()); + return 1; + } + + String szArgs; + szArgs = Utility::EscapeShellArg(szPath) + " --scm"; + + std::string scmUser = "NT AUTHORITY\\NetworkService"; + std::ifstream initf(Utility::GetIcingaDataPath() + "\\etc\\icinga2\\user"); + if (initf.good()) { + std::getline(initf, scmUser); + } + initf.close(); + + for (int i = 0; i < argc; i++) { + if (!strcmp(argv[i], "--scm-user") && i + 1 < argc) { + scmUser = argv[i + 1]; + i++; + } else + szArgs += " " + Utility::EscapeShellArg(argv[i]); + } + + SC_HANDLE schService = OpenService(schSCManager, "icinga2", SERVICE_ALL_ACCESS); + + if (schService) { + SERVICE_STATUS status; + ControlService(schService, SERVICE_CONTROL_STOP, &status); + + double start = Utility::GetTime(); + while (status.dwCurrentState != SERVICE_STOPPED) { + double end = Utility::GetTime(); + + if (end - start > 30) { + printf("Could not stop the service.\n"); + break; + } + + Utility::Sleep(5); + + if (!QueryServiceStatus(schService, &status)) { + printf("QueryServiceStatus failed (%d)\n", GetLastError()); + return 1; + } + } + } else if (install) { + schService = CreateService( + schSCManager, + "icinga2", + "Icinga 2", + SERVICE_ALL_ACCESS, + SERVICE_WIN32_OWN_PROCESS, + SERVICE_DEMAND_START, + SERVICE_ERROR_NORMAL, + szArgs.CStr(), + nullptr, + nullptr, + nullptr, + scmUser.c_str(), + nullptr); + + if (!schService) { + printf("CreateService failed (%d)\n", GetLastError()); + CloseServiceHandle(schSCManager); + return 1; + } + } else { + printf("Service isn't installed.\n"); + CloseServiceHandle(schSCManager); + return 0; + } + + if (!install) { + if (!DeleteService(schService)) { + printf("DeleteService failed (%d)\n", GetLastError()); + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + return 1; + } + + printf("Service uninstalled successfully\n"); + } else { + if (!ChangeServiceConfig(schService, SERVICE_NO_CHANGE, SERVICE_AUTO_START, + SERVICE_ERROR_NORMAL, szArgs.CStr(), nullptr, nullptr, nullptr, scmUser.c_str(), nullptr, nullptr)) { + printf("ChangeServiceConfig failed (%d)\n", GetLastError()); + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + return 1; + } + + SERVICE_DESCRIPTION sdDescription = { "The Icinga 2 monitoring application" }; + if(!ChangeServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, &sdDescription)) { + printf("ChangeServiceConfig2 failed (%d)\n", GetLastError()); + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + return 1; + } + + if (!StartService(schService, 0, nullptr)) { + printf("StartService failed (%d)\n", GetLastError()); + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + return 1; + } + + std::cout << "Service successfully installed for user '" << scmUser << "'\n"; + + String userFilePath = Utility::GetIcingaDataPath() + "\\etc\\icinga2\\user"; + + std::ofstream fuser(userFilePath.CStr(), std::ios::out | std::ios::trunc); + if (fuser) + fuser << scmUser; + else + std::cout << "Could not write user to " << userFilePath << "\n"; + } + + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + + return 0; +} + +static VOID ReportSvcStatus(DWORD dwCurrentState, + DWORD dwWin32ExitCode, + DWORD dwWaitHint) +{ + static DWORD dwCheckPoint = 1; + + l_SvcStatus.dwCurrentState = dwCurrentState; + l_SvcStatus.dwWin32ExitCode = dwWin32ExitCode; + l_SvcStatus.dwWaitHint = dwWaitHint; + + if (dwCurrentState == SERVICE_START_PENDING) + l_SvcStatus.dwControlsAccepted = 0; + else + l_SvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; + + if ((dwCurrentState == SERVICE_RUNNING) || + (dwCurrentState == SERVICE_STOPPED)) + l_SvcStatus.dwCheckPoint = 0; + else + l_SvcStatus.dwCheckPoint = dwCheckPoint++; + + SetServiceStatus(l_SvcStatusHandle, &l_SvcStatus); +} + +static VOID WINAPI ServiceControlHandler(DWORD dwCtrl) +{ + if (dwCtrl == SERVICE_CONTROL_STOP) { + ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0); + TerminateJobObject(l_Job, 0); + } +} + +static VOID WINAPI ServiceMain(DWORD argc, LPSTR *argv) +{ + l_SvcStatusHandle = RegisterServiceCtrlHandler( + "icinga2", + ServiceControlHandler); + + l_SvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + l_SvcStatus.dwServiceSpecificExitCode = 0; + + ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0); + l_Job = CreateJobObject(nullptr, nullptr); + + for (;;) { + LPSTR arg = argv[0]; + String args; + int uargc = Application::GetArgC(); + char **uargv = Application::GetArgV(); + + args += Utility::EscapeShellArg(Application::GetExePath(uargv[0])); + + for (int i = 2; i < uargc && uargv[i]; i++) { + if (args != "") + args += " "; + + args += Utility::EscapeShellArg(uargv[i]); + } + + STARTUPINFO si = { sizeof(si) }; + PROCESS_INFORMATION pi; + + char *uargs = strdup(args.CStr()); + + BOOL res = CreateProcess(nullptr, uargs, nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi); + + free(uargs); + + if (!res) + break; + + CloseHandle(pi.hThread); + + AssignProcessToJobObject(l_Job, pi.hProcess); + + if (WaitForSingleObject(pi.hProcess, INFINITE) != WAIT_OBJECT_0) + break; + + DWORD exitStatus; + + if (!GetExitCodeProcess(pi.hProcess, &exitStatus)) + break; + + if (exitStatus != 7) + break; + } + + TerminateJobObject(l_Job, 0); + + CloseHandle(l_Job); + + ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0); + + Application::Exit(0); +} +#endif /* _WIN32 */ + +/** +* Entry point for the Icinga application. +* +* @params argc Number of command line arguments. +* @params argv Command line arguments. +* @returns The application's exit status. +*/ +int main(int argc, char **argv) +{ +#ifndef _WIN32 + String keepFDs = Utility::GetFromEnvironment("ICINGA2_KEEP_FDS"); + if (keepFDs.IsEmpty()) { +#ifdef I2_DEBUG + Utility::CloseAllFDs({0, 1, 2}, [](int fd) { + std::cerr << "Closed FD " << fd << " which we inherited from our parent process." << std::endl; + }); +#else /* I2_DEBUG */ + Utility::CloseAllFDs({0, 1, 2}); +#endif /* I2_DEBUG */ + } +#endif /* _WIN32 */ + + /* must be called before using any other libbase functions */ + Application::InitializeBase(); + + /* Set command-line arguments. */ + Application::SetArgC(argc); + Application::SetArgV(argv); + +#ifdef _WIN32 + if (argc > 1 && strcmp(argv[1], "--scm-install") == 0) { + return SetupService(true, argc - 2, &argv[2]); + } + + if (argc > 1 && strcmp(argv[1], "--scm-uninstall") == 0) { + return SetupService(false, argc - 2, &argv[2]); + } + + if (argc > 1 && strcmp(argv[1], "--scm") == 0) { + SERVICE_TABLE_ENTRY dispatchTable[] = { + { "icinga2", ServiceMain }, + { nullptr, nullptr } + }; + + StartServiceCtrlDispatcher(dispatchTable); + Application::Exit(EXIT_FAILURE); + } +#endif /* _WIN32 */ + + int rc = Main(); + + Application::Exit(rc); +} |