icinga2/icinga-app/icinga.cpp
Julian Brost 0ebcd2662d No longer allow overriding the frozen attribute of containers
The Array, Dictionary, and Namespace types provide a Freeze() method that makes
them read-only. So far, there was the possibility to call some methods with
`overrideFrozen=true` which would then bypass the corresponding check and allow
modification of the data structures nonetheless.

With 24b57f0d3a222835178e88489eabd595755ed883, this possibility was already
removed from the Namespace type. However, for interface compatibility, it kept
the parameter and just ignores it, throwing an exception on any modification on
a frozen instance.

The only place using `overrideFrozen` was processing of the `-D`/`--define`
command line flag that allows setting additional variables in the DSL. At the
time it is evaluated, there are no user-created data structures yet that could
be frozen, so the only frozen objects that could be encountered are Namespaces
(Icinga doesn't freeze other types by itself) and for these, `overrideFrozen`
already has no effect.

Hence, there is no harm in removing `overrideFrozen` altogether. This
simplifies the code and also means that frozen objects are now indeed read-only
without exceptions, allowing further optimizations regarding locking in the
future.
2025-07-08 14:16:20 +02:00

948 lines
27 KiB
C++

/* 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 (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]))};
expr = std::move(indexerExpr);
}
std::unique_ptr<SetExpression> setExpr{new SetExpression(std::move(expr), OpSetLiteral, MakeLiteral(value))};
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 (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);
}