source: nscp/service/cli_parser.hpp @ 682ccd2

0.4.00.4.10.4.2
Last change on this file since 682ccd2 was 682ccd2, checked in by Michael Medin <michael@…>, 14 months ago
  • Fixed crash in new logger
  • Fixed a few command line parsing issues (most notably test and --log-to-file)
  • Changed default location for process dumps to be a non-elevated location (under common-appdata).
  • Fixed (ish) check_nt issue (check_nt really really is broken... this will probably cause problems with people sending large payloads though)
  • Property mode set to 100644
File size: 20.1 KB
Line 
1#pragma once
2
3#include <boost/program_options.hpp>
4#include "settings_client.hpp"
5#include <nsclient/logger.hpp>
6
7namespace po = boost::program_options;
8class cli_parser {
9       
10
11        NSClient* core_;
12        po::options_description root;
13        po::options_description settings;
14        po::options_description service;
15        po::options_description client;
16        po::options_description common;
17        po::options_description unittest;
18        po::options_description test;
19
20        bool help;
21        bool version;
22        std::wstring log_level;
23        std::wstring settings_store;
24        bool log_debug;
25
26        static nsclient::logging::logger_interface* get_logger() {
27                return nsclient::logging::logger::get_logger();
28        }
29
30public:
31        cli_parser(NSClient* core)
32                : core_(core)
33                , root("Allowed first option (Mode of operation)")
34                , common("Common options")
35                , settings("Settings options")
36                , service("Service Options")
37                , client("Client Options")
38                , unittest("Unit-test Options")
39                , test("Test Options")
40                , help(false)
41                , version(false)
42                , log_debug(false)
43        {
44                root.add_options()
45                        ("help", po::bool_switch(&help), "produce help message")
46                        ("version", po::bool_switch(&version), "Show version information")
47                        ;
48
49                common.add_options()
50                        ("settings", po::value<std::wstring>(&settings_store), "Override (temporarily) settings subsystem to use")
51                        ("help", po::bool_switch(&help), "produce help message")
52                        ("debug", po::bool_switch(&log_debug), "Set log level to debug (and show debug information)")
53                        ("log", po::value<std::wstring>(&log_level), "The log level to use")
54                        ("version", po::bool_switch(&version), "Show version information")
55                        ;
56
57                settings.add_options()
58                        ("migrate-to", po::value<std::wstring>(), "Migrate (copy) settings from current store to target store")
59                        ("migrate-from", po::value<std::wstring>(), "Migrate (copy) settings from current store to target store")
60                        ("generate", po::value<std::wstring>(), "(re)Generate a commented settings store or similar KEY can be trac, settings or the target store.")
61                        ("add-defaults", "Add all default (if missing) values.")
62                        ("load-all", "Load all plugins (currently only used with generate).")
63                        ("path", po::value<std::wstring>()->default_value(_T("")), "Path of key to work with.")
64                        ("key", po::value<std::wstring>()->default_value(_T("")), "Key to work with.")
65                        ("set", po::value<std::wstring>()->implicit_value(_T("")), "Set a key and path to a given value.")
66                        ("switch", po::value<std::wstring>(), "Set default context to use (similar to migrate but does NOT copy values)")
67                        ("show", "Set a value given a key and path.")
68                        ("list", "Set all keys below the path (or root).")
69                        ;
70
71                service.add_options()
72                        ("install", "Install service")
73                        ("uninstall", "Uninstall service")
74                        ("start", "Start service")
75                        ("stop", "Stop service")
76                        ("info", "Show information about service")
77                        ("run", "Run as a service")
78                        ("name", po::value<std::wstring>(), "Name of service")
79                        ("description", po::value<std::wstring>()->default_value(_T("")), "Description of service")
80                        ;
81
82                client.add_options()
83                        ("exec,e", po::value<std::wstring>()->implicit_value(_T("")), "Run a command (execute)")
84                        ("boot,b", "Boot the client before executing command (similar as running the command from test)")
85                        ("query,q", po::value<std::wstring>(), "Run a query with a given name")
86                        ("submit,s", po::value<std::wstring>(), "Name of query to ask")
87                        ("module,M", po::value<std::wstring>(), "Name of module to load (if not specified all modules in ini file will be loaded)")
88                        ("argument,a", po::wvalue<std::vector<std::wstring> >(), "List of arguments (gets -- prefixed automatically)")
89                        ("raw-argument", po::wvalue<std::vector<std::wstring> >(), "List of arguments (does not get -- prefixed)")
90                        ;
91
92                unittest.add_options()
93                        ("language,l", po::value<std::wstring>()->implicit_value(_T("")), "Language tests are written in")
94                        ("argument,a", po::wvalue<std::vector<std::wstring> >(), "List of arguments (gets -- prefixed automatically)")
95                        ("raw-argument", po::wvalue<std::vector<std::wstring> >(), "List of arguments (does not get -- prefixed)")
96                        ;
97
98                test.add_options()
99                        ("log-to-file", "Enable file logger (defaults is console only)")
100                        ;
101
102        }
103
104        bool process_common_options(std::string context, po::options_description &desc) {
105                nsclient::logging::logger::get_logger()->set_console_log(true);
106                if (log_debug) {
107                        log_level = _T("debug");
108                }
109                if (!log_level.empty())
110                        nsclient::logging::logger::set_log_level(log_level);
111                if (!settings_store.empty())
112                        core_->set_settings_context(settings_store);
113
114                if (help) {
115                        std::cout << desc << std::endl;
116                        return true;
117                }
118                if (version) {
119                        std::cout << APPLICATION_NAME << _T(", Version: ") << CURRENT_SERVICE_VERSION << _T(", Platform: ") << SZARCH << std::endl;
120                        return true;
121                }
122                return false;
123        }
124
125        typedef boost::function<int(int, wchar_t**)> handler_function;
126        typedef std::map<std::string,handler_function> handler_map;
127        typedef std::map<std::string,std::string> alias_map;
128
129        handler_map get_handlers() {
130                handler_map handlers;
131                handlers["settings"] = boost::bind(&cli_parser::parse_settings, this, _1, _2);
132                handlers["service"] = boost::bind(&cli_parser::parse_service, this, _1, _2);
133                handlers["client"] = boost::bind(&cli_parser::parse_client, this, _1, _2, _T(""));
134                handlers["test"] = boost::bind(&cli_parser::parse_test, this, _1, _2);
135                handlers["help"] = boost::bind(&cli_parser::parse_help, this, _1, _2);
136                handlers["unit"] = boost::bind(&cli_parser::parse_unittest, this, _1, _2);
137                return handlers;
138        }
139
140        alias_map get_aliases() {
141                alias_map aliases;
142                aliases["nrpe"] = "NRPEClient";
143                aliases["nscp"] = "NSCPClient";
144                aliases["nsca"] = "NSCAClient";
145                aliases["eventlog"] = "CheckEventLog";
146                aliases["python"] = "PythonScript";
147                aliases["py"] = "PythonScript";
148                aliases["lua"] = "LuaScript";
149                aliases["syslog"] = "SyslogClient";
150                aliases["sys"] = "CheckSystem";
151                aliases["wmi"] = "CheckWMI";
152                return aliases;
153        }
154
155        int parse(int argc, wchar_t* argv[]) {
156
157                if (argc > 1 && argv[1][0] != L'-') {
158                        handler_map handlers = get_handlers();
159                        alias_map aliases = get_aliases();
160
161                        std::string mod = utf8::cvt<std::string>(argv[1]);
162                        handler_map::const_iterator it = handlers.find(mod);
163                        if (it != handlers.end())
164                                return it->second(argc-1, &argv[1]);
165
166                        alias_map::const_iterator alias_it = aliases.find(mod);
167                        if (alias_it != aliases.end())
168                                return parse_client(argc-1, &argv[1], utf8::cvt<std::wstring>(alias_it->second));
169
170                        parse_help(argc, argv);
171                        std::cerr << "Invalid module specified: " << mod << std::endl;
172                        return 1;
173                }
174                return parse_help(argc, argv);
175        }
176        int parse_help(int argc, wchar_t* argv[]) {
177                try {
178
179                        po::options_description all("Allowed options");
180                        all.add(root).add(common).add(service).add(settings).add(client).add(test).add(unittest);
181                        std::cout << all << std::endl;
182
183                        std::cerr << "First argument has to be one of the following: ";
184                        handler_map handlers = get_handlers();
185                        BOOST_FOREACH(const handler_map::value_type &itm, handlers) {
186                                std::cerr << itm.first << ", ";
187                        }
188                        std::cerr << std::endl;
189                        std::cerr << "Or on of the following client aliases: ";
190                        alias_map aliases = get_aliases();
191                        BOOST_FOREACH(const alias_map::value_type &itm, aliases) {
192                                std::cerr << itm.first << ", ";
193                        }
194                        std::cerr << std::endl;
195                        return 1;
196                } catch(std::exception & e) {
197                        std::cerr << "Unable to parse root option: " << e.what() << std::endl;
198                        return 1;
199                } catch (...) {
200                        std::cerr << "Unable to parse root option" << std::endl;
201                        return 1;
202                }
203        }
204
205        int parse_test(int argc, wchar_t* argv[]) {
206                try {
207
208                        po::options_description all("Allowed options (test)");
209                        all.add(common).add(test);
210
211                        po::variables_map vm;
212                        po::store(po::parse_command_line(argc, argv, all), vm);
213                        po::notify(vm);
214
215                        if (log_level.empty())
216                                log_level  = _T("debug");
217
218                        if (process_common_options("test", all))
219                                return 1;
220
221                        if (vm.count("log-to-file") == 0) {
222                                nsclient::logging::logger::set_backend("console");
223                        }
224
225                        nsclient::simple_client client(core_);
226                        client.start(log_level);
227                        return 0;
228                } catch(std::exception & e) {
229                        get_logger()->error(__FILE__, __LINE__, std::wstring(_T("Unable to parse command line (settings): ")) + utf8::to_unicode(e.what()));
230                        return 1;
231                }
232        }
233
234        int parse_settings(int argc, wchar_t* argv[]) {
235                try {
236                        po::options_description all("Allowed options (settings)");
237                        all.add(common).add(settings);
238
239                        po::variables_map vm;
240                        po::store(po::parse_command_line(argc, argv, all), vm);
241                        po::notify(vm);
242
243                        if (process_common_options("settings", all))
244                                return 1;
245
246                        bool def = vm.count("add-defaults")==1;
247                        bool load_all = vm.count("load-all")==1;
248
249                        nsclient::settings_client client(core_);
250
251                        std::wstring current = _T(""); //client.get_source();
252
253
254                        client.set_current(current);
255                        client.set_update_defaults(def);
256                        client.set_load_all_files(load_all);
257
258                        client.boot(log_level);
259                        int ret = -1;
260
261                        if (vm.count("generate")) {
262                                ret = client.generate(vm["generate"].as<std::wstring>());
263                        } else if (vm.count("migrate-to")) {
264                                ret = client.migrate_to(vm["migrate-to"].as<std::wstring>());
265                        } else if (vm.count("migrate-from")) {
266                                ret = client.migrate_from(vm["migrate-from"].as<std::wstring>());
267                        } else if (vm.count("set")) {
268                                ret = client.set(vm["path"].as<std::wstring>(), vm["key"].as<std::wstring>(), vm["set"].as<std::wstring>());
269                        } else if (vm.count("list")) {
270                                ret = client.list(vm["path"].as<std::wstring>());
271                        } else if (vm.count("show")) {
272                                ret = client.show(vm["path"].as<std::wstring>(), vm["key"].as<std::wstring>());
273                        } else if (vm.count("switch")) {
274                                client.switch_context(vm["switch"].as<std::wstring>());
275                                ret = 0;
276                        } else if (vm.count("settings")) {
277                                client.set_current(vm["settings"].as<std::wstring>());
278                                ret = 0;
279                        } else {
280                                std::cout << all << std::endl;
281                                return 1;
282                        }
283                        client.exit();
284
285                        return ret;
286                } catch(std::exception & e) {
287                        get_logger()->error(__FILE__, __LINE__, std::wstring(_T("Unable to parse command line (settings): ")) + utf8::to_unicode(e.what()));
288                        return 1;
289                }
290        }
291
292
293        int parse_service(int argc, wchar_t* argv[]) {
294                try {
295                        po::options_description all("Allowed options (service)");
296                        all.add(common).add(service);
297
298                        po::variables_map vm;
299                        po::store(po::parse_command_line(argc, argv, all), vm);
300                        po::notify(vm);
301
302                        if (process_common_options("service", all))
303                                return 1;
304
305                        std::wstring name;
306                        if (vm.count("name")) {
307                                name = vm["name"].as<std::wstring>();
308                        } else {
309                                get_logger()->info(__FILE__, __LINE__, _T("TODO retrieve name from service here"));
310                        }
311                        std::wstring desc;
312                        if (vm.count("description")) {
313                                desc = vm["description"].as<std::wstring>();
314                        } else {
315                                get_logger()->info(__FILE__, __LINE__, _T("TODO retrieve name from service here"));
316                        }
317                        if (nsclient::logging::logger::get_logger()->should_log(NSCAPI::log_level::debug)) {
318                                get_logger()->debug(__FILE__, __LINE__, _T("Service name: ") + name);
319                                get_logger()->debug(__FILE__, __LINE__, _T("Service description: ") + desc);
320                        }
321
322                        if (vm.count("run")) {
323                                try {
324                                        mainClient.start_and_wait(name);
325                                } catch (...) {
326                                        get_logger()->error(__FILE__, __LINE__, _T("Unknown exception in service"));
327                                }
328                        } else {
329                                nsclient::client::service_manager service_manager(name);
330
331                                if (vm.count("install")) {
332                                        service_manager.install(desc);
333                                } else if (vm.count("uninstall")) {
334                                        service_manager.uninstall();
335                                } else if (vm.count("start")) {
336                                        service_manager.start();
337                                } else if (vm.count("stop")) {
338                                        service_manager.stop();
339                                } else if (vm.count("info")) {
340                                        service_manager.info();
341                                } else {
342                                        std::cerr << "Missing argument" << std::endl;
343                                        return 1;
344                                }
345                        }
346                        return 0;
347                } catch(std::exception & e) {
348                        std::cerr << std::string("Unable to parse command line (settings): ") << e.what() << "\n";
349                        return 1;
350                }
351        }
352
353        struct client_arguments {
354                std::wstring command, combined_query, module;
355                std::vector<std::wstring> arguments;
356                enum modes { exec, query, submit, none, combined};
357                modes mode;
358                bool boot;
359                client_arguments() : mode(none), boot(false) {}
360
361                void debug() {
362                        if (nsclient::logging::logger::get_logger()->should_log(NSCAPI::log_level::debug)) {
363                                get_logger()->info(__FILE__, __LINE__, _T("Module: ") + module);
364                                get_logger()->info(__FILE__, __LINE__, _T("Command: ") + command);
365                                get_logger()->info(__FILE__, __LINE__, _T("Extra Query: ") + combined_query);
366                                get_logger()->info(__FILE__, __LINE__, _T("Mode: ") + strEx::itos(mode));
367                                get_logger()->info(__FILE__, __LINE__, _T("Boot: ") + strEx::itos(boot));
368                                if (!module.empty() && boot)
369                                        get_logger()->info(__FILE__, __LINE__, _T("Warning module and boot specified only THAT module will be loaded"));
370                                std::wstring args;
371                                BOOST_FOREACH(std::wstring s, arguments)
372                                        strEx::append_list(args, s, _T(", "));
373                                get_logger()->info(__FILE__, __LINE__, _T("Arguments: ") + args);
374                        }
375
376                }
377        };
378        int parse_client(int argc, wchar_t* argv[], std::wstring module_ = _T("")) {
379                try {
380                        client_arguments args;
381
382                        args.module = module_;
383                        po::options_description all("Allowed options (client)");
384                        all.add(common).add(client);
385
386                        po::positional_options_description p;
387                        p.add("arguments", -1);
388
389                        po::variables_map vm;
390                        po::wparsed_options parsed =
391                                po::wcommand_line_parser(argc, argv).options(all).allow_unregistered().run();
392                        po::store(parsed, vm);
393                        po::notify(vm);
394
395                        if (process_common_options("client", all))
396                                return 1;
397
398
399                        if (vm.count("exec")) {
400                                args.command = vm["exec"].as<std::wstring>();
401                                args.mode = client_arguments::exec;
402                                if (vm.count("query")) {
403                                        args.combined_query = vm["query"].as<std::wstring>();
404                                        args.mode = client_arguments::combined;
405                                }
406                        } else if (vm.count("query")) {
407                                args.command = vm["query"].as<std::wstring>();
408                                args.mode = client_arguments::query;
409                        } else if (vm.count("submit")) {
410                                args.command = vm["submit"].as<std::wstring>();
411                                args.mode = client_arguments::submit;
412                        }
413
414                        if (vm.count("module"))
415                                args.module = vm["module"].as<std::wstring>();
416
417                        if (vm.count("boot"))
418                                args.boot = true;
419
420                        std::vector<std::wstring> kvp_args;
421                        if (vm.count("argument"))
422                                kvp_args = vm["argument"].as<std::vector<std::wstring> >();
423
424                        args.arguments = po::collect_unrecognized(parsed.options, po::include_positional);
425
426                        BOOST_FOREACH(std::wstring s, kvp_args) {
427                                std::wstring::size_type pos = s.find(L'=');
428                                if (pos == std::wstring::npos)
429                                        args.arguments.push_back(_T("--") + s);
430                                else {
431                                        args.arguments.push_back(_T("--") + s.substr(0,pos));
432                                        args.arguments.push_back(s.substr(pos+1));
433                                }
434                        }
435
436                        if (vm.count("raw-argument"))
437                                kvp_args = vm["raw-argument"].as<std::vector<std::wstring> >();
438                        BOOST_FOREACH(std::wstring s, kvp_args) {
439                                std::wstring::size_type pos = s.find(L'=');
440                                if (pos == std::wstring::npos)
441                                        args.arguments.push_back(s);
442                                else {
443                                        args.arguments.push_back(s.substr(0,pos));
444                                        args.arguments.push_back(s.substr(pos+1));
445                                }
446                        }
447                        return exec_client_mode(args);
448                } catch(const std::exception & e) {
449                        std::wcerr << _T("Client: Unable to parse command line: ") << utf8::to_unicode(e.what()) << std::endl;
450                        return 1;
451                } catch(...) {
452                        std::wcerr << _T("Client: Unable to parse command line: UNKNOWN") << std::endl;
453                        return 1;
454                }
455        }
456
457        int parse_unittest(int argc, wchar_t* argv[]) {
458                try {
459                        client_arguments args;
460                        settings_store = _T("dummy");
461                        po::options_description all("Allowed options (client)");
462                        all.add(common).add(unittest);
463
464                        po::positional_options_description p;
465                        p.add("arguments", -1);
466
467                        po::variables_map vm;
468                        po::wparsed_options parsed =
469                                po::wcommand_line_parser(argc, argv).options(all).allow_unregistered().run();
470                        po::store(parsed, vm);
471                        po::notify(vm);
472
473                        if (process_common_options("unitest", all))
474                                return 1;
475
476
477                        if (vm.count("language")) {
478                                std::wstring lang = vm["language"].as<std::wstring>();
479                                if (lang == _T("python") || lang == _T("py")) {
480                                        args.command = _T("python-script");
481                                        args.combined_query = _T("py_unittest");
482                                        args.mode = client_arguments::combined;
483                                        args.module = _T("PythonScript");
484                                } else {
485                                        std::wcerr << _T("Unknown language: ") << lang << std::endl;
486                                        return 1;
487                                }
488                        } else {
489                                args.command = _T("python-script");
490                                args.combined_query = _T("py_unittest");
491                                args.mode = client_arguments::combined;
492                                args.module = _T("PythonScript");
493                        }
494
495                        std::vector<std::wstring> kvp_args;
496                        if (vm.count("argument"))
497                                kvp_args = vm["argument"].as<std::vector<std::wstring> >();
498
499                        args.arguments = po::collect_unrecognized(parsed.options, po::include_positional);
500
501                        BOOST_FOREACH(std::wstring s, kvp_args) {
502                                std::wstring::size_type pos = s.find(L'=');
503                                if (pos == std::wstring::npos)
504                                        args.arguments.push_back(_T("--") + s);
505                                else {
506                                        args.arguments.push_back(_T("--") + s.substr(0,pos));
507                                        args.arguments.push_back(s.substr(pos+1));
508                                }
509                        }
510
511                        if (vm.count("raw-argument"))
512                                kvp_args = vm["raw-argument"].as<std::vector<std::wstring> >();
513                        BOOST_FOREACH(std::wstring s, kvp_args) {
514                                std::wstring::size_type pos = s.find(L'=');
515                                if (pos == std::wstring::npos)
516                                        args.arguments.push_back(s);
517                                else {
518                                        args.arguments.push_back(s.substr(0,pos));
519                                        args.arguments.push_back(s.substr(pos+1));
520                                }
521                        }
522                        return exec_client_mode(args);
523                } catch(const std::exception & e) {
524                        std::wcerr << _T("Client: Unable to parse command line: ") << utf8::to_unicode(e.what()) << std::endl;
525                        return 1;
526                } catch(...) {
527                        std::wcerr << _T("Client: Unable to parse command line: UNKNOWN") << std::endl;
528                        return 1;
529                }
530        }
531
532        int exec_client_mode(client_arguments &args) {
533                try {
534                        args.debug();
535
536                        core_->boot_init(log_level);
537                        if (args.module.empty())
538                                core_->boot_load_all_plugins();
539                        else
540                                core_->boot_load_plugin(args.module);
541                        core_->boot_start_plugins(args.boot);
542                        int ret = 0;
543                        std::list<std::wstring> resp;
544                        if (args.mode == client_arguments::none) {
545                                args.mode = client_arguments::exec;
546                                std::wcerr << _T("Since no mode was specified assuming --exec (other options are --query and --submit)") << std::endl;
547                        }
548                        if (args.mode == client_arguments::query) {
549                                ret = mainClient.simple_query(args.module, args.command, args.arguments, resp);
550                        } else if (args.mode == client_arguments::exec || args.mode == client_arguments::combined) {
551                                ret = mainClient.simple_exec(args.module, args.command, args.arguments, resp);
552                                if (ret == NSCAPI::returnIgnored) {
553                                        ret = 1;
554                                        std::wcout << _T("Command not found (by module): ") << args.command << std::endl;
555                                        resp.push_back(_T("Command not found: ") + args.command);
556                                        mainClient.simple_exec(args.module, _T("help"), args.arguments, resp);
557                                } else if (args.mode == client_arguments::combined) {
558                                        if (ret == NSCAPI::returnOK) {
559                                                mainClient.reload(_T("service"));
560                                                ret = mainClient.simple_query(args.module, args.combined_query, args.arguments, resp);
561                                        } else {
562                                                std::wcerr << _T("Failed to execute command, will not attempt query") << std::endl;
563                                        }
564                                }
565                        } else if (args.mode == client_arguments::submit) {
566                                std::wcerr << _T("--submit is currently not supported (but you can use --exec submit which is technically the same)") << std::endl;
567                        } else {
568                                std::wcerr << _T("Need to specify one of --exec, --query or --submit") << std::endl;
569                        }
570                        mainClient.stop_unload_plugins_pre();
571                        mainClient.stop_exit_pre();
572                        mainClient.stop_exit_post();
573
574                        BOOST_FOREACH(std::wstring r, resp) {
575                                std::wcout << r << std::endl;
576                        }
577                        return ret;
578                } catch(const std::exception & e) {
579                        std::wcerr << _T("Client: Unable to parse command line: ") << utf8::to_unicode(e.what()) << std::endl;
580                        return 1;
581                } catch(...) {
582                        std::wcerr << _T("Client: Unable to parse command line: UNKNOWN") << std::endl;
583                        return 1;
584                }
585        }
586};
587
588
589
590
591
Note: See TracBrowser for help on using the repository browser.