1 /******************************************************************************
3 * Copyright (C) 2012-2018 Icinga Development Team (https://icinga.com/) *
5 * This program is free software; you can redistribute it and/or *
6 * modify it under the terms of the GNU General Public License *
7 * as published by the Free Software Foundation; either version 2 *
8 * of the License, or (at your option) any later version. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the Free Software Foundation *
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
18 ******************************************************************************/
20 #include "cli/clicommand.hpp"
21 #include "config/configcompilercontext.hpp"
22 #include "config/configcompiler.hpp"
23 #include "config/configitembuilder.hpp"
24 #include "config/expression.hpp"
25 #include "base/application.hpp"
26 #include "base/configuration.hpp"
27 #include "base/logger.hpp"
28 #include "base/timer.hpp"
29 #include "base/utility.hpp"
30 #include "base/loader.hpp"
31 #include "base/exception.hpp"
32 #include "base/convert.hpp"
33 #include "base/scriptglobal.hpp"
34 #include "base/context.hpp"
35 #include "base/console.hpp"
36 #include "base/process.hpp"
38 #include <boost/program_options.hpp>
39 #include <boost/algorithm/string/split.hpp>
43 # include <sys/types.h>
49 # include <Shellapi.h>
53 using namespace icinga;
54 namespace po = boost::program_options;
57 static SERVICE_STATUS l_SvcStatus;
58 static SERVICE_STATUS_HANDLE l_SvcStatusHandle;
62 static std::vector<String> GetLogLevelCompletionSuggestions(const String& arg)
64 std::vector<String> result;
66 String debugLevel = "debug";
67 if (debugLevel.Find(arg) == 0)
68 result.push_back(debugLevel);
70 String noticeLevel = "notice";
71 if (noticeLevel.Find(arg) == 0)
72 result.push_back(noticeLevel);
74 String informationLevel = "information";
75 if (informationLevel.Find(arg) == 0)
76 result.push_back(informationLevel);
78 String warningLevel = "warning";
79 if (warningLevel.Find(arg) == 0)
80 result.push_back(warningLevel);
82 String criticalLevel = "critical";
83 if (criticalLevel.Find(arg) == 0)
84 result.push_back(criticalLevel);
89 static std::vector<String> GlobalArgumentCompletion(const String& argument, const String& word)
91 if (argument == "include")
92 return GetBashCompletionSuggestions("directory", word);
93 else if (argument == "log-level")
94 return GetLogLevelCompletionSuggestions(word);
96 return std::vector<String>();
99 static void HandleLegacyDefines()
102 String dataPrefix = Utility::GetIcingaDataPath();
105 Value localStateDir = Configuration::LocalStateDir;
107 if (!localStateDir.IsEmpty()) {
108 Log(LogWarning, "icinga-app")
109 << "Please do not set the deprecated 'LocalStateDir' constant,"
110 << " use the 'DataDir', 'LogDir', 'CacheDir' and 'SpoolDir' constants instead!"
111 << " For compatibility reasons, these are now set based on the 'LocalStateDir' constant.";
114 Configuration::DataDir = localStateDir + "\\lib\\icinga2";
115 Configuration::LogDir = localStateDir + "\\log\\icinga2";
116 Configuration::CacheDir = localStateDir + "\\cache\\icinga2";
117 Configuration::SpoolDir = localStateDir + "\\spool\\icinga2";
119 Configuration::LocalStateDir = dataPrefix + "\\var";
121 Configuration::DataDir = localStateDir + "/lib/icinga2";
122 Configuration::LogDir = localStateDir + "/log/icinga2";
123 Configuration::CacheDir = localStateDir + "/cache/icinga2";
124 Configuration::SpoolDir = localStateDir + "/spool/icinga2";
126 Configuration::LocalStateDir = ICINGA_LOCALSTATEDIR;
130 Value sysconfDir = Configuration::SysconfDir;
131 if (!sysconfDir.IsEmpty()) {
132 Log(LogWarning, "icinga-app")
133 << "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.";
136 Configuration::ConfigDir = sysconfDir + "\\icinga2";
138 Configuration::SysconfDir = dataPrefix + "\\etc";
140 Configuration::ConfigDir = sysconfDir + "/icinga2";
142 Configuration::SysconfDir = ICINGA_SYSCONFDIR;
146 Value runDir = Configuration::RunDir;
147 if (!runDir.IsEmpty()) {
148 Log(LogWarning, "icinga-app")
149 << "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.";
152 Configuration::InitRunDir = runDir + "\\icinga2";
154 Configuration::RunDir = dataPrefix + "\\var\\run";
156 Configuration::InitRunDir = runDir + "/icinga2";
158 Configuration::RunDir = ICINGA_RUNDIR;
165 int argc = Application::GetArgC();
166 char **argv = Application::GetArgV();
168 bool autocomplete = false;
171 if (argc >= 4 && strcmp(argv[1], "--autocomplete") == 0) {
175 autoindex = Convert::ToLong(argv[2]);
176 } catch (const std::invalid_argument&) {
177 Log(LogCritical, "icinga-app")
178 << "Invalid index for --autocomplete: " << argv[2];
186 Application::SetStartTime(Utility::GetTime());
188 /* Set thread title. */
189 Utility::SetThreadName("Main Thread", false);
191 /* Install exception handlers to make debugging easier. */
192 Application::InstallExceptionHandlers();
195 bool builtinPaths = true;
197 /* Programm install location, C:/Program Files/Icinga2 */
198 String binaryPrefix = Utility::GetIcingaInstallPath();
199 /* Returns the datapath for daemons, %PROGRAMDATA%/icinga2 */
200 String dataPrefix = Utility::GetIcingaDataPath();
202 if (!binaryPrefix.IsEmpty() && !dataPrefix.IsEmpty()) {
203 Configuration::ProgramData = dataPrefix;
205 Configuration::ConfigDir = dataPrefix + "\\etc\\icinga2";
207 Configuration::DataDir = dataPrefix + "\\var\\lib\\icinga2";
208 Configuration::LogDir = dataPrefix + "\\var\\log\\icinga2";
209 Configuration::CacheDir = dataPrefix + "\\var\\cache\\icinga2";
210 Configuration::SpoolDir = dataPrefix + "\\var\\spool\\icinga2";
212 Configuration::PrefixDir = binaryPrefix;
214 /* Internal constants. */
215 Configuration::PkgDataDir = binaryPrefix + "\\share\\icinga2";
216 Configuration::IncludeConfDir = binaryPrefix + "\\share\\icinga2\\include";
218 Configuration::InitRunDir = dataPrefix + "\\var\\run\\icinga2";
220 Log(LogWarning, "icinga-app", "Registry key could not be read. Falling back to built-in paths.");
223 Configuration::ConfigDir = ICINGA_CONFIGDIR;
225 Configuration::DataDir = ICINGA_DATADIR;
226 Configuration::LogDir = ICINGA_LOGDIR;
227 Configuration::CacheDir = ICINGA_CACHEDIR;
228 Configuration::SpoolDir = ICINGA_SPOOLDIR;
230 Configuration::PrefixDir = ICINGA_PREFIX;
232 /* Internal constants. */
233 Configuration::PkgDataDir = ICINGA_PKGDATADIR;
234 Configuration::IncludeConfDir = ICINGA_INCLUDECONFDIR;
236 Configuration::InitRunDir = ICINGA_INITRUNDIR;
242 Configuration::ZonesDir = Configuration::ConfigDir + "/zones.d";
244 String icingaUser = Utility::GetFromEnvironment("ICINGA2_USER");
245 if (icingaUser.IsEmpty())
246 icingaUser = ICINGA_USER;
248 String icingaGroup = Utility::GetFromEnvironment("ICINGA2_GROUP");
249 if (icingaGroup.IsEmpty())
250 icingaGroup = ICINGA_GROUP;
252 Configuration::RunAsUser = icingaUser;
253 Configuration::RunAsGroup = icingaGroup;
257 String rLimitFiles = Utility::GetFromEnvironment("ICINGA2_RLIMIT_FILES");
258 if (rLimitFiles.IsEmpty())
259 Configuration::RLimitFiles = Application::GetDefaultRLimitFiles();
262 Configuration::RLimitFiles = Convert::ToLong(rLimitFiles);
263 } catch (const std::invalid_argument& ex) {
265 << "Error setting \"ICINGA2_RLIMIT_FILES\": " << ex.what() << '\n';
269 #endif /* RLIMIT_NOFILE */
272 String rLimitProcesses = Utility::GetFromEnvironment("ICINGA2_RLIMIT_PROCESSES");
273 if (rLimitProcesses.IsEmpty())
274 Configuration::RLimitProcesses = Application::GetDefaultRLimitProcesses();
277 Configuration::RLimitProcesses = Convert::ToLong(rLimitProcesses);
278 } catch (const std::invalid_argument& ex) {
280 << "Error setting \"ICINGA2_RLIMIT_PROCESSES\": " << ex.what() << '\n';
284 #endif /* RLIMIT_NPROC */
287 String rLimitStack = Utility::GetFromEnvironment("ICINGA2_RLIMIT_STACK");
288 if (rLimitStack.IsEmpty())
289 Configuration::RLimitStack = Application::GetDefaultRLimitStack();
292 Configuration::RLimitStack = Convert::ToLong(rLimitStack);
293 } catch (const std::invalid_argument& ex) {
295 << "Error setting \"ICINGA2_RLIMIT_STACK\": " << ex.what() << '\n';
299 #endif /* RLIMIT_STACK */
302 ScriptGlobal::Set("MaxConcurrentChecks", Application::GetDefaultMaxConcurrentChecks());
304 ScriptGlobal::Set("System.PlatformKernel", Utility::GetPlatformKernel(), true);
305 ScriptGlobal::Set("System.PlatformKernelVersion", Utility::GetPlatformKernelVersion(), true);
306 ScriptGlobal::Set("System.PlatformName", Utility::GetPlatformName(), true);
307 ScriptGlobal::Set("System.PlatformVersion", Utility::GetPlatformVersion(), true);
308 ScriptGlobal::Set("System.PlatformArchitecture", Utility::GetPlatformArchitecture(), true);
310 ScriptGlobal::Set("System.BuildHostName", ICINGA_BUILD_HOST_NAME, true);
311 ScriptGlobal::Set("System.BuildCompilerName", ICINGA_BUILD_COMPILER_NAME, true);
312 ScriptGlobal::Set("System.BuildCompilerVersion", ICINGA_BUILD_COMPILER_VERSION, true);
315 Application::SetResourceLimits();
317 LogSeverity logLevel = Logger::GetConsoleLogSeverity();
318 Logger::SetConsoleLogSeverity(LogWarning);
320 po::options_description visibleDesc("Global options");
322 visibleDesc.add_options()
323 ("help,h", "show this help message")
324 ("version,V", "show version information")
326 ("color", "use VT100 color codes even when stdout is not a terminal")
328 ("define,D", po::value<std::vector<std::string> >(), "define a constant")
329 ("include,I", po::value<std::vector<std::string> >(), "add include search directory")
330 ("log-level,x", po::value<std::string>(), "specify the log level for the console log.\n"
331 "The valid value is either debug, notice, information (default), warning, or critical")
332 ("script-debugger,X", "whether to enable the script debugger");
334 po::options_description hiddenDesc("Hidden options");
336 hiddenDesc.add_options()
337 ("no-stack-rlimit", "used internally, do not specify manually")
338 ("arg", po::value<std::vector<std::string> >(), "positional argument");
340 po::positional_options_description positionalDesc;
341 positionalDesc.add("arg", -1);
344 CLICommand::Ptr command;
345 po::variables_map vm;
348 CLICommand::ParseCommand(argc, argv, visibleDesc, hiddenDesc, positionalDesc,
349 vm, cmdname, command, autocomplete);
350 } catch (const std::exception& ex) {
351 Log(LogCritical, "icinga-app")
352 << "Error while parsing command-line options: " << ex.what();
357 char username[UNLEN + 1];
358 DWORD usernameLen = UNLEN + 1;
359 GetUserName(username, &usernameLen);
361 std::ifstream userFile;
363 /* The implicit string assignment is needed for Windows builds. */
364 String configDir = Configuration::ConfigDir;
365 userFile.open(configDir + "/user");
367 if (userFile && command && !Application::IsProcessElevated()) {
368 std::string userLine;
369 if (std::getline(userFile, userLine)) {
372 std::vector<std::string> strs;
373 boost::split(strs, userLine, boost::is_any_of("\\"));
375 if (username != strs[1] && command->GetImpersonationLevel() == ImpersonationLevel::ImpersonateIcinga
376 || command->GetImpersonationLevel() == ImpersonationLevel::ImpersonateRoot) {
377 TCHAR szPath[MAX_PATH];
379 if (GetModuleFileName(nullptr, szPath, ARRAYSIZE(szPath))) {
380 SHELLEXECUTEINFO sei = { sizeof(sei) };
381 sei.lpVerb = _T("runas");
382 sei.lpFile = "cmd.exe";
383 sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NOASYNC | SEE_MASK_FLAG_NO_UI;
386 std::stringstream parameters;
388 parameters << "/C " << "\"" << szPath << "\"" << " ";
390 for (int i = 1; i < argc; i++) {
393 parameters << argv[i];
396 parameters << " & SET exitcode=%errorlevel%";
397 parameters << " & pause";
398 parameters << " & EXIT /B %exitcode%";
400 std::string str = parameters.str();
401 LPCSTR cstr = str.c_str();
403 sei.lpParameters = cstr;
405 if (!ShellExecuteEx(&sei)) {
406 DWORD dwError = GetLastError();
407 if (dwError == ERROR_CANCELLED)
408 Application::Exit(0);
410 WaitForSingleObject(sei.hProcess, INFINITE);
413 GetExitCodeProcess(sei.hProcess, &exitCode);
415 CloseHandle(sei.hProcess);
417 Application::Exit(exitCode);
428 if (vm.count("color")) {
429 Console::SetType(std::cout, Console_VT100);
430 Console::SetType(std::cerr, Console_VT100);
434 if (vm.count("define")) {
435 for (const String& define : vm["define"].as<std::vector<std::string> >()) {
437 size_t pos = define.FindFirstOf('=');
438 if (pos != String::NPos) {
439 key = define.SubStr(0, pos);
440 value = define.SubStr(pos + 1);
446 std::vector<String> keyTokens = key.Split(".");
448 std::unique_ptr<Expression> expr;
449 std::unique_ptr<VariableExpression> varExpr{new VariableExpression(keyTokens[0], {}, DebugInfo())};
450 expr = std::move(varExpr);
452 for (size_t i = 1; i < keyTokens.size(); i++) {
453 std::unique_ptr<IndexerExpression> indexerExpr{new IndexerExpression(std::move(expr), MakeLiteral(keyTokens[i]))};
454 indexerExpr->SetOverrideFrozen();
455 expr = std::move(indexerExpr);
458 std::unique_ptr<SetExpression> setExpr{new SetExpression(std::move(expr), OpSetLiteral, MakeLiteral(value))};
459 setExpr->SetOverrideFrozen();
461 ScriptFrame frame(true);
462 setExpr->Evaluate(frame);
466 Configuration::SetReadOnly(true);
468 /* Ensure that all defined constants work in the way we expect them. */
469 HandleLegacyDefines();
471 if (vm.count("script-debugger"))
472 Application::SetScriptDebuggerEnabled(true);
474 Configuration::StatePath = Configuration::DataDir + "/icinga2.state";
475 Configuration::ModAttrPath = Configuration::DataDir + "/modified-attributes.conf";
476 Configuration::ObjectsPath = Configuration::CacheDir + "/icinga2.debug";
477 Configuration::VarsPath = Configuration::CacheDir + "/icinga2.vars";
478 Configuration::PidPath = Configuration::InitRunDir + "/icinga2.pid";
480 ConfigCompiler::AddIncludeSearchDir(Configuration::IncludeConfDir);
482 if (!autocomplete && vm.count("include")) {
483 for (const String& includePath : vm["include"].as<std::vector<std::string> >()) {
484 ConfigCompiler::AddIncludeSearchDir(includePath);
489 Logger::SetConsoleLogSeverity(logLevel);
491 if (vm.count("log-level")) {
492 String severity = vm["log-level"].as<std::string>();
494 LogSeverity logLevel = LogInformation;
496 logLevel = Logger::StringToSeverity(severity);
497 } catch (std::exception&) {
498 /* Inform user and exit */
499 Log(LogCritical, "icinga-app", "Invalid log level set. Default is 'information'.");
503 Logger::SetConsoleLogSeverity(logLevel);
506 if (!command || vm.count("help") || vm.count("version")) {
510 appName = Utility::BaseName(Application::GetArgV()[0]);
511 } catch (const std::bad_alloc&) {
512 Log(LogCritical, "icinga-app", "Allocation failed.");
516 if (appName.GetLength() > 3 && appName.SubStr(0, 3) == "lt-")
517 appName = appName.SubStr(3, appName.GetLength() - 3);
519 std::cout << appName << " " << "- The Icinga 2 network monitoring daemon (version: "
520 << ConsoleColorTag(vm.count("version") ? Console_ForegroundRed : Console_Normal)
521 << Application::GetAppVersion()
524 #endif /* I2_DEBUG */
525 << ConsoleColorTag(Console_Normal)
526 << ")" << std::endl << std::endl;
528 if ((!command || vm.count("help")) && !vm.count("version")) {
529 std::cout << "Usage:" << std::endl
530 << " " << Utility::BaseName(argv[0]) << " ";
532 if (cmdname.IsEmpty())
533 std::cout << "<command>";
535 std::cout << cmdname;
537 std::cout << " [<arguments>]" << std::endl;
540 std::cout << std::endl
541 << command->GetDescription() << std::endl;
545 if (vm.count("version")) {
546 std::cout << "Copyright (c) 2012-" << Utility::FormatDateTime("%Y", Utility::GetTime())
547 << " Icinga GmbH (https://icinga.com/)" << std::endl
548 << "License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl2.html>" << std::endl
549 << "This is free software: you are free to change and redistribute it." << std::endl
550 << "There is NO WARRANTY, to the extent permitted by law.";
553 std::cout << std::endl;
555 if (vm.count("version")) {
556 std::cout << std::endl;
558 Application::DisplayInfoMessage(std::cout, true);
564 if (!command || vm.count("help")) {
566 CLICommand::ShowCommands(argc, argv, nullptr);
568 std::cout << visibleDesc << std::endl
569 << "Report bugs at <https://github.com/Icinga/icinga2>" << std::endl
570 << "Get support: <https://icinga.com/support/>" << std::endl
571 << "Documentation: <https://icinga.com/docs/>" << std::endl
572 << "Icinga home page: <https://icinga.com/>" << std::endl;
580 CLICommand::ShowCommands(argc, argv, &visibleDesc, &hiddenDesc,
581 &GlobalArgumentCompletion, true, autoindex);
583 } else if (command) {
584 Logger::DisableTimestamp();
586 if (command->GetImpersonationLevel() == ImpersonateRoot) {
588 Log(LogCritical, "cli", "This command must be run as root.");
591 } else if (command && command->GetImpersonationLevel() == ImpersonateIcinga) {
592 String group = Configuration::RunAsGroup;
593 String user = Configuration::RunAsUser;
596 struct group *gr = getgrnam(group.CStr());
600 Log(LogCritical, "cli")
601 << "Invalid group specified: " << group;
604 Log(LogCritical, "cli")
605 << "getgrnam() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
610 if (getgid() != gr->gr_gid) {
611 if (!vm.count("reload-internal") && setgroups(0, nullptr) < 0) {
612 Log(LogCritical, "cli")
613 << "setgroups() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
614 Log(LogCritical, "cli")
615 << "Please re-run this command as a privileged user or using the \"" << user << "\" account.";
619 if (setgid(gr->gr_gid) < 0) {
620 Log(LogCritical, "cli")
621 << "setgid() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
627 struct passwd *pw = getpwnam(user.CStr());
631 Log(LogCritical, "cli")
632 << "Invalid user specified: " << user;
635 Log(LogCritical, "cli")
636 << "getpwnam() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
641 // also activate the additional groups the configured user is member of
642 if (getuid() != pw->pw_uid) {
643 if (!vm.count("reload-internal") && initgroups(user.CStr(), pw->pw_gid) < 0) {
644 Log(LogCritical, "cli")
645 << "initgroups() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
646 Log(LogCritical, "cli")
647 << "Please re-run this command as a privileged user or using the \"" << user << "\" account.";
651 if (setuid(pw->pw_uid) < 0) {
652 Log(LogCritical, "cli")
653 << "setuid() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
654 Log(LogCritical, "cli")
655 << "Please re-run this command as a privileged user or using the \"" << user << "\" account.";
661 Process::InitializeSpawnHelper();
664 std::vector<std::string> args;
666 args = vm["arg"].as<std::vector<std::string> >();
668 if (static_cast<int>(args.size()) < command->GetMinArguments()) {
669 Log(LogCritical, "cli")
670 << "Too few arguments. Command needs at least " << command->GetMinArguments()
671 << " argument" << (command->GetMinArguments() != 1 ? "s" : "") << ".";
675 if (command->GetMaxArguments() >= 0 && static_cast<int>(args.size()) > command->GetMaxArguments()) {
676 Log(LogCritical, "cli")
677 << "Too many arguments. At most " << command->GetMaxArguments()
678 << " argument" << (command->GetMaxArguments() != 1 ? "s" : "") << " may be specified.";
682 rc = command->Run(vm, args);
689 static int SetupService(bool install, int argc, char **argv)
691 SC_HANDLE schSCManager = OpenSCManager(nullptr, nullptr, SC_MANAGER_ALL_ACCESS);
694 printf("OpenSCManager failed (%d)\n", GetLastError());
698 TCHAR szPath[MAX_PATH];
700 if (!GetModuleFileName(nullptr, szPath, MAX_PATH)) {
701 printf("Cannot install service (%d)\n", GetLastError());
706 szArgs = Utility::EscapeShellArg(szPath) + " --scm";
708 std::string scmUser = "NT AUTHORITY\\NetworkService";
709 std::ifstream initf(Utility::GetIcingaDataPath() + "\\etc\\icinga2\\user");
711 std::getline(initf, scmUser);
715 for (int i = 0; i < argc; i++) {
716 if (!strcmp(argv[i], "--scm-user") && i + 1 < argc) {
717 scmUser = argv[i + 1];
720 szArgs += " " + Utility::EscapeShellArg(argv[i]);
723 SC_HANDLE schService = OpenService(schSCManager, "icinga2", SERVICE_ALL_ACCESS);
726 SERVICE_STATUS status;
727 ControlService(schService, SERVICE_CONTROL_STOP, &status);
729 double start = Utility::GetTime();
730 while (status.dwCurrentState != SERVICE_STOPPED) {
731 double end = Utility::GetTime();
733 if (end - start > 30) {
734 printf("Could not stop the service.\n");
740 if (!QueryServiceStatus(schService, &status)) {
741 printf("QueryServiceStatus failed (%d)\n", GetLastError());
745 } else if (install) {
746 schService = CreateService(
751 SERVICE_WIN32_OWN_PROCESS,
752 SERVICE_DEMAND_START,
753 SERVICE_ERROR_NORMAL,
762 printf("CreateService failed (%d)\n", GetLastError());
763 CloseServiceHandle(schSCManager);
767 printf("Service isn't installed.\n");
768 CloseServiceHandle(schSCManager);
773 if (!DeleteService(schService)) {
774 printf("DeleteService failed (%d)\n", GetLastError());
775 CloseServiceHandle(schService);
776 CloseServiceHandle(schSCManager);
780 printf("Service uninstalled successfully\n");
782 if (!ChangeServiceConfig(schService, SERVICE_NO_CHANGE, SERVICE_AUTO_START,
783 SERVICE_ERROR_NORMAL, szArgs.CStr(), nullptr, nullptr, nullptr, scmUser.c_str(), nullptr, nullptr)) {
784 printf("ChangeServiceConfig failed (%d)\n", GetLastError());
785 CloseServiceHandle(schService);
786 CloseServiceHandle(schSCManager);
790 SERVICE_DESCRIPTION sdDescription = { "The Icinga 2 monitoring application" };
791 if(!ChangeServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, &sdDescription)) {
792 printf("ChangeServiceConfig2 failed (%d)\n", GetLastError());
793 CloseServiceHandle(schService);
794 CloseServiceHandle(schSCManager);
798 if (!StartService(schService, 0, nullptr)) {
799 printf("StartService failed (%d)\n", GetLastError());
800 CloseServiceHandle(schService);
801 CloseServiceHandle(schSCManager);
805 std::cout << "Service successfully installed for user '" << scmUser << "'\n";
807 String userFilePath = Utility::GetIcingaDataPath() + "\\etc\\icinga2\\user";
809 std::ofstream fuser(userFilePath.CStr(), std::ios::out | std::ios::trunc);
813 std::cout << "Could not write user to " << userFilePath << "\n";
816 CloseServiceHandle(schService);
817 CloseServiceHandle(schSCManager);
822 static VOID ReportSvcStatus(DWORD dwCurrentState,
823 DWORD dwWin32ExitCode,
826 static DWORD dwCheckPoint = 1;
828 l_SvcStatus.dwCurrentState = dwCurrentState;
829 l_SvcStatus.dwWin32ExitCode = dwWin32ExitCode;
830 l_SvcStatus.dwWaitHint = dwWaitHint;
832 if (dwCurrentState == SERVICE_START_PENDING)
833 l_SvcStatus.dwControlsAccepted = 0;
835 l_SvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
837 if ((dwCurrentState == SERVICE_RUNNING) ||
838 (dwCurrentState == SERVICE_STOPPED))
839 l_SvcStatus.dwCheckPoint = 0;
841 l_SvcStatus.dwCheckPoint = dwCheckPoint++;
843 SetServiceStatus(l_SvcStatusHandle, &l_SvcStatus);
846 static VOID WINAPI ServiceControlHandler(DWORD dwCtrl)
848 if (dwCtrl == SERVICE_CONTROL_STOP) {
849 ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0);
850 TerminateJobObject(l_Job, 0);
854 static VOID WINAPI ServiceMain(DWORD argc, LPSTR *argv)
856 l_SvcStatusHandle = RegisterServiceCtrlHandler(
858 ServiceControlHandler);
860 l_SvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
861 l_SvcStatus.dwServiceSpecificExitCode = 0;
863 ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0);
864 l_Job = CreateJobObject(nullptr, nullptr);
869 int uargc = Application::GetArgC();
870 char **uargv = Application::GetArgV();
872 args += Utility::EscapeShellArg(Application::GetExePath(uargv[0]));
874 for (int i = 2; i < uargc && uargv[i]; i++) {
878 args += Utility::EscapeShellArg(uargv[i]);
881 STARTUPINFO si = { sizeof(si) };
882 PROCESS_INFORMATION pi;
884 char *uargs = strdup(args.CStr());
886 BOOL res = CreateProcess(nullptr, uargs, nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi);
893 CloseHandle(pi.hThread);
895 AssignProcessToJobObject(l_Job, pi.hProcess);
897 if (WaitForSingleObject(pi.hProcess, INFINITE) != WAIT_OBJECT_0)
902 if (!GetExitCodeProcess(pi.hProcess, &exitStatus))
909 TerminateJobObject(l_Job, 0);
913 ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
915 Application::Exit(0);
920 * Entry point for the Icinga application.
922 * @params argc Number of command line arguments.
923 * @params argv Command line arguments.
924 * @returns The application's exit status.
926 int main(int argc, char **argv)
929 if (!getenv("ICINGA2_KEEP_FDS")) {
931 if (getrlimit(RLIMIT_NOFILE, &rl) >= 0) {
932 rlim_t maxfds = rl.rlim_max;
934 if (maxfds == RLIM_INFINITY)
937 for (rlim_t i = 3; i < maxfds; i++) {
942 std::cerr << "Closed FD " << i << " which we inherited from our parent process." << std::endl;
943 #endif /* I2_DEBUG */
949 /* must be called before using any other libbase functions */
950 Application::InitializeBase();
952 /* Set command-line arguments. */
953 Application::SetArgC(argc);
954 Application::SetArgV(argv);
957 if (argc > 1 && strcmp(argv[1], "--scm-install") == 0) {
958 return SetupService(true, argc - 2, &argv[2]);
961 if (argc > 1 && strcmp(argv[1], "--scm-uninstall") == 0) {
962 return SetupService(false, argc - 2, &argv[2]);
965 if (argc > 1 && strcmp(argv[1], "--scm") == 0) {
966 SERVICE_TABLE_ENTRY dispatchTable[] = {
967 { "icinga2", ServiceMain },
971 StartServiceCtrlDispatcher(dispatchTable);
972 Application::Exit(EXIT_FAILURE);
978 Application::Exit(rc);