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