]> granicus.if.org Git - icinga2/blob - icinga-app/icinga.cpp
Run the Windows Service as NT AUTHORITY\NetworkService.
[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 "config/configcompilercontext.h"
21 #include "config/configcompiler.h"
22 #include "config/configitembuilder.h"
23 #include "base/application.h"
24 #include "base/logger.h"
25 #include "base/timer.h"
26 #include "base/utility.h"
27 #include "base/exception.h"
28 #include "base/convert.h"
29 #include "base/scriptvariable.h"
30 #include "base/context.h"
31 #include "config.h"
32 #include <boost/program_options.hpp>
33 #include <boost/tuple/tuple.hpp>
34 #include <boost/foreach.hpp>
35
36 #ifndef _WIN32
37 #       include <sys/types.h>
38 #       include <pwd.h>
39 #       include <grp.h>
40 #endif /* _WIN32 */
41
42 using namespace icinga;
43 namespace po = boost::program_options;
44
45 static po::variables_map g_AppParams;
46
47 #ifdef _WIN32
48 SERVICE_STATUS l_SvcStatus;
49 SERVICE_STATUS_HANDLE l_SvcStatusHandle;
50 #endif /* _WIN32 */
51
52 static String LoadAppType(const String& typeSpec)
53 {
54         int index;
55
56         Log(LogInformation, "icinga-app", "Loading application type: " + typeSpec);
57
58         index = typeSpec.FindFirstOf('/');
59
60         if (index == String::NPos)
61                 return typeSpec;
62
63         String library = typeSpec.SubStr(0, index);
64
65         (void) Utility::LoadExtensionLibrary(library);
66
67         return typeSpec.SubStr(index + 1);
68 }
69
70 static bool LoadConfigFiles(const String& appType, ValidationType validate)
71 {
72         ConfigCompilerContext::GetInstance()->Reset();
73
74         if (g_AppParams.count("config") > 0) {
75                 BOOST_FOREACH(const String& configPath, g_AppParams["config"].as<std::vector<std::string> >()) {
76                         ConfigCompiler::CompileFile(configPath);
77                 }
78         }
79
80         String name, fragment;
81         BOOST_FOREACH(boost::tie(name, fragment), ConfigFragmentRegistry::GetInstance()->GetItems()) {
82                 ConfigCompiler::CompileText(name, fragment);
83         }
84
85         ConfigItemBuilder::Ptr builder = make_shared<ConfigItemBuilder>();
86         builder->SetType(appType);
87         builder->SetName("application");
88         ConfigItem::Ptr item = builder->Compile();
89         item->Register();
90
91         bool result = ConfigItem::ActivateItems(validate);
92
93         int warnings = 0, errors = 0;
94
95         BOOST_FOREACH(const ConfigCompilerMessage& message, ConfigCompilerContext::GetInstance()->GetMessages()) {
96                 std::ostringstream locbuf;
97                 ShowCodeFragment(locbuf, message.Location, true);
98                 String location = locbuf.str();
99
100                 String logmsg;
101
102                 if (!location.IsEmpty())
103                         logmsg = "Location:\n" + location;
104
105                 logmsg += String("\nConfig ") + (message.Error ? "error" : "warning") + ": " + message.Text;
106
107                 if (message.Error) {
108                         Log(LogCritical, "config", logmsg);
109                         errors++;
110                 } else {
111                         Log(LogWarning, "config", logmsg);
112                         warnings++;
113                 }
114         }
115
116         if (warnings > 0 || errors > 0) {
117                 LogSeverity severity;
118
119                 if (errors == 0)
120                         severity = LogWarning;
121                 else
122                         severity = LogCritical;
123
124                 Log(severity, "config", Convert::ToString(errors) + " errors, " + Convert::ToString(warnings) + " warnings.");
125         }
126
127         if (!result)
128                 return false;
129
130         return true;
131 }
132
133 #ifndef _WIN32
134 static void SigHupHandler(int)
135 {
136         Application::RequestRestart();
137 }
138 #endif /* _WIN32 */
139
140 static bool Daemonize(const String& stderrFile)
141 {
142 #ifndef _WIN32
143         pid_t pid = fork();
144         if (pid == -1) {
145                 return false;
146         }
147
148         if (pid)
149                 exit(0);
150
151         int fdnull = open("/dev/null", O_RDWR);
152         if (fdnull > 0) {
153                 if (fdnull != 0)
154                         dup2(fdnull, 0);
155
156                 if (fdnull != 1)
157                         dup2(fdnull, 1);
158
159                 if (fdnull > 2)
160                         close(fdnull);
161         }
162
163         const char *errPath = "/dev/null";
164
165         if (!stderrFile.IsEmpty())
166                 errPath = stderrFile.CStr();
167
168         int fderr = open(errPath, O_WRONLY | O_APPEND);
169
170         if (fderr < 0 && errno == ENOENT)
171                 fderr = open(errPath, O_CREAT | O_WRONLY | O_APPEND, 0600);
172
173         if (fderr > 0) {
174                 if (fderr != 2)
175                         dup2(fderr, 2);
176
177                 if (fderr > 2)
178                         close(fderr);
179         }
180
181         pid_t sid = setsid();
182         if (sid == -1) {
183                 return false;
184         }
185 #endif
186
187         return true;
188 }
189
190 int Main(void)
191 {
192         int argc = Application::GetArgC();
193         char **argv = Application::GetArgV();
194
195         Application::SetStartTime(Utility::GetTime());
196
197         Application::SetResourceLimits();
198
199         /* Set thread title. */
200         Utility::SetThreadName("Main Thread", false);
201
202         /* Install exception handlers to make debugging easier. */
203         Application::InstallExceptionHandlers();
204
205         Application::DeclarePrefixDir(ICINGA_PREFIX);
206         Application::DeclareSysconfDir(ICINGA_SYSCONFDIR);
207         Application::DeclareLocalStateDir(ICINGA_LOCALSTATEDIR);
208         Application::DeclarePkgDataDir(ICINGA_PKGDATADIR);
209
210         Application::DeclareApplicationType("icinga/IcingaApplication");
211
212         po::options_description desc("Supported options");
213         desc.add_options()
214                 ("help", "show this help message")
215                 ("version,V", "show version information")
216                 ("library,l", po::value<std::vector<std::string> >(), "load a library")
217                 ("include,I", po::value<std::vector<std::string> >(), "add include search directory")
218                 ("define,D", po::value<std::vector<std::string> >(), "define a constant")
219                 ("config,c", po::value<std::vector<std::string> >(), "parse a configuration file")
220                 ("no-config,z", "start without a configuration file")
221                 ("validate,C", "exit after validating the configuration")
222                 ("no-validate,Z", "skip validating the configuration")
223                 ("debug,x", "enable debugging")
224                 ("errorlog,e", po::value<std::string>(), "log fatal errors to the specified log file (only works in combination with --daemonize)")
225 #ifndef _WIN32
226                 ("daemonize,d", "detach from the controlling terminal")
227                 ("user,u", po::value<std::string>(), "user to run Icinga as")
228                 ("group,g", po::value<std::string>(), "group to run Icinga as")
229 #else /* _WIN32 */
230                 ("scm", "run as a Windows service (must be the first argument if specified)")
231                 ("scm-install", "installs Icinga 2 as a Windows service (must be the first argument if specified")
232                 ("scm-uninstall", "uninstalls the Icinga 2 Windows service (must be the first argument if specified")
233 #endif /* _WIN32 */
234         ;
235
236         try {
237                 po::store(po::parse_command_line(argc, argv, desc), g_AppParams);
238         } catch (const std::exception& ex) {
239                 std::ostringstream msgbuf;
240                 msgbuf << "Error while parsing command-line options: " << ex.what();
241                 Log(LogCritical, "icinga-app", msgbuf.str());
242                 return EXIT_FAILURE;
243         }
244
245         po::notify(g_AppParams);
246
247         if (g_AppParams.count("define")) {
248                 BOOST_FOREACH(const String& define, g_AppParams["define"].as<std::vector<std::string> >()) {
249                         String key, value;
250                         size_t pos = define.FindFirstOf('=');
251                         if (pos != String::NPos) {
252                                 key = define.SubStr(0, pos);
253                                 value = define.SubStr(pos + 1);
254                         } else {
255                                 key = define;
256                                 value = "1";
257                         }
258                         ScriptVariable::Set(key, value);
259                 }
260         }
261
262         Application::DeclareStatePath(Application::GetLocalStateDir() + "/lib/icinga2/icinga2.state");
263         Application::DeclarePidPath(Application::GetLocalStateDir() + "/run/icinga2/icinga2.pid");
264
265 #ifndef _WIN32
266         if (g_AppParams.count("group")) {
267                 String group = g_AppParams["group"].as<std::string>();
268
269                 errno = 0;
270                 struct group *gr = getgrnam(group.CStr());
271
272                 if (!gr) {
273                         if (errno == 0) {
274                                 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid group specified: " + group));
275                         } else {
276                                 BOOST_THROW_EXCEPTION(posix_error()
277                                         << boost::errinfo_api_function("getgrnam")
278                                         << boost::errinfo_errno(errno));
279                         }
280                 }
281
282                 if (setgid(gr->gr_gid) < 0) {
283                         BOOST_THROW_EXCEPTION(posix_error()
284                                 << boost::errinfo_api_function("setgid")
285                                 << boost::errinfo_errno(errno));
286                 }
287         }
288
289         if (g_AppParams.count("user")) {
290                 String user = g_AppParams["user"].as<std::string>();
291
292                 errno = 0;
293                 struct passwd *pw = getpwnam(user.CStr());
294
295                 if (!pw) {
296                         if (errno == 0) {
297                                 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid user specified: " + user));
298                         } else {
299                                 BOOST_THROW_EXCEPTION(posix_error()
300                                         << boost::errinfo_api_function("getpwnam")
301                                         << boost::errinfo_errno(errno));
302                         }
303                 }
304
305                 if (setuid(pw->pw_uid) < 0) {
306                         BOOST_THROW_EXCEPTION(posix_error()
307                                 << boost::errinfo_api_function("setuid")
308                                 << boost::errinfo_errno(errno));
309                 }
310         }
311 #endif /* _WIN32 */
312
313         if (g_AppParams.count("debug"))
314                 Application::SetDebugging(true);
315
316         if (g_AppParams.count("help") || g_AppParams.count("version")) {
317                 String appName = Utility::BaseName(argv[0]);
318
319                 if (appName.GetLength() > 3 && appName.SubStr(0, 3) == "lt-")
320                         appName = appName.SubStr(3, appName.GetLength() - 3);
321
322                 std::cout << appName << " " << "- The Icinga 2 network monitoring daemon.";
323
324                 if (g_AppParams.count("version")) {
325                         std::cout << " (Version: " << Application::GetVersion() << ")";
326                         std::cout << std::endl
327                                 << "Copyright (c) 2012-2014 Icinga Development Team (http://www.icinga.org)" << std::endl
328                                 << "License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl2.html>" << std::endl
329                                 << "This is free software: you are free to change and redistribute it." << std::endl
330                                 << "There is NO WARRANTY, to the extent permitted by law.";
331                 }
332
333                 std::cout << std::endl;
334
335                 if (g_AppParams.count("version"))
336                         return EXIT_SUCCESS;
337         }
338
339         if (g_AppParams.count("help")) {
340                 std::cout << std::endl
341                         << desc << std::endl
342                         << "Report bugs at <https://dev.icinga.org/>" << std::endl
343                         << "Icinga home page: <http://www.icinga.org/>" << std::endl;
344                 return EXIT_SUCCESS;
345         }
346
347         ScriptVariable::Set("UseVfork", true, false, true);
348
349         Application::MakeVariablesConstant();
350
351         Log(LogInformation, "icinga-app", "Icinga application loader (version: " + Application::GetVersion() + ")");
352
353         String appType = LoadAppType(Application::GetApplicationType());
354
355         if (g_AppParams.count("library")) {
356                 BOOST_FOREACH(const String& libraryName, g_AppParams["library"].as<std::vector<std::string> >()) {
357                         (void)Utility::LoadExtensionLibrary(libraryName);
358                 }
359         }
360
361         ConfigCompiler::AddIncludeSearchDir(Application::GetPkgDataDir());
362
363         if (g_AppParams.count("include")) {
364                 BOOST_FOREACH(const String& includePath, g_AppParams["include"].as<std::vector<std::string> >()) {
365                         ConfigCompiler::AddIncludeSearchDir(includePath);
366                 }
367         }
368
369         if (g_AppParams.count("no-config") == 0 && g_AppParams.count("config") == 0) {
370                 Log(LogCritical, "icinga-app", "You need to specify at least one config file (using the --config option).");
371
372                 return EXIT_FAILURE;
373         }
374
375         if (g_AppParams.count("daemonize")) {
376                 String errorLog;
377
378                 if (g_AppParams.count("errorlog"))
379                         errorLog = g_AppParams["errorlog"].as<std::string>();
380
381                 Daemonize(errorLog);
382                 Logger::DisableConsoleLog();
383         }
384
385         ValidationType validate = ValidateStart;
386
387         if (g_AppParams.count("validate"))
388                 validate = ValidateOnly;
389
390         if (g_AppParams.count("no-validate"))
391                 validate = ValidateNone;
392
393         if (!LoadConfigFiles(appType, validate))
394                 return EXIT_FAILURE;
395
396         if (validate == ValidateOnly) {
397                 Log(LogInformation, "icinga-app", "Finished validating the configuration file(s).");
398                 return EXIT_SUCCESS;
399         }
400
401 #ifndef _WIN32
402         struct sigaction sa;
403         memset(&sa, 0, sizeof(sa));
404         sa.sa_handler = &SigHupHandler;
405         sigaction(SIGHUP, &sa, NULL);
406 #endif /* _WIN32 */
407
408         int rc = Application::GetInstance()->Run();
409
410 #ifndef _DEBUG
411         _exit(rc); // Yay, our static destructors are pretty much beyond repair at this point.
412 #endif /* _DEBUG */
413
414         return rc;
415 }
416
417 #ifdef _WIN32
418 static int SetupService(bool install, int argc, char **argv)
419 {
420         SC_HANDLE schSCManager;
421         SC_HANDLE schService;
422         TCHAR szPath[MAX_PATH * 5];
423
424         if (!GetModuleFileName(NULL, szPath, MAX_PATH)) {
425                 printf("Cannot install service (%d)\n", GetLastError());
426                 return 1;
427         }
428
429         schSCManager = OpenSCManager(
430                 NULL,
431                 NULL,
432                 SC_MANAGER_ALL_ACCESS);
433
434         if (NULL == schSCManager) {
435                 printf("OpenSCManager failed (%d)\n", GetLastError());
436                 return 1;
437         }
438
439         strcat(szPath, " --scm");
440
441         for (int i = 0; i < argc; i++) {
442                 strcat(szPath, " \"");
443                 strcat(szPath, argv[i]);
444                 strcat(szPath, "\"");
445         }
446
447         schService = OpenService(schSCManager, "icinga2", DELETE);
448
449         if (schService != NULL) {
450                 if (!DeleteService(schService)) {
451                         printf("DeleteService failed (%d)\n", GetLastError());
452                         CloseServiceHandle(schService);
453                         CloseServiceHandle(schSCManager);
454                         return 1;
455                 }
456
457                 if (!install)
458                         printf("Service uninstalled successfully\n");
459
460                 CloseServiceHandle(schService);
461         }
462
463         if (install) {
464                 schService = CreateService(
465                         schSCManager,
466                         "icinga2",
467                         "Icinga 2",
468                         SERVICE_ALL_ACCESS,
469                         SERVICE_WIN32_OWN_PROCESS,
470                         SERVICE_DEMAND_START,
471                         SERVICE_ERROR_NORMAL,
472                         szPath,
473                         NULL,
474                         NULL,
475                         NULL,
476                         "NT AUTHORITY\\NetworkService",
477                         NULL);
478
479                 if (schService == NULL) {
480                         printf("CreateService failed (%d)\n", GetLastError());
481                         CloseServiceHandle(schSCManager);
482                         return 1;
483                 } else
484                         printf("Service installed successfully\n");
485
486                 SERVICE_DESCRIPTION sdDescription = { "The Icinga 2 monitoring application" };
487                 ChangeServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, &sdDescription);
488
489                 CloseServiceHandle(schService);
490         }
491
492         CloseServiceHandle(schSCManager);
493
494         return 0;
495 }
496
497 VOID ReportSvcStatus(DWORD dwCurrentState,
498         DWORD dwWin32ExitCode,
499         DWORD dwWaitHint)
500 {
501         static DWORD dwCheckPoint = 1;
502
503         l_SvcStatus.dwCurrentState = dwCurrentState;
504         l_SvcStatus.dwWin32ExitCode = dwWin32ExitCode;
505         l_SvcStatus.dwWaitHint = dwWaitHint;
506
507         if (dwCurrentState == SERVICE_START_PENDING)
508                 l_SvcStatus.dwControlsAccepted = 0;
509         else
510                 l_SvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
511
512         if ((dwCurrentState == SERVICE_RUNNING) ||
513             (dwCurrentState == SERVICE_STOPPED))
514                 l_SvcStatus.dwCheckPoint = 0;
515         else
516                 l_SvcStatus.dwCheckPoint = dwCheckPoint++;
517
518         SetServiceStatus(l_SvcStatusHandle, &l_SvcStatus);
519 }
520
521 VOID WINAPI ServiceControlHandler(DWORD dwCtrl)
522 {
523         if (dwCtrl == SERVICE_CONTROL_STOP) {
524                 ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0);
525                 Application::RequestShutdown();
526         }
527 }
528
529 VOID WINAPI ServiceMain(DWORD argc, LPSTR *argv)
530 {
531         l_SvcStatusHandle = RegisterServiceCtrlHandler(
532                 "icinga2",
533                 ServiceControlHandler);
534
535         l_SvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
536         l_SvcStatus.dwServiceSpecificExitCode = 0;
537
538         ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0);
539
540         int rc = Main();
541
542         ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, rc);
543 }
544 #endif /* _WIN32 */
545
546 /**
547 * Entry point for the Icinga application.
548 *
549 * @params argc Number of command line arguments.
550 * @params argv Command line arguments.
551 * @returns The application's exit status.
552 */
553 int main(int argc, char **argv)
554 {
555         /* Set command-line arguments. */
556         Application::SetArgC(argc);
557         Application::SetArgV(argv);
558
559 #ifdef _WIN32
560         if (argc > 1 && strcmp(argv[1], "--scm-install") == 0) {
561                 return SetupService(true, argc - 2, &argv[2]);
562         }
563
564         if (argc > 1 && strcmp(argv[1], "--scm-uninstall") == 0) {
565                 return SetupService(false, argc - 2, &argv[2]);
566         }
567
568         if (argc > 1 && strcmp(argv[1], "--scm") == 0) {
569                 SERVICE_TABLE_ENTRY dispatchTable[] = {
570                         { "icinga2", ServiceMain },
571                         { NULL, NULL }
572                 };
573
574                 StartServiceCtrlDispatcher(dispatchTable);
575                 _exit(1);
576         }
577 #endif /* _WIN32 */
578
579         int rc = Main();
580         exit(rc);
581 }