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