]> granicus.if.org Git - icinga2/blob - icinga-app/icinga.cpp
Move clicommand.{cpp,hpp} to lib/cli
[icinga2] / icinga-app / icinga.cpp
1 /******************************************************************************
2  * Icinga 2                                                                   *
3  * Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org)    *
4  *                                                                            *
5  * This program is free software; you can redistribute it and/or              *
6  * modify it under the terms of the GNU General Public License                *
7  * as published by the Free Software Foundation; either version 2             *
8  * of the License, or (at your option) any later version.                     *
9  *                                                                            *
10  * This program is distributed in the hope that it will be useful,            *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of             *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              *
13  * GNU General Public License for more details.                               *
14  *                                                                            *
15  * You should have received a copy of the GNU General Public License          *
16  * along with this program; if not, write to the Free Software Foundation     *
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.             *
18  ******************************************************************************/
19
20 #include "cli/clicommand.hpp"
21 #include "config/configcompilercontext.hpp"
22 #include "config/configcompiler.hpp"
23 #include "config/configitembuilder.hpp"
24 #include "base/application.hpp"
25 #include "base/logger.hpp"
26 #include "base/timer.hpp"
27 #include "base/utility.hpp"
28 #include "base/exception.hpp"
29 #include "base/convert.hpp"
30 #include "base/scriptvariable.hpp"
31 #include "base/context.hpp"
32 #include "base/console.hpp"
33 #include "config.h"
34 #include <boost/program_options.hpp>
35 #include <boost/tuple/tuple.hpp>
36 #include <boost/foreach.hpp>
37
38 #ifndef _WIN32
39 #       include <sys/types.h>
40 #       include <pwd.h>
41 #       include <grp.h>
42 #endif /* _WIN32 */
43
44 using namespace icinga;
45 namespace po = boost::program_options;
46
47 #ifdef _WIN32
48 SERVICE_STATUS l_SvcStatus;
49 SERVICE_STATUS_HANDLE l_SvcStatusHandle;
50 #endif /* _WIN32 */
51
52 static std::vector<String> GetLogLevelCompletionSuggestions(const String& arg)
53 {
54         std::vector<String> result;
55         
56         String debugLevel = "debug";
57         if (debugLevel.Find(arg) == 0)
58                 result.push_back(debugLevel);
59
60         String noticeLevel = "notice";
61         if (noticeLevel.Find(arg) == 0)
62                 result.push_back(noticeLevel);
63
64         String informationLevel = "information";
65         if (informationLevel.Find(arg) == 0)
66                 result.push_back(informationLevel);
67
68         String warningLevel = "warning";
69         if (warningLevel.Find(arg) == 0)
70                 result.push_back(warningLevel);
71
72         String criticalLevel = "critical";
73         if (criticalLevel.Find(arg) == 0)
74                 result.push_back(criticalLevel);
75
76         return result;
77 }
78
79 static std::vector<String> GlobalArgumentCompletion(const String& argument, const String& word)
80 {
81         if (argument == "include")
82                 return GetBashCompletionSuggestions("directory", word);
83         else if (argument == "log-level")
84                 return GetLogLevelCompletionSuggestions(word);
85         else
86                 return std::vector<String>();
87 }
88
89 int Main(void)
90 {
91         int argc = Application::GetArgC();
92         char **argv = Application::GetArgV();
93
94         bool autocomplete = false;
95         int autoindex = 0;
96
97         if (argc >= 4 && strcmp(argv[1], "--autocomplete") == 0) {
98                 autocomplete = true;
99                 autoindex = Convert::ToLong(argv[2]);
100                 argc -= 3;
101                 argv += 3;
102         }
103
104         Application::SetStartTime(Utility::GetTime());
105
106         if (!autocomplete)
107                 Application::SetResourceLimits();
108
109         /* Set thread title. */
110         Utility::SetThreadName("Main Thread", false);
111
112         /* Install exception handlers to make debugging easier. */
113         Application::InstallExceptionHandlers();
114
115 #ifdef _WIN32
116         bool builtinPaths = true;
117
118         HKEY hKey;
119         if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Icinga Development Team\\ICINGA2", 0,
120             KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS) {
121                 BYTE pvData[MAX_PATH];
122                 DWORD cbData = sizeof(pvData)-1;
123                 DWORD lType;
124                 if (RegQueryValueEx(hKey, NULL, NULL, &lType, pvData, &cbData) == ERROR_SUCCESS && lType == REG_SZ) {
125                         pvData[cbData] = '\0';
126
127                         String prefix = (char *)pvData;
128                         Application::DeclarePrefixDir(prefix);
129                         Application::DeclareSysconfDir(prefix + "\\etc");
130                         Application::DeclareRunDir(prefix + "\\var\\run");
131                         Application::DeclareLocalStateDir(prefix + "\\var");
132                         Application::DeclarePkgDataDir(prefix + "\\share\\icinga2");
133                         Application::DeclareIncludeConfDir(prefix + "\\share\\icinga2\\include");
134
135                         builtinPaths = false;
136                 }
137
138                 RegCloseKey(hKey);
139         }
140
141         if (builtinPaths) {
142                 Log(LogWarning, "icinga-app", "Registry key could not be read. Falling back to built-in paths.");
143
144 #endif /* _WIN32 */
145                 Application::DeclarePrefixDir(ICINGA_PREFIX);
146                 Application::DeclareSysconfDir(ICINGA_SYSCONFDIR);
147                 Application::DeclareRunDir(ICINGA_RUNDIR);
148                 Application::DeclareLocalStateDir(ICINGA_LOCALSTATEDIR);
149                 Application::DeclarePkgDataDir(ICINGA_PKGDATADIR);
150                 Application::DeclareIncludeConfDir(ICINGA_INCLUDECONFDIR);
151 #ifdef _WIN32
152         }
153 #endif /* _WIN32 */
154
155         Application::DeclareZonesDir(Application::GetSysconfDir() + "/icinga2/zones.d");
156         Application::DeclareApplicationType("icinga/IcingaApplication");
157         Application::DeclareRunAsUser(ICINGA_USER);
158         Application::DeclareRunAsGroup(ICINGA_GROUP);
159
160         LogSeverity logLevel = Logger::GetConsoleLogSeverity();
161         Logger::SetConsoleLogSeverity(LogWarning);
162
163         Utility::LoadExtensionLibrary("cli");
164
165         po::options_description visibleDesc("Global options");
166
167         visibleDesc.add_options()
168                 ("help", "show this help message")
169                 ("version,V", "show version information")
170 #ifndef _WIN32
171                 ("color", "use VT100 color codes even when stdout is not a terminal")
172 #endif /* _WIN32 */
173                 ("define,D", po::value<std::vector<std::string> >(), "define a constant")
174                 ("library,l", po::value<std::vector<std::string> >(), "load a library")
175                 ("include,I", po::value<std::vector<std::string> >(), "add include search directory")
176                 ("log-level,x", po::value<std::string>(), "specify the log level for the console log");
177
178         po::options_description hiddenDesc("Hidden options");
179
180         hiddenDesc.add_options()
181                 ("no-stack-rlimit", "used internally, do not specify manually")
182                 ("arg", po::value<std::vector<std::string> >(), "positional argument");
183
184         po::positional_options_description positionalDesc;
185         positionalDesc.add("arg", -1);
186
187         String cmdname;
188         CLICommand::Ptr command;
189         po::variables_map vm;
190
191         try {
192                 CLICommand::ParseCommand(argc, argv, visibleDesc, hiddenDesc, positionalDesc,
193                     vm, cmdname, command, autocomplete);
194         } catch (const std::exception& ex) {
195                 Log(LogCritical, "icinga-app")
196                     << "Error while parsing command-line options: " << ex.what();
197                 return EXIT_FAILURE;
198         }
199
200         String initconfig = Application::GetSysconfDir() + "/icinga2/init.conf";
201
202         if (Utility::PathExists(initconfig)) {
203                 ConfigCompilerContext::GetInstance()->Reset();
204                 ConfigCompiler::CompileFile(initconfig);
205         }
206
207 #ifndef _WIN32
208         if (vm.count("color")) {
209                 Console::SetType(std::cout, Console_VT100);
210                 Console::SetType(std::cerr, Console_VT100);
211         }
212 #endif /* _WIN32 */
213
214         if (vm.count("define")) {
215                 BOOST_FOREACH(const String& define, vm["define"].as<std::vector<std::string> >()) {
216                         String key, value;
217                         size_t pos = define.FindFirstOf('=');
218                         if (pos != String::NPos) {
219                                 key = define.SubStr(0, pos);
220                                 value = define.SubStr(pos + 1);
221                         } else {
222                                 key = define;
223                                 value = "1";
224                         }
225                         ScriptVariable::Set(key, value);
226                 }
227         }
228
229         Application::DeclareStatePath(Application::GetLocalStateDir() + "/lib/icinga2/icinga2.state");
230         Application::DeclareObjectsPath(Application::GetLocalStateDir() + "/cache/icinga2/icinga2.debug");
231         Application::DeclarePidPath(Application::GetRunDir() + "/icinga2/icinga2.pid");
232
233         ConfigCompiler::AddIncludeSearchDir(Application::GetIncludeConfDir());
234
235         if (!autocomplete && vm.count("include")) {
236                 BOOST_FOREACH(const String& includePath, vm["include"].as<std::vector<std::string> >()) {
237                         ConfigCompiler::AddIncludeSearchDir(includePath);
238                 }
239         }
240
241         Logger::SetConsoleLogSeverity(logLevel);
242
243         if (!autocomplete) {
244                 if (vm.count("log-level")) {
245                         String severity = vm["log-level"].as<std::string>();
246
247                         LogSeverity logLevel = LogInformation;
248                         try {
249                                 logLevel = Logger::StringToSeverity(severity);
250                         } catch (std::exception&) {
251                                 /* use the default */
252                                 Log(LogWarning, "icinga", "Invalid log level set. Using default 'information'.");
253                         }
254
255                         Logger::SetConsoleLogSeverity(logLevel);
256                 }
257
258                 if (vm.count("library")) {
259                         BOOST_FOREACH(const String& libraryName, vm["library"].as<std::vector<std::string> >()) {
260                                 (void)Utility::LoadExtensionLibrary(libraryName);
261                         }
262                 }
263
264                 if (!command || vm.count("help") || vm.count("version")) {
265                         String appName = Utility::BaseName(Application::GetArgV()[0]);
266
267                         if (appName.GetLength() > 3 && appName.SubStr(0, 3) == "lt-")
268                                 appName = appName.SubStr(3, appName.GetLength() - 3);
269
270                         std::cout << appName << " " << "- The Icinga 2 network monitoring daemon.";
271
272                         if (!command || vm.count("help")) {
273                                 std::cout << std::endl << std::endl
274                                     << "Usage:" << std::endl
275                                     << "  " << argv[0] << " ";
276
277                                 if (cmdname.IsEmpty())
278                                         std::cout << "<command>";
279                                 else
280                                         std::cout << cmdname;
281
282                                 std::cout << " [<arguments>]";
283
284                                 if (command) {
285                                         std::cout << std::endl << std::endl
286                                                   << command->GetDescription();
287                                 }
288                         }
289
290                         if (vm.count("version")) {
291                                 std::cout << " (Version: " << Application::GetVersion() << ")";
292                                 std::cout << std::endl
293                                         << "Copyright (c) 2012-2014 Icinga Development Team (http://www.icinga.org)" << std::endl
294                                         << "License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl2.html>" << std::endl
295                                         << "This is free software: you are free to change and redistribute it." << std::endl
296                                         << "There is NO WARRANTY, to the extent permitted by law.";
297                         }
298
299                         std::cout << std::endl;
300
301                         if (vm.count("version")) {
302                                 std::cout << std::endl;
303
304                                 Application::DisplayInfoMessage(true);
305
306                                 return EXIT_SUCCESS;
307                         }
308                 }
309
310                 if (!command || vm.count("help")) {
311                         if (!command) {
312                                 std::cout << std::endl;
313                                 CLICommand::ShowCommands(argc, argv, NULL);
314                         }
315
316                         std::cout << std::endl
317                                 << visibleDesc << std::endl
318                                 << "Report bugs at <https://dev.icinga.org/>" << std::endl
319                                 << "Icinga home page: <http://www.icinga.org/>" << std::endl;
320                         return EXIT_SUCCESS;
321                 }
322         }
323
324         int rc = 1;
325
326         if (autocomplete) {
327                 CLICommand::ShowCommands(argc, argv, &visibleDesc, &hiddenDesc,
328                     &GlobalArgumentCompletion, true, autoindex);
329                 rc = 0;
330         } else if (command) {
331 #ifndef _WIN32
332                 String group = Application::GetRunAsGroup();
333         
334                 errno = 0;
335                 struct group *gr = getgrnam(group.CStr());
336         
337                 if (!gr) {
338                         if (errno == 0) {
339                                 Log(LogCritical, "cli")
340                                     << "Invalid group specified: " << group;
341                                 return EXIT_FAILURE;
342                         } else {
343                                 Log(LogCritical, "cli")
344                                     << "getgrnam() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
345                                 return EXIT_FAILURE;
346                         }
347                 }
348         
349                 if (getgid() != gr->gr_gid) {
350                         if (!vm.count("reload-internal") && setgroups(0, NULL) < 0) {
351                                 Log(LogCritical, "cli")
352                                     << "setgroups() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
353                                 return EXIT_FAILURE;
354                         }
355         
356                         if (setgid(gr->gr_gid) < 0) {
357                                 Log(LogCritical, "cli")
358                                     << "setgid() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
359                                 return EXIT_FAILURE;
360                         }
361                 }
362         
363                 String user = Application::GetRunAsUser();
364         
365                 errno = 0;
366                 struct passwd *pw = getpwnam(user.CStr());
367         
368                 if (!pw) {
369                         if (errno == 0) {
370                                 Log(LogCritical, "cli")
371                                     << "Invalid user specified: " << user;
372                                 return EXIT_FAILURE;
373                         } else {
374                                 Log(LogCritical, "cli")
375                                     << "getpwnam() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
376                                 return EXIT_FAILURE;
377                         }
378                 }
379         
380                 // also activate the additional groups the configured user is member of
381                 if (getuid() != pw->pw_uid) {
382                         if (!vm.count("reload-internal") && initgroups(user.CStr(), pw->pw_gid) < 0) {
383                                 Log(LogCritical, "cli")
384                                     << "initgroups() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
385                                 return EXIT_FAILURE;
386                         }
387         
388                         if (setuid(pw->pw_uid) < 0) {
389                                 Log(LogCritical, "cli")
390                                     << "setuid() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
391                                 return EXIT_FAILURE;
392                         }
393                 }
394 #endif /* _WIN32 */
395
396                 std::vector<std::string> args;
397                 if (vm.count("arg"))
398                         args = vm["arg"].as<std::vector<std::string> >();
399
400                 rc = command->Run(vm, args);
401         }
402
403 #ifndef _DEBUG
404         Application::Exit(rc);
405 #endif /* _DEBUG */
406
407         return rc;
408 }
409
410 #ifdef _WIN32
411 static int SetupService(bool install, int argc, char **argv)
412 {
413         SC_HANDLE schSCManager = OpenSCManager(
414                 NULL,
415                 NULL,
416                 SC_MANAGER_ALL_ACCESS);
417
418         if (NULL == schSCManager) {
419                 printf("OpenSCManager failed (%d)\n", GetLastError());
420                 return 1;
421         }
422
423         TCHAR szPath[MAX_PATH];
424
425         if (!GetModuleFileName(NULL, szPath, MAX_PATH)) {
426                 printf("Cannot install service (%d)\n", GetLastError());
427                 return 1;
428         }
429
430         String szArgs;
431         szArgs = Utility::EscapeShellArg(szPath) + " --scm";
432
433         for (int i = 0; i < argc; i++)
434                 szArgs += " " + Utility::EscapeShellArg(argv[i]);
435
436         SC_HANDLE schService = OpenService(schSCManager, "icinga2", DELETE | SERVICE_STOP | SERVICE_QUERY_STATUS);
437
438         if (schService != NULL) {
439                 SERVICE_STATUS status;
440                 ControlService(schService, SERVICE_CONTROL_STOP, &status);
441
442                 double start = Utility::GetTime();
443                 while (status.dwCurrentState != SERVICE_STOPPED) {
444                         double end = Utility::GetTime();
445
446                         if (end - start > 30) {
447                                 printf("Could not stop the service.\n");
448                                 break;
449                         }
450
451                         Utility::Sleep(5);
452
453                         if (!QueryServiceStatus(schService, &status)) {
454                                 printf("QueryServiceStatus failed (%d)\n", GetLastError());
455                                 return 1;
456                         }
457                 }
458
459                 if (!DeleteService(schService)) {
460                         printf("DeleteService failed (%d)\n", GetLastError());
461                         CloseServiceHandle(schService);
462                         CloseServiceHandle(schSCManager);
463                         return 1;
464                 }
465
466                 if (!install)
467                         printf("Service uninstalled successfully\n");
468
469                 CloseServiceHandle(schService);
470         }
471
472         if (install) {
473                 schService = CreateService(
474                         schSCManager,
475                         "icinga2",
476                         "Icinga 2",
477                         SERVICE_ALL_ACCESS,
478                         SERVICE_WIN32_OWN_PROCESS,
479                         SERVICE_DEMAND_START,
480                         SERVICE_ERROR_NORMAL,
481                         szArgs.CStr(),
482                         NULL,
483                         NULL,
484                         NULL,
485                         "NT AUTHORITY\\NetworkService",
486                         NULL);
487
488                 if (schService == NULL) {
489                         printf("CreateService failed (%d)\n", GetLastError());
490                         CloseServiceHandle(schSCManager);
491                         return 1;
492                 } else
493                         printf("Service installed successfully\n");
494
495                 ChangeServiceConfig(schService, SERVICE_NO_CHANGE, SERVICE_AUTO_START,
496                     SERVICE_ERROR_NORMAL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
497
498                 SERVICE_DESCRIPTION sdDescription = { "The Icinga 2 monitoring application" };
499                 ChangeServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, &sdDescription);
500
501                 if (!StartService(schService, 0, NULL)) {
502                         printf("StartService failed (%d)\n", GetLastError());
503                         CloseServiceHandle(schService);
504                         CloseServiceHandle(schSCManager);
505                         return 1;
506                 }
507
508                 CloseServiceHandle(schService);
509         }
510
511         CloseServiceHandle(schSCManager);
512
513         return 0;
514 }
515
516 VOID ReportSvcStatus(DWORD dwCurrentState,
517         DWORD dwWin32ExitCode,
518         DWORD dwWaitHint)
519 {
520         static DWORD dwCheckPoint = 1;
521
522         l_SvcStatus.dwCurrentState = dwCurrentState;
523         l_SvcStatus.dwWin32ExitCode = dwWin32ExitCode;
524         l_SvcStatus.dwWaitHint = dwWaitHint;
525
526         if (dwCurrentState == SERVICE_START_PENDING)
527                 l_SvcStatus.dwControlsAccepted = 0;
528         else
529                 l_SvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
530
531         if ((dwCurrentState == SERVICE_RUNNING) ||
532             (dwCurrentState == SERVICE_STOPPED))
533                 l_SvcStatus.dwCheckPoint = 0;
534         else
535                 l_SvcStatus.dwCheckPoint = dwCheckPoint++;
536
537         SetServiceStatus(l_SvcStatusHandle, &l_SvcStatus);
538 }
539
540 VOID WINAPI ServiceControlHandler(DWORD dwCtrl)
541 {
542         if (dwCtrl == SERVICE_CONTROL_STOP) {
543                 ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0);
544                 Application::RequestShutdown();
545         }
546 }
547
548 VOID WINAPI ServiceMain(DWORD argc, LPSTR *argv)
549 {
550         l_SvcStatusHandle = RegisterServiceCtrlHandler(
551                 "icinga2",
552                 ServiceControlHandler);
553
554         l_SvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
555         l_SvcStatus.dwServiceSpecificExitCode = 0;
556
557         ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0);
558
559         int rc = Main();
560
561         ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, rc);
562 }
563 #endif /* _WIN32 */
564
565 /**
566 * Entry point for the Icinga application.
567 *
568 * @params argc Number of command line arguments.
569 * @params argv Command line arguments.
570 * @returns The application's exit status.
571 */
572 int main(int argc, char **argv)
573 {
574         /* must be called before using any other libbase functions */
575         Application::InitializeBase();
576
577         /* Set command-line arguments. */
578         Application::SetArgC(argc);
579         Application::SetArgV(argv);
580
581 #ifdef _WIN32
582         if (argc > 1 && strcmp(argv[1], "--scm-install") == 0) {
583                 return SetupService(true, argc - 2, &argv[2]);
584         }
585
586         if (argc > 1 && strcmp(argv[1], "--scm-uninstall") == 0) {
587                 return SetupService(false, argc - 2, &argv[2]);
588         }
589
590         if (argc > 1 && strcmp(argv[1], "--scm") == 0) {
591                 SERVICE_TABLE_ENTRY dispatchTable[] = {
592                         { "icinga2", ServiceMain },
593                         { NULL, NULL }
594                 };
595
596                 StartServiceCtrlDispatcher(dispatchTable);
597                 Application::Exit(1);
598         }
599 #endif /* _WIN32 */
600
601         int rc = Main();
602
603         Application::Exit(rc);
604 }