1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
2
3 #include "cli/clicommand.hpp"
4 #include "config/configcompilercontext.hpp"
5 #include "config/configcompiler.hpp"
6 #include "config/configitembuilder.hpp"
7 #include "config/expression.hpp"
8 #include "base/application.hpp"
9 #include "base/configuration.hpp"
10 #include "base/logger.hpp"
11 #include "base/timer.hpp"
12 #include "base/utility.hpp"
13 #include "base/loader.hpp"
14 #include "base/exception.hpp"
15 #include "base/convert.hpp"
16 #include "base/scriptglobal.hpp"
17 #include "base/context.hpp"
18 #include "base/console.hpp"
19 #include "base/process.hpp"
20 #include "config.h"
21 #include <boost/program_options.hpp>
22 #include <boost/algorithm/string/split.hpp>
23 #include <thread>
24
25 #ifndef _WIN32
26 # include <sys/types.h>
27 # include <pwd.h>
28 # include <grp.h>
29 #else
30 # include <windows.h>
31 # include <Lmcons.h>
32 # include <Shellapi.h>
33 # include <tchar.h>
34 #endif /* _WIN32 */
35
36 using namespace icinga;
37 namespace po = boost::program_options;
38
39 #ifdef _WIN32
40 static SERVICE_STATUS l_SvcStatus;
41 static SERVICE_STATUS_HANDLE l_SvcStatusHandle;
42 static HANDLE l_Job;
43 #endif /* _WIN32 */
44
GetLogLevelCompletionSuggestions(const String & arg)45 static std::vector<String> GetLogLevelCompletionSuggestions(const String& arg)
46 {
47 std::vector<String> result;
48
49 String debugLevel = "debug";
50 if (debugLevel.Find(arg) == 0)
51 result.push_back(debugLevel);
52
53 String noticeLevel = "notice";
54 if (noticeLevel.Find(arg) == 0)
55 result.push_back(noticeLevel);
56
57 String informationLevel = "information";
58 if (informationLevel.Find(arg) == 0)
59 result.push_back(informationLevel);
60
61 String warningLevel = "warning";
62 if (warningLevel.Find(arg) == 0)
63 result.push_back(warningLevel);
64
65 String criticalLevel = "critical";
66 if (criticalLevel.Find(arg) == 0)
67 result.push_back(criticalLevel);
68
69 return result;
70 }
71
GlobalArgumentCompletion(const String & argument,const String & word)72 static std::vector<String> GlobalArgumentCompletion(const String& argument, const String& word)
73 {
74 if (argument == "include")
75 return GetBashCompletionSuggestions("directory", word);
76 else if (argument == "log-level")
77 return GetLogLevelCompletionSuggestions(word);
78 else
79 return std::vector<String>();
80 }
81
HandleLegacyDefines()82 static void HandleLegacyDefines()
83 {
84 #ifdef _WIN32
85 String dataPrefix = Utility::GetIcingaDataPath();
86 #endif /* _WIN32 */
87
88 Value localStateDir = Configuration::LocalStateDir;
89
90 if (!localStateDir.IsEmpty()) {
91 Log(LogWarning, "icinga-app")
92 << "Please do not set the deprecated 'LocalStateDir' constant,"
93 << " use the 'DataDir', 'LogDir', 'CacheDir' and 'SpoolDir' constants instead!"
94 << " For compatibility reasons, these are now set based on the 'LocalStateDir' constant.";
95
96 #ifdef _WIN32
97 Configuration::DataDir = localStateDir + "\\lib\\icinga2";
98 Configuration::LogDir = localStateDir + "\\log\\icinga2";
99 Configuration::CacheDir = localStateDir + "\\cache\\icinga2";
100 Configuration::SpoolDir = localStateDir + "\\spool\\icinga2";
101 } else {
102 Configuration::LocalStateDir = dataPrefix + "\\var";
103 #else /* _WIN32 */
104 Configuration::DataDir = localStateDir + "/lib/icinga2";
105 Configuration::LogDir = localStateDir + "/log/icinga2";
106 Configuration::CacheDir = localStateDir + "/cache/icinga2";
107 Configuration::SpoolDir = localStateDir + "/spool/icinga2";
108 } else {
109 Configuration::LocalStateDir = ICINGA_LOCALSTATEDIR;
110 #endif /* _WIN32 */
111 }
112
113 Value sysconfDir = Configuration::SysconfDir;
114 if (!sysconfDir.IsEmpty()) {
115 Log(LogWarning, "icinga-app")
116 << "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.";
117
118 #ifdef _WIN32
119 Configuration::ConfigDir = sysconfDir + "\\icinga2";
120 } else {
121 Configuration::SysconfDir = dataPrefix + "\\etc";
122 #else /* _WIN32 */
123 Configuration::ConfigDir = sysconfDir + "/icinga2";
124 } else {
125 Configuration::SysconfDir = ICINGA_SYSCONFDIR;
126 #endif /* _WIN32 */
127 }
128
129 Value runDir = Configuration::RunDir;
130 if (!runDir.IsEmpty()) {
131 Log(LogWarning, "icinga-app")
132 << "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.";
133
134 #ifdef _WIN32
135 Configuration::InitRunDir = runDir + "\\icinga2";
136 } else {
137 Configuration::RunDir = dataPrefix + "\\var\\run";
138 #else /* _WIN32 */
139 Configuration::InitRunDir = runDir + "/icinga2";
140 } else {
141 Configuration::RunDir = ICINGA_RUNDIR;
142 #endif /* _WIN32 */
143 }
144 }
145
Main()146 static int Main()
147 {
148 int argc = Application::GetArgC();
149 char **argv = Application::GetArgV();
150
151 bool autocomplete = false;
152 int autoindex = 0;
153
154 if (argc >= 4 && strcmp(argv[1], "--autocomplete") == 0) {
155 autocomplete = true;
156
157 try {
158 autoindex = Convert::ToLong(argv[2]);
159 } catch (const std::invalid_argument&) {
160 Log(LogCritical, "icinga-app")
161 << "Invalid index for --autocomplete: " << argv[2];
162 return EXIT_FAILURE;
163 }
164
165 argc -= 3;
166 argv += 3;
167 }
168
169 /* Set thread title. */
170 Utility::SetThreadName("Main Thread", false);
171
172 /* Install exception handlers to make debugging easier. */
173 Application::InstallExceptionHandlers();
174
175 #ifdef _WIN32
176 bool builtinPaths = true;
177
178 /* Programm install location, C:/Program Files/Icinga2 */
179 String binaryPrefix = Utility::GetIcingaInstallPath();
180 /* Returns the datapath for daemons, %PROGRAMDATA%/icinga2 */
181 String dataPrefix = Utility::GetIcingaDataPath();
182
183 if (!binaryPrefix.IsEmpty() && !dataPrefix.IsEmpty()) {
184 Configuration::ProgramData = dataPrefix;
185
186 Configuration::ConfigDir = dataPrefix + "\\etc\\icinga2";
187
188 Configuration::DataDir = dataPrefix + "\\var\\lib\\icinga2";
189 Configuration::LogDir = dataPrefix + "\\var\\log\\icinga2";
190 Configuration::CacheDir = dataPrefix + "\\var\\cache\\icinga2";
191 Configuration::SpoolDir = dataPrefix + "\\var\\spool\\icinga2";
192
193 Configuration::PrefixDir = binaryPrefix;
194
195 /* Internal constants. */
196 Configuration::PkgDataDir = binaryPrefix + "\\share\\icinga2";
197 Configuration::IncludeConfDir = binaryPrefix + "\\share\\icinga2\\include";
198
199 Configuration::InitRunDir = dataPrefix + "\\var\\run\\icinga2";
200 } else {
201 Log(LogWarning, "icinga-app", "Registry key could not be read. Falling back to built-in paths.");
202
203 #endif /* _WIN32 */
204 Configuration::ConfigDir = ICINGA_CONFIGDIR;
205
206 Configuration::DataDir = ICINGA_DATADIR;
207 Configuration::LogDir = ICINGA_LOGDIR;
208 Configuration::CacheDir = ICINGA_CACHEDIR;
209 Configuration::SpoolDir = ICINGA_SPOOLDIR;
210
211 Configuration::PrefixDir = ICINGA_PREFIX;
212
213 /* Internal constants. */
214 Configuration::PkgDataDir = ICINGA_PKGDATADIR;
215 Configuration::IncludeConfDir = ICINGA_INCLUDECONFDIR;
216
217 Configuration::InitRunDir = ICINGA_INITRUNDIR;
218
219 #ifdef _WIN32
220 }
221 #endif /* _WIN32 */
222
223 Configuration::ZonesDir = Configuration::ConfigDir + "/zones.d";
224
225 String icingaUser = Utility::GetFromEnvironment("ICINGA2_USER");
226 if (icingaUser.IsEmpty())
227 icingaUser = ICINGA_USER;
228
229 String icingaGroup = Utility::GetFromEnvironment("ICINGA2_GROUP");
230 if (icingaGroup.IsEmpty())
231 icingaGroup = ICINGA_GROUP;
232
233 Configuration::RunAsUser = icingaUser;
234 Configuration::RunAsGroup = icingaGroup;
235
236 if (!autocomplete) {
237 #ifdef RLIMIT_NOFILE
238 String rLimitFiles = Utility::GetFromEnvironment("ICINGA2_RLIMIT_FILES");
239 if (rLimitFiles.IsEmpty())
240 Configuration::RLimitFiles = Application::GetDefaultRLimitFiles();
241 else {
242 try {
243 Configuration::RLimitFiles = Convert::ToLong(rLimitFiles);
244 } catch (const std::invalid_argument& ex) {
245 std::cout
246 << "Error setting \"ICINGA2_RLIMIT_FILES\": " << ex.what() << '\n';
247 return EXIT_FAILURE;
248 }
249 }
250 #endif /* RLIMIT_NOFILE */
251
252 #ifdef RLIMIT_NPROC
253 String rLimitProcesses = Utility::GetFromEnvironment("ICINGA2_RLIMIT_PROCESSES");
254 if (rLimitProcesses.IsEmpty())
255 Configuration::RLimitProcesses = Application::GetDefaultRLimitProcesses();
256 else {
257 try {
258 Configuration::RLimitProcesses = Convert::ToLong(rLimitProcesses);
259 } catch (const std::invalid_argument& ex) {
260 std::cout
261 << "Error setting \"ICINGA2_RLIMIT_PROCESSES\": " << ex.what() << '\n';
262 return EXIT_FAILURE;
263 }
264 }
265 #endif /* RLIMIT_NPROC */
266
267 #ifdef RLIMIT_STACK
268 String rLimitStack = Utility::GetFromEnvironment("ICINGA2_RLIMIT_STACK");
269 if (rLimitStack.IsEmpty())
270 Configuration::RLimitStack = Application::GetDefaultRLimitStack();
271 else {
272 try {
273 Configuration::RLimitStack = Convert::ToLong(rLimitStack);
274 } catch (const std::invalid_argument& ex) {
275 std::cout
276 << "Error setting \"ICINGA2_RLIMIT_STACK\": " << ex.what() << '\n';
277 return EXIT_FAILURE;
278 }
279 }
280 #endif /* RLIMIT_STACK */
281 }
282
283 /* Calculate additional global constants. */
284 ScriptGlobal::Set("System.PlatformKernel", Utility::GetPlatformKernel(), true);
285 ScriptGlobal::Set("System.PlatformKernelVersion", Utility::GetPlatformKernelVersion(), true);
286 ScriptGlobal::Set("System.PlatformName", Utility::GetPlatformName(), true);
287 ScriptGlobal::Set("System.PlatformVersion", Utility::GetPlatformVersion(), true);
288 ScriptGlobal::Set("System.PlatformArchitecture", Utility::GetPlatformArchitecture(), true);
289
290 ScriptGlobal::Set("System.BuildHostName", ICINGA_BUILD_HOST_NAME, true);
291 ScriptGlobal::Set("System.BuildCompilerName", ICINGA_BUILD_COMPILER_NAME, true);
292 ScriptGlobal::Set("System.BuildCompilerVersion", ICINGA_BUILD_COMPILER_VERSION, true);
293
294 if (!autocomplete)
295 Application::SetResourceLimits();
296
297 LogSeverity logLevel = Logger::GetConsoleLogSeverity();
298 Logger::SetConsoleLogSeverity(LogWarning);
299
300 po::options_description visibleDesc("Global options");
301
302 visibleDesc.add_options()
303 ("help,h", "show this help message")
304 ("version,V", "show version information")
305 #ifndef _WIN32
306 ("color", "use VT100 color codes even when stdout is not a terminal")
307 #endif /* _WIN32 */
308 ("define,D", po::value<std::vector<std::string> >(), "define a constant")
309 ("include,I", po::value<std::vector<std::string> >(), "add include search directory")
310 ("log-level,x", po::value<std::string>(), "specify the log level for the console log.\n"
311 "The valid value is either debug, notice, information (default), warning, or critical")
312 ("script-debugger,X", "whether to enable the script debugger");
313
314 po::options_description hiddenDesc("Hidden options");
315
316 hiddenDesc.add_options()
317 ("no-stack-rlimit", "used internally, do not specify manually")
318 ("arg", po::value<std::vector<std::string> >(), "positional argument");
319
320 po::positional_options_description positionalDesc;
321 positionalDesc.add("arg", -1);
322
323 String cmdname;
324 CLICommand::Ptr command;
325 po::variables_map vm;
326
327 try {
328 CLICommand::ParseCommand(argc, argv, visibleDesc, hiddenDesc, positionalDesc,
329 vm, cmdname, command, autocomplete);
330 } catch (const std::exception& ex) {
331 Log(LogCritical, "icinga-app")
332 << "Error while parsing command-line options: " << ex.what();
333 return EXIT_FAILURE;
334 }
335
336 #ifdef _WIN32
337 char username[UNLEN + 1];
338 DWORD usernameLen = UNLEN + 1;
339 GetUserName(username, &usernameLen);
340
341 std::ifstream userFile;
342
343 /* The implicit string assignment is needed for Windows builds. */
344 String configDir = Configuration::ConfigDir;
345 userFile.open(configDir + "/user");
346
347 if (userFile && command && !Application::IsProcessElevated()) {
348 std::string userLine;
349 if (std::getline(userFile, userLine)) {
350 userFile.close();
351
352 std::vector<std::string> strs;
353 boost::split(strs, userLine, boost::is_any_of("\\"));
354
355 if (username != strs[1] && command->GetImpersonationLevel() == ImpersonationLevel::ImpersonateIcinga
356 || command->GetImpersonationLevel() == ImpersonationLevel::ImpersonateRoot) {
357 TCHAR szPath[MAX_PATH];
358
359 if (GetModuleFileName(nullptr, szPath, ARRAYSIZE(szPath))) {
360 SHELLEXECUTEINFO sei = { sizeof(sei) };
361 sei.lpVerb = _T("runas");
362 sei.lpFile = "cmd.exe";
363 sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NOASYNC | SEE_MASK_FLAG_NO_UI;
364 sei.nShow = SW_SHOW;
365
366 std::stringstream parameters;
367
368 parameters << "/C " << "\"" << szPath << "\"" << " ";
369
370 for (int i = 1; i < argc; i++) {
371 if (i != 1)
372 parameters << " ";
373 parameters << argv[i];
374 }
375
376 parameters << " & SET exitcode=%errorlevel%";
377 parameters << " & pause";
378 parameters << " & EXIT /B %exitcode%";
379
380 std::string str = parameters.str();
381 LPCSTR cstr = str.c_str();
382
383 sei.lpParameters = cstr;
384
385 if (!ShellExecuteEx(&sei)) {
386 DWORD dwError = GetLastError();
387 if (dwError == ERROR_CANCELLED)
388 Application::Exit(0);
389 } else {
390 WaitForSingleObject(sei.hProcess, INFINITE);
391
392 DWORD exitCode;
393 GetExitCodeProcess(sei.hProcess, &exitCode);
394
395 CloseHandle(sei.hProcess);
396
397 Application::Exit(exitCode);
398 }
399 }
400 }
401 } else {
402 userFile.close();
403 }
404 }
405 #endif /* _WIN32 */
406
407 #ifndef _WIN32
408 if (vm.count("color")) {
409 Console::SetType(std::cout, Console_VT100);
410 Console::SetType(std::cerr, Console_VT100);
411 }
412 #endif /* _WIN32 */
413
414 if (vm.count("define")) {
415 for (const String& define : vm["define"].as<std::vector<std::string> >()) {
416 String key, value;
417 size_t pos = define.FindFirstOf('=');
418 if (pos != String::NPos) {
419 key = define.SubStr(0, pos);
420 value = define.SubStr(pos + 1);
421 } else {
422 key = define;
423 value = "1";
424 }
425
426 std::vector<String> keyTokens = key.Split(".");
427
428 std::unique_ptr<Expression> expr;
429 std::unique_ptr<VariableExpression> varExpr{new VariableExpression(keyTokens[0], {}, DebugInfo())};
430 expr = std::move(varExpr);
431
432 for (size_t i = 1; i < keyTokens.size(); i++) {
433 std::unique_ptr<IndexerExpression> indexerExpr{new IndexerExpression(std::move(expr), MakeLiteral(keyTokens[i]))};
434 indexerExpr->SetOverrideFrozen();
435 expr = std::move(indexerExpr);
436 }
437
438 std::unique_ptr<SetExpression> setExpr{new SetExpression(std::move(expr), OpSetLiteral, MakeLiteral(value))};
439 setExpr->SetOverrideFrozen();
440
441 ScriptFrame frame(true);
442 setExpr->Evaluate(frame);
443 }
444 }
445
446 Configuration::SetReadOnly(true);
447
448 /* Ensure that all defined constants work in the way we expect them. */
449 HandleLegacyDefines();
450
451 if (vm.count("script-debugger"))
452 Application::SetScriptDebuggerEnabled(true);
453
454 Configuration::StatePath = Configuration::DataDir + "/icinga2.state";
455 Configuration::ModAttrPath = Configuration::DataDir + "/modified-attributes.conf";
456 Configuration::ObjectsPath = Configuration::CacheDir + "/icinga2.debug";
457 Configuration::VarsPath = Configuration::CacheDir + "/icinga2.vars";
458 Configuration::PidPath = Configuration::InitRunDir + "/icinga2.pid";
459
460 ConfigCompiler::AddIncludeSearchDir(Configuration::IncludeConfDir);
461
462 if (!autocomplete && vm.count("include")) {
463 for (const String& includePath : vm["include"].as<std::vector<std::string> >()) {
464 ConfigCompiler::AddIncludeSearchDir(includePath);
465 }
466 }
467
468 if (!autocomplete) {
469 Logger::SetConsoleLogSeverity(logLevel);
470
471 if (vm.count("log-level")) {
472 String severity = vm["log-level"].as<std::string>();
473
474 LogSeverity logLevel = LogInformation;
475 try {
476 logLevel = Logger::StringToSeverity(severity);
477 } catch (std::exception&) {
478 /* Inform user and exit */
479 Log(LogCritical, "icinga-app", "Invalid log level set. Default is 'information'.");
480 return EXIT_FAILURE;
481 }
482
483 Logger::SetConsoleLogSeverity(logLevel);
484 }
485
486 if (!command || vm.count("help") || vm.count("version")) {
487 String appName;
488
489 try {
490 appName = Utility::BaseName(Application::GetArgV()[0]);
491 } catch (const std::bad_alloc&) {
492 Log(LogCritical, "icinga-app", "Allocation failed.");
493 return EXIT_FAILURE;
494 }
495
496 if (appName.GetLength() > 3 && appName.SubStr(0, 3) == "lt-")
497 appName = appName.SubStr(3, appName.GetLength() - 3);
498
499 std::cout << appName << " " << "- The Icinga 2 network monitoring daemon (version: "
500 << ConsoleColorTag(vm.count("version") ? Console_ForegroundRed : Console_Normal)
501 << Application::GetAppVersion()
502 #ifdef I2_DEBUG
503 << "; debug"
504 #endif /* I2_DEBUG */
505 << ConsoleColorTag(Console_Normal)
506 << ")" << std::endl << std::endl;
507
508 if ((!command || vm.count("help")) && !vm.count("version")) {
509 std::cout << "Usage:" << std::endl
510 << " " << Utility::BaseName(argv[0]) << " ";
511
512 if (cmdname.IsEmpty())
513 std::cout << "<command>";
514 else
515 std::cout << cmdname;
516
517 std::cout << " [<arguments>]" << std::endl;
518
519 if (command) {
520 std::cout << std::endl
521 << command->GetDescription() << std::endl;
522 }
523 }
524
525 if (vm.count("version")) {
526 std::cout << "Copyright (c) 2012-" << Utility::FormatDateTime("%Y", Utility::GetTime())
527 << " Icinga GmbH (https://icinga.com/)" << std::endl
528 << "License GPLv2+: GNU GPL version 2 or later <https://gnu.org/licenses/gpl2.html>" << std::endl
529 << "This is free software: you are free to change and redistribute it." << std::endl
530 << "There is NO WARRANTY, to the extent permitted by law.";
531 }
532
533 std::cout << std::endl;
534
535 if (vm.count("version")) {
536 std::cout << std::endl;
537
538 Application::DisplayInfoMessage(std::cout, true);
539
540 return EXIT_SUCCESS;
541 }
542 }
543
544 if (!command || vm.count("help")) {
545 if (!command)
546 CLICommand::ShowCommands(argc, argv, nullptr);
547
548 std::cout << visibleDesc << std::endl
549 << "Report bugs at <https://github.com/Icinga/icinga2>" << std::endl
550 << "Get support: <https://icinga.com/support/>" << std::endl
551 << "Documentation: <https://icinga.com/docs/>" << std::endl
552 << "Icinga home page: <https://icinga.com/>" << std::endl;
553 return EXIT_SUCCESS;
554 }
555 }
556
557 int rc = 1;
558
559 if (autocomplete) {
560 CLICommand::ShowCommands(argc, argv, &visibleDesc, &hiddenDesc,
561 &GlobalArgumentCompletion, true, autoindex);
562 rc = 0;
563 } else if (command) {
564 Logger::DisableTimestamp();
565 #ifndef _WIN32
566 if (command->GetImpersonationLevel() == ImpersonateRoot) {
567 if (getuid() != 0) {
568 Log(LogCritical, "cli", "This command must be run as root.");
569 return 0;
570 }
571 } else if (command && command->GetImpersonationLevel() == ImpersonateIcinga) {
572 String group = Configuration::RunAsGroup;
573 String user = Configuration::RunAsUser;
574
575 errno = 0;
576 struct group *gr = getgrnam(group.CStr());
577
578 if (!gr) {
579 if (errno == 0) {
580 Log(LogCritical, "cli")
581 << "Invalid group specified: " << group;
582 return EXIT_FAILURE;
583 } else {
584 Log(LogCritical, "cli")
585 << "getgrnam() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
586 return EXIT_FAILURE;
587 }
588 }
589
590 if (getgid() != gr->gr_gid) {
591 if (!vm.count("reload-internal") && setgroups(0, nullptr) < 0) {
592 Log(LogCritical, "cli")
593 << "setgroups() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
594 Log(LogCritical, "cli")
595 << "Please re-run this command as a privileged user or using the \"" << user << "\" account.";
596 return EXIT_FAILURE;
597 }
598
599 if (setgid(gr->gr_gid) < 0) {
600 Log(LogCritical, "cli")
601 << "setgid() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
602 return EXIT_FAILURE;
603 }
604 }
605
606 errno = 0;
607 struct passwd *pw = getpwnam(user.CStr());
608
609 if (!pw) {
610 if (errno == 0) {
611 Log(LogCritical, "cli")
612 << "Invalid user specified: " << user;
613 return EXIT_FAILURE;
614 } else {
615 Log(LogCritical, "cli")
616 << "getpwnam() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
617 return EXIT_FAILURE;
618 }
619 }
620
621 // also activate the additional groups the configured user is member of
622 if (getuid() != pw->pw_uid) {
623 if (!vm.count("reload-internal") && initgroups(user.CStr(), pw->pw_gid) < 0) {
624 Log(LogCritical, "cli")
625 << "initgroups() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
626 Log(LogCritical, "cli")
627 << "Please re-run this command as a privileged user or using the \"" << user << "\" account.";
628 return EXIT_FAILURE;
629 }
630
631 if (setuid(pw->pw_uid) < 0) {
632 Log(LogCritical, "cli")
633 << "setuid() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
634 Log(LogCritical, "cli")
635 << "Please re-run this command as a privileged user or using the \"" << user << "\" account.";
636 return EXIT_FAILURE;
637 }
638 }
639 }
640 #endif /* _WIN32 */
641
642 std::vector<std::string> args;
643 if (vm.count("arg"))
644 args = vm["arg"].as<std::vector<std::string> >();
645
646 if (static_cast<int>(args.size()) < command->GetMinArguments()) {
647 Log(LogCritical, "cli")
648 << "Too few arguments. Command needs at least " << command->GetMinArguments()
649 << " argument" << (command->GetMinArguments() != 1 ? "s" : "") << ".";
650 return EXIT_FAILURE;
651 }
652
653 if (command->GetMaxArguments() >= 0 && static_cast<int>(args.size()) > command->GetMaxArguments()) {
654 Log(LogCritical, "cli")
655 << "Too many arguments. At most " << command->GetMaxArguments()
656 << " argument" << (command->GetMaxArguments() != 1 ? "s" : "") << " may be specified.";
657 return EXIT_FAILURE;
658 }
659
660 rc = command->Run(vm, args);
661 }
662
663 return rc;
664 }
665
666 #ifdef _WIN32
SetupService(bool install,int argc,char ** argv)667 static int SetupService(bool install, int argc, char **argv)
668 {
669 SC_HANDLE schSCManager = OpenSCManager(nullptr, nullptr, SC_MANAGER_ALL_ACCESS);
670
671 if (!schSCManager) {
672 printf("OpenSCManager failed (%d)\n", GetLastError());
673 return 1;
674 }
675
676 TCHAR szPath[MAX_PATH];
677
678 if (!GetModuleFileName(nullptr, szPath, MAX_PATH)) {
679 printf("Cannot install service (%d)\n", GetLastError());
680 return 1;
681 }
682
683 String szArgs;
684 szArgs = Utility::EscapeShellArg(szPath) + " --scm";
685
686 std::string scmUser = "NT AUTHORITY\\NetworkService";
687 std::ifstream initf(Utility::GetIcingaDataPath() + "\\etc\\icinga2\\user");
688 if (initf.good()) {
689 std::getline(initf, scmUser);
690 }
691 initf.close();
692
693 for (int i = 0; i < argc; i++) {
694 if (!strcmp(argv[i], "--scm-user") && i + 1 < argc) {
695 scmUser = argv[i + 1];
696 i++;
697 } else
698 szArgs += " " + Utility::EscapeShellArg(argv[i]);
699 }
700
701 SC_HANDLE schService = OpenService(schSCManager, "icinga2", SERVICE_ALL_ACCESS);
702
703 if (schService) {
704 SERVICE_STATUS status;
705 ControlService(schService, SERVICE_CONTROL_STOP, &status);
706
707 double start = Utility::GetTime();
708 while (status.dwCurrentState != SERVICE_STOPPED) {
709 double end = Utility::GetTime();
710
711 if (end - start > 30) {
712 printf("Could not stop the service.\n");
713 break;
714 }
715
716 Utility::Sleep(5);
717
718 if (!QueryServiceStatus(schService, &status)) {
719 printf("QueryServiceStatus failed (%d)\n", GetLastError());
720 return 1;
721 }
722 }
723 } else if (install) {
724 schService = CreateService(
725 schSCManager,
726 "icinga2",
727 "Icinga 2",
728 SERVICE_ALL_ACCESS,
729 SERVICE_WIN32_OWN_PROCESS,
730 SERVICE_DEMAND_START,
731 SERVICE_ERROR_NORMAL,
732 szArgs.CStr(),
733 nullptr,
734 nullptr,
735 nullptr,
736 scmUser.c_str(),
737 nullptr);
738
739 if (!schService) {
740 printf("CreateService failed (%d)\n", GetLastError());
741 CloseServiceHandle(schSCManager);
742 return 1;
743 }
744 } else {
745 printf("Service isn't installed.\n");
746 CloseServiceHandle(schSCManager);
747 return 0;
748 }
749
750 if (!install) {
751 if (!DeleteService(schService)) {
752 printf("DeleteService failed (%d)\n", GetLastError());
753 CloseServiceHandle(schService);
754 CloseServiceHandle(schSCManager);
755 return 1;
756 }
757
758 printf("Service uninstalled successfully\n");
759 } else {
760 if (!ChangeServiceConfig(schService, SERVICE_NO_CHANGE, SERVICE_AUTO_START,
761 SERVICE_ERROR_NORMAL, szArgs.CStr(), nullptr, nullptr, nullptr, scmUser.c_str(), nullptr, nullptr)) {
762 printf("ChangeServiceConfig failed (%d)\n", GetLastError());
763 CloseServiceHandle(schService);
764 CloseServiceHandle(schSCManager);
765 return 1;
766 }
767
768 SERVICE_DESCRIPTION sdDescription = { "The Icinga 2 monitoring application" };
769 if(!ChangeServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, &sdDescription)) {
770 printf("ChangeServiceConfig2 failed (%d)\n", GetLastError());
771 CloseServiceHandle(schService);
772 CloseServiceHandle(schSCManager);
773 return 1;
774 }
775
776 if (!StartService(schService, 0, nullptr)) {
777 printf("StartService failed (%d)\n", GetLastError());
778 CloseServiceHandle(schService);
779 CloseServiceHandle(schSCManager);
780 return 1;
781 }
782
783 std::cout << "Service successfully installed for user '" << scmUser << "'\n";
784
785 String userFilePath = Utility::GetIcingaDataPath() + "\\etc\\icinga2\\user";
786
787 std::ofstream fuser(userFilePath.CStr(), std::ios::out | std::ios::trunc);
788 if (fuser)
789 fuser << scmUser;
790 else
791 std::cout << "Could not write user to " << userFilePath << "\n";
792 }
793
794 CloseServiceHandle(schService);
795 CloseServiceHandle(schSCManager);
796
797 return 0;
798 }
799
ReportSvcStatus(DWORD dwCurrentState,DWORD dwWin32ExitCode,DWORD dwWaitHint)800 static VOID ReportSvcStatus(DWORD dwCurrentState,
801 DWORD dwWin32ExitCode,
802 DWORD dwWaitHint)
803 {
804 static DWORD dwCheckPoint = 1;
805
806 l_SvcStatus.dwCurrentState = dwCurrentState;
807 l_SvcStatus.dwWin32ExitCode = dwWin32ExitCode;
808 l_SvcStatus.dwWaitHint = dwWaitHint;
809
810 if (dwCurrentState == SERVICE_START_PENDING)
811 l_SvcStatus.dwControlsAccepted = 0;
812 else
813 l_SvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
814
815 if ((dwCurrentState == SERVICE_RUNNING) ||
816 (dwCurrentState == SERVICE_STOPPED))
817 l_SvcStatus.dwCheckPoint = 0;
818 else
819 l_SvcStatus.dwCheckPoint = dwCheckPoint++;
820
821 SetServiceStatus(l_SvcStatusHandle, &l_SvcStatus);
822 }
823
ServiceControlHandler(DWORD dwCtrl)824 static VOID WINAPI ServiceControlHandler(DWORD dwCtrl)
825 {
826 if (dwCtrl == SERVICE_CONTROL_STOP) {
827 ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0);
828 TerminateJobObject(l_Job, 0);
829 }
830 }
831
ServiceMain(DWORD argc,LPSTR * argv)832 static VOID WINAPI ServiceMain(DWORD argc, LPSTR *argv)
833 {
834 l_SvcStatusHandle = RegisterServiceCtrlHandler(
835 "icinga2",
836 ServiceControlHandler);
837
838 l_SvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
839 l_SvcStatus.dwServiceSpecificExitCode = 0;
840
841 ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0);
842 l_Job = CreateJobObject(nullptr, nullptr);
843
844 for (;;) {
845 LPSTR arg = argv[0];
846 String args;
847 int uargc = Application::GetArgC();
848 char **uargv = Application::GetArgV();
849
850 args += Utility::EscapeShellArg(Application::GetExePath(uargv[0]));
851
852 for (int i = 2; i < uargc && uargv[i]; i++) {
853 if (args != "")
854 args += " ";
855
856 args += Utility::EscapeShellArg(uargv[i]);
857 }
858
859 STARTUPINFO si = { sizeof(si) };
860 PROCESS_INFORMATION pi;
861
862 char *uargs = strdup(args.CStr());
863
864 BOOL res = CreateProcess(nullptr, uargs, nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi);
865
866 free(uargs);
867
868 if (!res)
869 break;
870
871 CloseHandle(pi.hThread);
872
873 AssignProcessToJobObject(l_Job, pi.hProcess);
874
875 if (WaitForSingleObject(pi.hProcess, INFINITE) != WAIT_OBJECT_0)
876 break;
877
878 DWORD exitStatus;
879
880 if (!GetExitCodeProcess(pi.hProcess, &exitStatus))
881 break;
882
883 if (exitStatus != 7)
884 break;
885 }
886
887 TerminateJobObject(l_Job, 0);
888
889 CloseHandle(l_Job);
890
891 ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
892
893 Application::Exit(0);
894 }
895 #endif /* _WIN32 */
896
897 /**
898 * Entry point for the Icinga application.
899 *
900 * @params argc Number of command line arguments.
901 * @params argv Command line arguments.
902 * @returns The application's exit status.
903 */
main(int argc,char ** argv)904 int main(int argc, char **argv)
905 {
906 #ifndef _WIN32
907 String keepFDs = Utility::GetFromEnvironment("ICINGA2_KEEP_FDS");
908 if (keepFDs.IsEmpty()) {
909 #ifdef I2_DEBUG
910 Utility::CloseAllFDs({0, 1, 2}, [](int fd) {
911 std::cerr << "Closed FD " << fd << " which we inherited from our parent process." << std::endl;
912 });
913 #else /* I2_DEBUG */
914 Utility::CloseAllFDs({0, 1, 2});
915 #endif /* I2_DEBUG */
916 }
917 #endif /* _WIN32 */
918
919 /* must be called before using any other libbase functions */
920 Application::InitializeBase();
921
922 /* Set command-line arguments. */
923 Application::SetArgC(argc);
924 Application::SetArgV(argv);
925
926 #ifdef _WIN32
927 if (argc > 1 && strcmp(argv[1], "--scm-install") == 0) {
928 return SetupService(true, argc - 2, &argv[2]);
929 }
930
931 if (argc > 1 && strcmp(argv[1], "--scm-uninstall") == 0) {
932 return SetupService(false, argc - 2, &argv[2]);
933 }
934
935 if (argc > 1 && strcmp(argv[1], "--scm") == 0) {
936 SERVICE_TABLE_ENTRY dispatchTable[] = {
937 { "icinga2", ServiceMain },
938 { nullptr, nullptr }
939 };
940
941 StartServiceCtrlDispatcher(dispatchTable);
942 Application::Exit(EXIT_FAILURE);
943 }
944 #endif /* _WIN32 */
945
946 int rc = Main();
947
948 Application::Exit(rc);
949 }
950