diff --git a/ecpp/alert_rules_list.ecpp b/ecpp/alert_rules_list.ecpp index 1c50fd1bd..64fd1c9b4 100644 --- a/ecpp/alert_rules_list.ecpp +++ b/ecpp/alert_rules_list.ecpp @@ -188,7 +188,7 @@ if (streq (part, "LIST")) { http_die ("internal-error", err.c_str ()); } part = zmsg_popstr (recv_msg); - + cxxtools::SerializationInfo si; si.setCategory(cxxtools::SerializationInfo::Category::Array); @@ -199,7 +199,7 @@ if (streq (part, "LIST")) { log_debug ("Collecting the data:"); while (part) { - + const char *internalCat = "\"CAT_INTERNAL\""; const char *automationCat = "\"CAT_AUTOMATION\""; @@ -295,7 +295,7 @@ if (streq (part, "LIST")) { { //ignore the change and continue log_error ("Error during replacement of ename: %s", e.what()); - } + } } //deserialize the new data to add it in the answer json diff --git a/ecpp/average.ecpp b/ecpp/average.ecpp index cfd2ebf7b..bdfc4bacb 100644 --- a/ecpp/average.ecpp +++ b/ecpp/average.ecpp @@ -259,11 +259,10 @@ bool database_ready; http_die ("internal-error", err.c_str ()); } - const int RECV_TIMEOUT_S = 60; - zmsg_t *recv_msg = client->recv (zuuid_str_canonical (uuid), RECV_TIMEOUT_S); + zmsg_t *recv_msg = client->recv (zuuid_str_canonical (uuid), 30); zuuid_destroy (&uuid); if (!recv_msg) { - log_fatal ("client->recv (timeout = '%d') returned NULL", RECV_TIMEOUT_S); + log_fatal ("client->recv (timeout = '30') returned NULL"); std::string err = TRANSLATE_ME ("client->recv () returned NULL"); http_die ("internal-error", err.c_str ()); } @@ -391,11 +390,10 @@ bool database_ready; http_die ("internal-error", err.c_str ()); } - const int RECV_TIMEOUT_S = 60; - zmsg_t *recv_msg = client->recv (zuuid_str_canonical (uuid), RECV_TIMEOUT_S); + zmsg_t *recv_msg = client->recv (zuuid_str_canonical (uuid), 30); zuuid_destroy (&uuid); if (!recv_msg) { - log_fatal ("client->recv (timeout = '%d') returned NULL", RECV_TIMEOUT_S); + log_fatal ("client->recv (timeout = '30') returned NULL"); std::string err = TRANSLATE_ME ("client->recv () returned NULL"); http_die ("internal-error", err.c_str ()); } diff --git a/ecpp/email_feedback.ecpp b/ecpp/email_feedback.ecpp index 32b31bc26..fb89d2984 100644 --- a/ecpp/email_feedback.ecpp +++ b/ecpp/email_feedback.ecpp @@ -1,6 +1,6 @@ <# # - # Copyright (C) 2016 - 2020 Eaton + # Copyright (C) 2016 - 2022 Eaton # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -21,6 +21,7 @@ * \file email_feedback.ecpp * \author Barbora Stepankova * \author Michal Vyskocil + * \author Arnaud Quette * \brief Sends feedback email. */ #><%pre> @@ -36,6 +37,47 @@ #include #include //POSIX version of basename!! #include +#include + +// TODO: put in fty-common-rest +static const char* BRANDING_INFO = "/etc/etn-ipm2-branding.conf"; + +static cxxtools::SerializationInfo* s_load_branding_info() +{ + cxxtools::SerializationInfo* si = new cxxtools::SerializationInfo(); + try { + std::ifstream f(BRANDING_INFO); + std::string json_string(std::istreambuf_iterator(f), {}); + std::stringstream s(json_string); + cxxtools::JsonDeserializer json(s); + json.deserialize(*si); + log_info("email_feedback: load %s OK", BRANDING_INFO); + } catch (const std::exception& e) { + log_error("Error while parsing JSON: %s", e.what()); + } + return si; +} + +static char* s_get_branding_info(cxxtools::SerializationInfo* si, const char* key, const char* dfl) +{ + std::string value; + try { + si->getMember(key) >>= value; + } catch (const std::exception& e) { + log_info("Problem with getting %s in JSON: %s", key, e.what()); + if (dfl) { + return strdup(dfl); + } else { + return NULL; + } + } + if (value.empty() && dfl) { + log_debug("%s: can't get value. Using default '%s'", __func__, dfl); + return strdup(dfl); + } + return strdup(value.c_str()); +} +// end-of TODO: put in fty-common-rest // encode GET message for smtp agent static zmsg_t* @@ -129,15 +171,26 @@ UserInfo user; bool attach_system_state = false; bool participate = false; unsigned _timeout = 60; + char *feedback_email = NULL; { - std::string to = qparam.param ("to"); if (to.empty ()) { const char* c_to = getenv ("BIOS_FEEDBACK_EMAIL"); if (c_to) to = std::string {c_to}; - else - to = std::string {"EatonProductFeedback@Eaton.com"}; + else { + // Get Product feedback mail from branding info + cxxtools::SerializationInfo* bi = s_load_branding_info(); + feedback_email = s_get_branding_info(bi, "feedback_email", "EatonProductFeedback@Eaton.com"); + if (bi) { + delete bi; + bi = nullptr; + } + if (feedback_email) { + to = std::string {feedback_email}; + zstr_free (&feedback_email); + } + } } if (to.find ('@', 0) == std::string::npos) { @@ -147,6 +200,7 @@ UserInfo user; } checked_to = to; + logInfo("Using Product Feedback email address '{}'", checked_to.c_str()); std::string _reply = qparam.param ("reply"); @@ -322,7 +376,7 @@ UserInfo user; log_error_audit ("Request CREATE email_feedback FAILED"); http_die ("internal-error", err.c_str ()); } - log_info_audit ("Request CREATE email_feedback SUCCESS"); + log_info_audit ("Request CREATE email_feedback SUCCESS (using %s)", checked_to.c_str()); { diff --git a/ecpp/email_vote.ecpp b/ecpp/email_vote.ecpp index 6c25af8a7..141af5f44 100644 --- a/ecpp/email_vote.ecpp +++ b/ecpp/email_vote.ecpp @@ -1,6 +1,6 @@ <# # - # Copyright (C) 2016 - 2020 Eaton + # Copyright (C) 2016 - 2022 Eaton # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -21,6 +21,7 @@ * \file email_vote.ecpp * \author Barbora Stepankova * \author Michal Vyskocil + * \author Arnaud Quette * \brief Sends voting email. */ #><%pre> @@ -36,6 +37,48 @@ #include //POSIX version of basename!! #include +#include + +// TODO: put in fty-common-rest +static const char* BRANDING_INFO = "/etc/etn-ipm2-branding.conf"; + +static cxxtools::SerializationInfo* s_load_branding_info() +{ + cxxtools::SerializationInfo* si = new cxxtools::SerializationInfo(); + try { + std::ifstream f(BRANDING_INFO); + std::string json_string(std::istreambuf_iterator(f), {}); + std::stringstream s(json_string); + cxxtools::JsonDeserializer json(s); + json.deserialize(*si); + log_info("email_feedback: load %s OK", BRANDING_INFO); + } catch (const std::exception& e) { + log_error("Error while parsing JSON: %s", e.what()); + } + return si; +} + +static char* s_get_branding_info(cxxtools::SerializationInfo* si, const char* key, const char* dfl) +{ + std::string value; + try { + si->getMember(key) >>= value; + } catch (const std::exception& e) { + log_info("Problem with getting %s in JSON: %s", key, e.what()); + if (dfl) { + return strdup(dfl); + } else { + return NULL; + } + } + if (value.empty() && dfl) { + log_debug("%s: can't get value. Using default '%s'", __func__, dfl); + return strdup(dfl); + } + return strdup(value.c_str()); +} +// end-of TODO: put in fty-common-rest + // encode GET message for smtp agent static zmsg_t* s_smtp_GET_message ( @@ -90,7 +133,7 @@ UserInfo user; {BiosProfile::Admin, "C"}, {BiosProfile::Dashboard, "C"} }; - std::string audit_msg = std::string ("Request CREATE email_test FAILED"); + std::string audit_msg = std::string ("Request CREATE email_vote FAILED"); CHECK_USER_PERMISSIONS_OR_DIE_AUDIT (PERMISSIONS, audit_msg.empty () ? nullptr : audit_msg.c_str ()); std::string checked_to; @@ -98,25 +141,37 @@ UserInfo user; std::string checked_context; bool attach_system_state = false; unsigned _timeout = 60; + char *feedback_email = NULL; { std::string to = qparam.param ("to"); if (to.empty ()) { const char* c_to = getenv ("BIOS_FEEDBACK_EMAIL"); if (c_to) to = std::string {c_to}; - else - to = std::string {"EatonProductFeedback@Eaton.com"}; + else { + // Get Product feedback mail from branding info + cxxtools::SerializationInfo* bi = s_load_branding_info(); + feedback_email = s_get_branding_info(bi, "feedback_email", "EatonProductFeedback@Eaton.com"); + if (bi) { + delete bi; + bi = nullptr; + } + if (feedback_email) { + to = std::string {feedback_email}; + zstr_free (&feedback_email); + } + } } checked_to = to; if (to.find ('@', 0) == std::string::npos) { std::string expected = TRANSLATE_ME ("valid email address"); - log_error_audit ("Request CREATE email_test FAILED"); + log_error_audit ("Request CREATE email_vote FAILED"); http_die ("request-param-bad", "to", to.c_str (), expected.c_str ()); } std::string value = qparam.param ("value"); if (value.empty ()) { - log_error_audit ("Request CREATE email_test FAILED"); + log_error_audit ("Request CREATE email_vote FAILED"); http_die ("request-param-required", "value"); } @@ -125,13 +180,13 @@ UserInfo user; } catch (const std::exception& e) { std::string expected = TRANSLATE_ME ("number 1-3"); - log_error_audit ("Request CREATE email_test FAILED"); + log_error_audit ("Request CREATE email_vote FAILED"); http_die ("request-param-bad", "value", value.c_str (), expected.c_str ()); } std::string context = qparam.param ("context"); if (context.empty ()) { - log_error_audit ("Request CREATE email_test FAILED"); + log_error_audit ("Request CREATE email_vote FAILED"); http_die ("request-param-required", "context"); } @@ -156,7 +211,7 @@ UserInfo user; if (!client) { log_fatal ("Error: mlm_pool.get () failed."); std::string err = TRANSLATE_ME ("mlm_pool.get () failed."); - log_error_audit ("Request CREATE email_test FAILED"); + log_error_audit ("Request CREATE email_vote FAILED"); http_die ("internal-error", err.c_str ()); } @@ -177,7 +232,7 @@ UserInfo user; AGENT_FTY_EMAIL_SENDMAIL_ONLY, "SENDMAIL"); std::string err = TRANSLATE_ME ("client->sendto () failed"); zmsg_destroy (&send); - log_error_audit ("Request CREATE email_test FAILED"); + log_error_audit ("Request CREATE email_vote FAILED"); http_die ("internal-error", err.c_str ()); } @@ -186,7 +241,7 @@ UserInfo user; { std::string msg = TRANSLATE_ME ("Error: client-> recv (timeout = '%d') returned NULL", _timeout); log_error ("%s", msg.c_str ()); - log_error_audit ("Request CREATE email_test FAILED"); + log_error_audit ("Request CREATE email_vote FAILED"); http_die ("internal-error", msg.c_str ()); } @@ -203,7 +258,7 @@ UserInfo user; if (streq (msg_subject, "SENDMAIL-ERR")) { status = "Failed"; - log_error_audit ("Request CREATE email_test FAILED"); + log_error_audit ("Request CREATE email_vote FAILED"); http_die ("upstream-err", err_message.get ()); } else @@ -211,10 +266,10 @@ UserInfo user; status = "Failed"; log_fatal ("Error: message recieved with invalid subject."); std::string err = TRANSLATE_ME ("client->recv () invalid message subject"); - log_error_audit ("Request CREATE email_test FAILED"); + log_error_audit ("Request CREATE email_vote FAILED"); http_die ("internal-error", err.c_str ()); } - log_info_audit ("Request CREATE email_test SUCCESS"); + log_info_audit ("Request CREATE email_vote SUCCESS (using %s)", checked_to.c_str()); { diff --git a/ecpp/license_POST.ecpp b/ecpp/license_POST.ecpp index 45b1022ea..53eb0ecd4 100644 --- a/ecpp/license_POST.ecpp +++ b/ecpp/license_POST.ecpp @@ -31,9 +31,7 @@ #include #include #include -#include #include -#include #include "shared/utils.h" #include "cleanup.h" @@ -132,239 +130,140 @@ bool database_ready; log_info ("Starting db service, timestamp=%" PRIu64 " ..." , tme_start); std::string proc_out, proc_err; - // Just a short sleep first: hopefully systemd would already - // pick up the appearance of touch-file and begin handling FLA: sleep(10); // due to some OS circumstances, the script can sometimes block // while calling the /bin/systemctl and is eventually killed, // even though the actual services of our product have started - // long before this; so even if it failed - we would try to use - // the DB opportunistically... Still, better to know we have - // all the backend services running that are needed for EULA - // wizard and beyond! + // long before this; so even if it failed - try to use the DB... fty::Process proc("/usr/libexec/fty/start-db-services", {}, fty::Capture::Out | fty::Capture::Err); if (auto ret = proc.run(); !ret) { log_error("Starting of start-db-services have failed. Consult system logs"); log_error(ret.error().c_str()); http_die("internal-error", ret.error().c_str()); } - - // The start-db-services script ensures that all FTY/IPM2 related - // services and targets become active, including that the database - // schema gets initialized and its chain of consumers starts, - // and we are blocked until we "know" there is now someone running - // that would actually handle the backend requests for the rest of - // EULA wizard. - // So as far as we are concerned here, the script runtime can last - // several minutes (even if the CPU/IO/Time load is mostly elsewhere). - // Normally tntnet would suicide with a 10-minute request processing, - // unless a deadman timer is tickled to confirm we are stoll alive. - // This can be tuned with maxRequestTime (0 to disable the watchdog) - // or handled safely with a loop sleeping, checking that the script - // still runs, and tickling tntnet::Worker::touch() in the known-long - // processing loop. It should suffice to catch `unexpected("timeout")` - // (child still running as of last check before time limit; no other - // errors) as per fty-utils::fty/process.h wording of Process::wait(). - // the wait() would end earlier if the child process finishes: - - // Use shorter loops to avoid long wait if script is stuck flushing - // its output: - int wait_cycle = 5000; - -#if 0 - // Override to longer waits... at a risk of longer unblocking of - // stuck write() calls in child process: - wait_cycle = 3 * 60000; - - // configured in seconds, waiting in milliseconds: - unsigned int mrt = tnt::TntConfig::it().maxRequestTime; - if (mrt > 0 && mrt < (INT_MAX / 1000)) { // if enabled at all - wait_cycle = static_cast(mrt) * 666 ; // convert to msec and trim 2/3 for overheads + auto rv_svc = proc.wait(15000); + proc_out = proc.readAllStandardOutput(); + proc_err = proc.readAllStandardError(); + proc.kill(); + + uint64_t tme_finish = uint64_t(::time (NULL)); + if (rv_svc) { + log_info ("Starting db service completed, timestamp=%" PRIu64 " (duration=%" PRIu64 "), exit-code=%i ..." , + tme_finish, (tme_finish - tme_start), *rv_svc); + } else { + log_info ("Starting db service completed, timestamp=%" PRIu64 " (duration=%" PRIu64 "), exit-code=%s ..." , + tme_finish, (tme_finish - tme_start), rv_svc.error().c_str()); } -#endif - log_debug ("Starting db service will refresh tntnet deadman timer every %u msec", wait_cycle); - - for (;;) { // repeat_start_db_services: - // Read the outputs (continuously, to avoid script blocking - // on write() here; repeated below for endspiel): - while (true) { - std::string strerr = proc.readAllStandardError(100); - std::string strout = proc.readAllStandardOutput(100); - if (strerr.empty() && strout.empty()) { - break; - } - if (!strerr.empty()) proc_err += strerr; - if (!strout.empty()) proc_out += strout; - } + if (!rv_svc || *rv_svc != 0) { + log_error ("Starting of start-db-services have failed. Consult system logs"); + log_error ("%s failed with error %s.\n===== STDOUT: =====\n%s\n===== STDERR: =====\n%s\n=====", + "/usr/libexec/fty/start-db-services", rv_svc.error().c_str(), proc_out.c_str (), proc_err.c_str ()); - // May finish before "wait_cycle" elapses: - auto rv_svc = proc.wait(wait_cycle); - uint64_t tme_finish = uint64_t(::time (NULL)); - - // Success? Fail? Keep waiting?.. - if (!rv_svc && "timeout" == rv_svc.error()) { - // ...keep waiting, script is not finished: - log_info ("Starting db service takes longer than expected on this system, waiting more, timestamp=%" PRIu64 " (duration=%" PRIu64 ")" , - tme_finish, (tme_finish - tme_start)); - request.touch(); - continue; // goto repeat_start_db_services; + if (rv_svc.error() != "timeout") { + std::string err = TRANSLATE_ME ("Starting of start-db-services have failed. Consult system logs"); + log_error_audit ("Request CREATE license FAILED"); + http_die ("internal-error", err.c_str ()); } + // Negative return means killed by signal, typically by our + // subprocess timeout handling - fall through to try using DB - // ...success or fail, script is finished - log_info ("Starting db service helper script has finished, collecting results"); - - // Read all of the buffers; note that fty-utils defaults the args - // for readFromFd() behind these methods in a way that it would - // only try twice. So much for "all" in the name. Loop until dry - // (otherwise after some traffic the script effectively blocks - // when it is not milked away): - while (true) { - std::string strerr = proc.readAllStandardError(100); - std::string strout = proc.readAllStandardOutput(100); - if (strerr.empty() && strout.empty()) { - break; - } - if (!strerr.empty()) proc_err += strerr; - if (!strout.empty()) proc_out += strout; - } + { + std::string proc_out1, proc_err1; + int lastResult = 0; + for (int i = 0; i < 10; ++i) { + fty::Process::Arguments proc_cmd = {"systemctl", "list-ipm-units", "--active", "fty-db-engine"}; + // This should get our "systemctl" wrapper via PATH - // Terminate child (if any) to avoid leaks: - proc.kill(); + auto rv = fty::Process::run("sudo", proc_cmd, proc_out1, proc_err1); - if (rv_svc) { - log_info ("Starting db service completed, timestamp=%" PRIu64 " (duration=%" PRIu64 "), exit-code=%i ..." , - tme_finish, (tme_finish - tme_start), *rv_svc); - } else { - log_info ("Starting db service completed, timestamp=%" PRIu64 " (duration=%" PRIu64 "), exit-code=%s ..." , - tme_finish, (tme_finish - tme_start), rv_svc.error().c_str()); - } + if (!rv) { + log_error(rv.error().c_str()); + log_error_audit ("Request CREATE license FAILED"); + http_die ("internal-error", TRANSLATE_ME ("Database software service is not running").c_str()); + } - // non-zero exit code or proc class exception: - if (!rv_svc || *rv_svc != 0) { - std::string rv_svc_err; - if (rv_svc) { - rv_svc_err = "code "; - rv_svc_err += std::to_string(*rv_svc); - } else { - rv_svc_err = rv_svc.error(); + lastResult = *rv; + if (*rv == 0) { + break; + } + sleep(5); } - log_error ("Starting of start-db-services have failed. Consult system logs"); - log_error ("%s failed with error %s.\n===== STDOUT: =====\n%s\n===== STDERR: =====\n%s\n=====", - "/usr/libexec/fty/start-db-services", - rv_svc_err.c_str(), proc_out.c_str(), proc_err.c_str()); - - if (!rv_svc && rv_svc.error() != "timeout") { - std::string err = TRANSLATE_ME ("Starting of start-db-services have failed. Consult system logs"); + if (lastResult != 0) { + std::string message; + message = "`sudo systemctl list-ipm-units --active fty-db-engine`" + " failed (service is not currently active). Return value = '" + + std::to_string (lastResult) + "', stderr = '" + proc_err1 + "'." ; + log_error ("%s", message.c_str ()); + std::string err = TRANSLATE_ME ("Database software service is not running"); log_error_audit ("Request CREATE license FAILED"); http_die ("internal-error", err.c_str ()); + } else { + // Not quite an error, but we want this message at the same logging level + log_error ("NOTE: Although start-db-services script was killed, the fty-db-engine service is okay"); } - // Negative return means killed by signal, typically by our - // subprocess timeout handling - fall through to try using DB - - { // scoping for error-handling: inspect fty-db-engine (mysqld etc) service unit status: - std::string proc_out1, proc_err1; - int lastResult = 0; - for (int i = 0; i < 10; ++i) { - fty::Process::Arguments proc_cmd = {"systemctl", "list-ipm-units", "--active", "fty-db-engine"}; - // This should get our "systemctl" wrapper via PATH - - auto rv = fty::Process::run("sudo", proc_cmd, proc_out1, proc_err1); - - if (!rv) { - log_error(rv.error().c_str()); - log_error_audit ("Request CREATE license FAILED"); - http_die ("internal-error", TRANSLATE_ME ("Database software service is not running").c_str()); - } - - lastResult = *rv; - if (*rv == 0) { - break; - } - sleep(5); - } - - if (lastResult != 0) { - std::string message; - message = "`sudo systemctl list-ipm-units --active fty-db-engine`" - " failed (service is not currently active). Return value = '" - + std::to_string (lastResult) + "', stderr = '" + proc_err1 + "'." ; - log_error ("%s", message.c_str ()); - std::string err = TRANSLATE_ME ("Database software service is not running"); - log_error_audit ("Request CREATE license FAILED"); - http_die ("internal-error", err.c_str ()); - } else { - // Not quite an error, but we want this message at the same logging level - log_error ("NOTE: Although start-db-services script was killed, the fty-db-engine service is okay"); - } - } - + } - { // scoping for error-handling: inspect fty-db-init (schema import) service unit status: - std::string proc_out1, proc_err1; - int lastResult = 0; - // try to increase timeout.. - for (int i = 0; i < 50; ++i) { - fty::Process::Arguments proc_cmd {"systemctl", "list-ipm-units", "--active", "fty-db-init"}; - // This should get our "systemctl" wrapper via PATH + { + std::string proc_out1, proc_err1; + int lastResult = 0; - auto rv = fty::Process::run("sudo", proc_cmd, proc_out1, proc_err1); + // try to increase timeout.. + for (int i = 0; i < 50; ++i) { + fty::Process::Arguments proc_cmd {"systemctl", "list-ipm-units", "--active", "fty-db-init"}; + // This should get our "systemctl" wrapper via PATH - if (!rv) { - log_error(rv.error().c_str()); - log_error_audit("--! %s", rv.error().c_str()); - log_error_audit ("Request CREATE license FAILED"); - http_die ("internal-error", TRANSLATE_ME ("Database schema was not installed or verified successfully").c_str()); - } + auto rv = fty::Process::run("sudo", proc_cmd, proc_out1, proc_err1); - lastResult = *rv; - if (*rv == 0) { - log_error_audit("is ok"); - break; - } - log_error_audit("next to check"); - sleep(5); + if (!rv) { + log_error(rv.error().c_str()); + log_error_audit("--! %s", rv.error().c_str()); + log_error_audit ("Request CREATE license FAILED"); + http_die ("internal-error", TRANSLATE_ME ("Database schema was not installed or verified successfully").c_str()); } - if (lastResult != 0) { - std::string message = "`sudo systemctl list-ipm-units --active fty-db-init`" - " failed (service is not currently active). Return value = '" - + std::to_string (lastResult) + "', stderr = '" + proc_err1 + "'." ; - log_error ("%s", message.c_str()); - log_error_audit("%s", message.c_str()); - std::string err = TRANSLATE_ME ("Database schema was not installed or verified successfully"); - log_error_audit ("Request CREATE license FAILED"); - http_die ("internal-error", err.c_str ()); - } else { - // Not quite an error, but we want this message at the same logging level - log_error ("NOTE: Although start-db-services script was killed, the fty-db-init service is okay"); + lastResult = *rv; + if (*rv == 0) { + log_error_audit("is ok"); + break; } + log_error_audit("next to check"); + sleep(5); } - } // handle if script failed - - // once done, check environment files for accessing the database - uint64_t tme_started = uint64_t(::time (NULL)); - log_info ("db services were started by timestamp=%" PRIu64 ", re-reading password ...", tme_started); - int rv_dbcred = DBConn::dbreadcredentials (); - if (!rv_dbcred) { - std::string message; - if (*rv_svc != 0) { - message = TRANSLATE_ME ("Database password file is missing AND failed to start start-db-services. Consult system logs"); + + if (lastResult != 0) { + std::string message = "`sudo systemctl list-ipm-units --active fty-db-init`" + " failed (service is not currently active). Return value = '" + + std::to_string (lastResult) + "', stderr = '" + proc_err1 + "'." ; + log_error ("%s", message.c_str()); + log_error_audit("%s", message.c_str()); + std::string err = TRANSLATE_ME ("Database schema was not installed or verified successfully"); + log_error_audit ("Request CREATE license FAILED"); + http_die ("internal-error", err.c_str ()); } else { - message = TRANSLATE_ME ("Database password file is missing"); + // Not quite an error, but we want this message at the same logging level + log_error ("NOTE: Although start-db-services script was killed, the fty-db-init service is okay"); } - log_error ("%s", message.c_str ()); - log_error_audit ("Request CREATE license FAILED"); - http_die ("internal-error", message.c_str ()); } + } - // we get here when script completed (well or not), - // so finish the infinite loop: - break; - } // for deadman timer around start-db-services - + // once done, check environment files for accessing the database + uint64_t tme_started = uint64_t(::time (NULL)); + log_info ("db services were started by timestamp=%" PRIu64 ", re-reading password ...", tme_started); + int rv_dbcred = DBConn::dbreadcredentials (); + if (!rv_dbcred) { + std::string message; + if (*rv_svc != 0) { + message = TRANSLATE_ME ("Database password file is missing AND failed to start start-db-services. Consult system logs"); + } else { + message = TRANSLATE_ME ("Database password file is missing"); + } + log_error ("%s", message.c_str ()); + log_error_audit ("Request CREATE license FAILED"); + http_die ("internal-error", message.c_str ()); + } } else { // enforce reload of credentials, e.g. if timely service startup // had failed previously but the system has caught up by now diff --git a/ecpp/netcfg.ecpp b/ecpp/netcfg.ecpp index 5b9db7de4..23c6de98f 100644 --- a/ecpp/netcfg.ecpp +++ b/ecpp/netcfg.ecpp @@ -30,7 +30,6 @@ #include #include #include -#include #include #include @@ -48,55 +47,37 @@ using namespace shared; UserInfo user; <%cpp> -// check user permissions -static const std::map PERMISSIONS = { - {BiosProfile::Admin, "RU"} - }; - -std::string audit_msg; -if (request.getMethod () == "PUT") - audit_msg = std::string ("Request CREATE OR UPDATE netcfg FAILED"); - -CHECK_USER_PERMISSIONS_OR_DIE_AUDIT (PERMISSIONS, audit_msg.empty () ? nullptr : audit_msg.c_str ()); - -const bool sudoer = (request.getMethod () != "GET"); // augtool require sudo privileges? -augtool* augeas = nullptr; -int counter = 0; - -while(augeas == nullptr) -{ - augeas = augtool::get_instance (sudoer); - counter++; - if(counter >= 10) - { - break; - } -} - + // check user permissions + static const std::map PERMISSIONS = { + {BiosProfile::Admin, "RU"} + }; + std::string audit_msg; + if (request.getMethod () == "PUT") + audit_msg = std::string ("Request CREATE OR UPDATE netcfg FAILED"); + CHECK_USER_PERMISSIONS_OR_DIE_AUDIT (PERMISSIONS, audit_msg.empty () ? nullptr : audit_msg.c_str ()); + +augtool* augeas = augtool::get_instance (); if (!augeas) { - std::string err = TRANSLATE_ME ("Cannot communicate with augtool. Is it installed or properly configured?"); + std::string err = TRANSLATE_ME ("Cannot communicate with augtool. Is it installed or properly configured?"); if (request.getMethod () == "PUT") { log_error_audit ("Request CREATE OR UPDATE netcfg FAILED"); } http_die ("internal-error", err.c_str ()); } - std::string nil; // Make sure we have data that works - nil = augeas->get_cmd_out ("ls /augeas/files/etc/network/interfaces/error"); if (!nil.empty ()) { - std::string err = TRANSLATE_ME ("Syntax error in /etc/network/interfaces config file"); + std::string err = TRANSLATE_ME ("Syntax error in /etc/network/interfaces config file"); if (request.getMethod () == "PUT") { log_error_audit ("Request CREATE OR UPDATE netcfg FAILED"); } http_die ("internal-error", err.c_str ()); } - nil = augeas->get_cmd_out ("ls /augeas/files/etc/resolv.conf/error"); if (!nil.empty ()) { - std::string err = TRANSLATE_ME ("Syntax error in /etc/resolv.conf config file"); + std::string err = TRANSLATE_ME ("Syntax error in /etc/resolv.conf config file"); if (request.getMethod () == "PUT") { log_error_audit ("Request CREATE OR UPDATE netcfg FAILED"); } @@ -104,42 +85,37 @@ if (!nil.empty ()) { } // Are we asked to just list possible configurations? - -static const cxxtools::Regex rex_ncfgs ("^.*/netcfgs$"); -if (rex_ncfgs.match (request.getUrl ())) { - - std::vector all_interfaces; - { - std::string all_interfaces_str = augeas->get_cmd_out ("match /files/etc/network/interfaces/iface[*]", - true, ",", - [](const std::string iface) -> bool { - return iface == "lo"; // ignore "lo" if - } - ); - if (all_interfaces_str.empty ()) { - std::string err = TRANSLATE_ME ("No configurable interfaces found"); - if (request.getMethod () == "PUT") { - log_error_audit ("Request CREATE OR UPDATE netcfg FAILED"); - } - http_die ("internal-error", err.c_str ()); +static cxxtools::Regex rex_ncfg ("^.*/netcfgs$"); +if (rex_ncfg.match (request.getUrl ())) { + std::string all_interfaces_str = augeas->get_cmd_out ("match /files/etc/network/interfaces/iface[*]", + true, ",", + [](const std::string iface) -> bool { + return iface == "lo"; + } + ); + if (all_interfaces_str.empty ()) { + std::string err = TRANSLATE_ME ("No configurable interfaces found"); + if (request.getMethod () == "PUT") { + log_error_audit ("Request CREATE OR UPDATE netcfg FAILED"); } - - cxxtools::split (",", all_interfaces_str, std::back_inserter (all_interfaces)); + http_die ("internal-error", err.c_str ()); } std::set active_interfaces = get_ifaces (); + std::vector all_interfaces; + cxxtools::split (",", all_interfaces_str, std::back_inserter (all_interfaces)); std::string out; for (std::string iface : all_interfaces) { if (active_interfaces.find (iface) != active_interfaces.end ()) { - out += (out.empty() ? "" : ",") + std::string{"\"" + iface + "\""}; + out += "\"" + iface + "\","; } } - + // remove trailing comma + out.pop_back (); { "netcfgs": [ <$$ out $> ] } <%cpp> - if (request.getMethod () == "PUT") { log_info_audit ("Request CREATE OR UPDATE netcfg SUCCESS"); } @@ -167,7 +143,7 @@ std::string checked_iface; // Where is the tree reflecting the interface? std::string address = augeas->get_cmd_out ( "match /files/etc/network/interfaces/iface[*] " + checked_iface, - false); + false); if (address.empty ()) { // Not perfect, but bad enough to create another message? if (request.getMethod () == "PUT") { @@ -184,10 +160,12 @@ AUG_GET ("method", method); // Modifications requested? if (request.getMethod () == "PUT") { + std::stringstream input (request.getBody (), std::ios_base::in); cxxtools::SerializationInfo si; + cxxtools::SerializationInfo rsi; + cxxtools::JsonDeserializer deserializer (input); + std::string val; try { - std::stringstream input (request.getBody (), std::ios_base::in); - cxxtools::JsonDeserializer deserializer (input); deserializer.deserialize (si); } catch (const std::exception& e) { std::string msg = TRANSLATE_ME ("Expected valid json document: %s", JSONIFY (e.what ()).c_str ()); @@ -195,7 +173,6 @@ if (request.getMethod () == "PUT") { http_die ("bad-request-document", msg.c_str ()); } - cxxtools::SerializationInfo rsi; try { rsi = si.getMember (checked_iface); } catch (const std::exception& e) { @@ -204,13 +181,16 @@ if (request.getMethod () == "PUT") { http_die ("bad-request-document", msg.c_str ()); } + // Gets configuration from json and sets it in config while verifying it matches regexp -#define AUG_SET(NAME, CHECK) \ +#define AUG_SET(NAME, CHECK) \ if (it.name () == NAME) { \ it.getValue (val); \ if (! CHECK ) { \ std::string msg = TRANSLATE_ME ("Wrong value for '%s' setting or setting not expected for method %s.", NAME, method.c_str ()); \ - log_error_audit ("Request CREATE OR UPDATE netcfg FAILED"); \ + if (request.getMethod () == "PUT") { \ + log_error_audit ("Request CREATE OR UPDATE netcfg FAILED"); \ + } \ http_die ("parameter-conflict", msg.c_str ()); \ } \ augeas->run_cmd ("set " + address + "/" NAME " " + val); \ @@ -219,13 +199,10 @@ if (request.getMethod () == "PUT") { // All the things we need to set for (auto it : rsi) { - static const cxxtools::Regex rex_method ("^(dhcp|static|none|manual)$"); - std::string val; //ZZZ set & used internally by AUG_SET bool handled = false; - - AUG_SET ("method", rex_method.match (method)); - AUG_GET ("method", method); //ZZZ how many times do we augtool::get 'method' (useless/timeless calls)!? - + static cxxtools::Regex rex_mtd ("^(dhcp|static|none|manual)$"); + AUG_SET ("method", rex_mtd.match (method)); + AUG_GET ("method", method); AUG_SET ("address", (CIDRAddress (val).valid () && method == "static")); AUG_SET ("netmask", (CIDRAddress (val).isNetmask () && method == "static")); @@ -240,8 +217,7 @@ if (request.getMethod () == "PUT") { it.getValue (val); if (val.empty ()){ augeas->run_cmd ("rm " + address + "/gateway"); - } - else { + }else{ if (! CIDRAddress (val).valid () ) { std::string msg = TRANSLATE_ME ("Wrong value for 'gateway' setting or setting not expected for method %s.", method.c_str ()); log_error_audit ("Request CREATE OR UPDATE netcfg FAILED"); @@ -255,100 +231,92 @@ if (request.getMethod () == "PUT") { //DNS is array, handle differently if (it.name () == "nameservers") { if (it.category () != cxxtools::SerializationInfo::Category::Array) { - std::string err = TRANSLATE_ME ("Wrong value for DNS setting - array expected"); + std::string err = TRANSLATE_ME ("Wrong value for DNS setting - array expected"); log_error_audit ("Request CREATE OR UPDATE netcfg FAILED"); http_die ("bad-request-document", err.c_str ()); } - // get all interface from /etc/network/interfaces std::vector interfaces; std::string in = augeas->get_cmd_out_raw ("match /files/etc/network/interfaces/iface[*]"); cxxtools::split ("\n", in, std::back_inserter (interfaces)); + std::string ns_list = ""; // Build the list of nameservers - std::string ns_list; for (auto i : it) { i.getValue (val); if (!CIDRAddress (val).valid ()) { - std::string err = TRANSLATE_ME ("Wrong value for DNS setting - array of IPs expected") ; + std::string err = TRANSLATE_ME ("Wrong value for DNS setting - array of IPs expected") ; log_error_audit ("Request CREATE OR UPDATE netcfg FAILED"); http_die ("bad-request-document", err.c_str ()); } - ns_list += (ns_list.empty() ? "" : " ") + val; + if (ns_list.length ()>0){ + ns_list += " " + val; + }else{ + ns_list = val; + } } log_debug ("Nameservers list = '%s'", ns_list.c_str ()); - // Now set nameservers for each interfaces for (auto iface : interfaces) { log_debug ("Processing interface %s", iface.c_str ()); //avoid lo interface and some augtool cli weird iface if (iface.find ("lo")==std::string::npos && iface.find ("*")==std::string::npos - && iface.find ("]")!=std::string::npos) - { + && iface.find ("]")!=std::string::npos) { std::size_t found = iface.find ("]"); - std::string sub_iface = iface.substr (0, found + 1); - char *path = NULL; - if (ns_list.empty ()) { + std::string sub_iface=iface.substr (0,found+1); + char *path; + if (ns_list.empty ()){ + log_debug ("rm %s/dns-nameservers",sub_iface.c_str ()); path = zsys_sprintf ("rm %s/dns-nameservers", sub_iface.c_str ()); - } - else { + }else{ + log_debug ("set %s/dns-nameservers '%s'", + sub_iface.c_str (), ns_list.c_str ()); path = zsys_sprintf ("set %s/dns-nameservers '%s'", sub_iface.c_str (), ns_list.c_str ()); - } - log_debug("%s", path); - augeas->run_cmd (std::string (path)); - zstr_free(&path); + } + augeas->run_cmd (std::string (path)); } } handled = true; } - if (!handled) { std::string msg = TRANSLATE_ME ("Invalid option '%s'", it.name ().c_str ()); log_error_audit ("Request CREATE OR UPDATE netcfg FAILED"); http_die ("bad-request-document", msg.c_str ()); } - } //for -#undef AUG_SET + } // Commit everything - augeas->save (); + augeas->save (); // Make sure we have data that works - - nil = augeas->get_cmd_out ("ls /augeas/files/etc/network/interfaces/error"); + nil = augeas->get_cmd_out ("ls /augeas/files/etc/network/interfaces/error"); if (!nil.empty ()) { - std::string err = TRANSLATE_ME ("Syntax error in /etc/network/interfaces config file"); + std::string err = TRANSLATE_ME ("Syntax error in /etc/network/interfaces config file"); log_error_audit ("Request CREATE OR UPDATE netcfg FAILED"); http_die ("internal-error", err.c_str ()); } - - nil = augeas->get_cmd_out ("ls /augeas/files/etc/resolv.conf/error"); + nil = augeas->get_cmd_out ("ls /augeas/files/etc/resolv.conf/error"); if (!nil.empty ()) { - std::string err = TRANSLATE_ME ("Syntax error in /etc/resolv.conf config file"); + std::string err = TRANSLATE_ME ("Syntax error in /etc/resolv.conf config file"); log_error_audit ("Request CREATE OR UPDATE netcfg FAILED"); http_die ("internal-error", err.c_str ()); } log_info_audit ("Request CREATE OR UPDATE netcfg SUCCESS"); -} //"PUT" +} -std::string ip, netmask, gateway; +std::string ip, netmask, gateway, dns; AUG_GET ("method", method); AUG_GET ("address", ip); AUG_GET ("netmask", netmask); AUG_GET ("gateway", gateway); -#undef AUG_GET - -std::string dns; if (request.getMethod() == "PUT") { - std::string dnsRawData = augeas->get_cmd_out ( - "match /files/etc/network/interfaces/iface[*]/dns-nameservers", - true, " "); + std::string dnsRawData = augeas->get_cmd_out ("match /files/etc/network/interfaces/iface[*]/dns-nameservers", true, " "); std::set dnsSet; cxxtools::split (" ", dnsRawData, std::inserter (dnsSet, dnsSet.end())); diff --git a/ecpp/scan_progress.ecpp b/ecpp/scan_progress.ecpp index 35027c16c..de0f4dd7c 100644 --- a/ecpp/scan_progress.ecpp +++ b/ecpp/scan_progress.ecpp @@ -29,97 +29,113 @@ #include #include -// request progress to the fty-discovery service -// returns 0 if ok, else <0 -// if ok, output is set as the response (json payload) -static int get_scan_progress (std::string& output) +// progress request +static zmsg_t * +req_progress (zuuid_t *uuid) +{ + zmsg_t *msg = zmsg_new (); + zmsg_addstr (msg, "PROGRESS"); + zmsg_addstr (msg, zuuid_str_canonical (uuid)); + return msg; +} + +int +get_scan_progress (std::string &output) { output.clear(); // connect to mlm client auto client = mlm_pool.get(); - if (!client) { - log_error ("scan_progress: mlm_pool.get () failed."); + if (!client) + { + log_fatal ("scan_progress: mlm_pool.get () failed."); return -1; } - // send request zuuid_t *uuid = zuuid_new (); - zmsg_t *request = zmsg_new (); - zmsg_addstr (request, "PROGRESS"); - zmsg_addstr (request, zuuid_str_canonical (uuid)); + zmsg_t *request = req_progress (uuid); int rv = client->sendto ("fty-discovery", "progress", 1000, &request); - zmsg_destroy (&request); - if (rv == -1) { + if (rv == -1) + { zuuid_destroy (&uuid); - log_error ("scan_progress: client->sendto (address = '%s') failed.", "fty-discovery"); + log_fatal ("scan_progress: client->sendto (address = '%s') failed.", "fty-discovery"); return -2; } ZmsgGuard resp(client->recv (zuuid_str_canonical (uuid), 20)); zuuid_destroy (&uuid); - - if (!resp) { - log_error ("scan_progress: client->recv (timeout = '20') returned NULL"); + if (!resp) + { + log_fatal ("info: client->recv (timeout = '20') returned NULL"); return -3; } ZstrGuard result (zmsg_popstr (resp)); - if (!result) { - log_error("scan_progress: received unexpected NULL response"); - return -4; + if (result) { + if (streq (result, "OK")) { + output.append ("{"); + + ZstrGuard status (zmsg_popstr (resp)); + if(!status) return -5; + output.append ("\"status\" : \""); + output.append(status); + if(streq(status, "-1")) { + output.append("\"}"); + return 0; + } + output.append("\","); + + ZstrGuard progress (zmsg_popstr (resp)); + if (!progress) return -5; + output.append ("\"progress\" : \""); + output.append (progress); + output.append ("%\", "); + + ZstrGuard discovered (zmsg_popstr (resp)); + if(!discovered) return -5; + output.append ("\"discovered\" : \""); + output.append(discovered); + output.append("\","); + + ZstrGuard discovered_ups (zmsg_popstr (resp)); + if(!discovered_ups) return -5; + output.append ("\"ups-discovered\" : \""); + output.append(discovered_ups); + output.append("\","); + + ZstrGuard discovered_epdu (zmsg_popstr (resp)); + if(!discovered_epdu) return -5; + output.append ("\"epdu-discovered\" : \""); + output.append(discovered_epdu); + output.append("\","); + + ZstrGuard discovered_sts (zmsg_popstr (resp)); + if(!discovered_sts) return -5; + output.append ("\"sts-discovered\" : \""); + output.append(discovered_sts); + output.append("\","); + + ZstrGuard discovered_sensors (zmsg_popstr (resp)); + if(!discovered_sensors) return -5; + output.append ("\"sensors-discovered\" : \""); + output.append(discovered_sensors); + output.append("\""); + + output.append("}"); + } + else if (streq (result, "ERROR")) { + return -4; + } + else { + return -5; + } } - if (streq (result, "ERROR")) { - log_error("scan_progress: received ERROR response"); + else return -5; - } - if (!streq (result, "OK")) { - log_error("scan_progress: received unexpected response"); - return -6; - } - - // here result == "OK" - - ZstrGuard status (zmsg_popstr (resp)); - if (!status) { - log_error("scan_progress: status is NULL"); - return -7; - } - - output.append("{"); - - output.append("\"status\": \"").append(status).append("\""); - - if (!streq(status, "-1")) { - // scan details - ZstrGuard progress (zmsg_popstr (resp)); - ZstrGuard discovered (zmsg_popstr (resp)); - ZstrGuard discovered_ups (zmsg_popstr (resp)); - ZstrGuard discovered_epdu (zmsg_popstr (resp)); - ZstrGuard discovered_sts (zmsg_popstr (resp)); - ZstrGuard discovered_sensors (zmsg_popstr (resp)); - - if (!progress) { log_error("scan_progress: progress is NULL"); return -7; } - if (!discovered) { log_error("scan_progress: discovered is NULL"); return -7; } - if (!discovered_ups) { log_error("scan_progress: discovered_ups is NULL"); return -7; } - if (!discovered_epdu) { log_error("scan_progress: discovered_epdu is NULL"); return -7; } - if (!discovered_sts) { log_error("scan_progress: discovered_sts is NULL"); return -7; } - if (!discovered_sensors) { log_error("scan_progress: discovered_sensors is NULL"); return -7; } - - output.append(", \"progress\": \"").append(progress).append("%\""); - output.append(", \"discovered\": \"").append(discovered).append("\""); - output.append(", \"ups-discovered\": \"").append(discovered_ups).append("\""); - output.append(", \"epdu-discovered\": \"").append(discovered_epdu).append("\""); - output.append(", \"sts-discovered\": \"").append(discovered_sts).append("\""); - output.append(", \"sensors-discovered\": \"").append(discovered_sensors).append("\""); - } - - output.append("}"); return 0; } - <%request scope="global"> UserInfo user; @@ -127,19 +143,38 @@ UserInfo user; <%cpp> // permission check static const std::map PERMISSIONS = { - {BiosProfile::Anonymous, "R"}, - {BiosProfile::Dashboard, "R"}, - {BiosProfile::Admin, "R"} - }; + {BiosProfile::Anonymous, "R"}, + {BiosProfile::Dashboard, "R"}, + {BiosProfile::Admin, "R"} + }; CHECK_USER_PERMISSIONS_OR_DIE (PERMISSIONS); std::string output; int rv = get_scan_progress (output); - if (rv != 0) { - log_error("get_scan_progress() failed (rv: %d)", rv); - std::string err = TRANSLATE_ME("Automatic discovery service not available"); - http_die ("internal-error", err.c_str()); + if (rv == -1) { + log_error("mlmpool.get () failed."); + http_die ("internal-error", TRANSLATE_ME("Automatic discovery service not available")); } - - reply.out () << output; + else + if (rv == -2) { + log_error("client->sendto () failed."); + http_die ("internal-error", TRANSLATE_ME("Automatic discovery service not available")); + } + else + if (rv == -3) { + log_error("client->recv () failed."); + http_die ("internal-error", TRANSLATE_ME("Automatic discovery service not available")); + } + else + if (rv == -4) { + log_error("fty-discovery returned error."); + http_die ("internal-error", TRANSLATE_ME("Automatic discovery service not available")); + } + else + if (rv == -5) { + log_error("fty-discovery returned malformed or unexpected message."); + http_die ("internal-error", TRANSLATE_ME("Automatic discovery service not available")); + } + else +<$$ output $> diff --git a/ecpp/time.ecpp b/ecpp/time.ecpp index 3a59772b7..3f98c8264 100644 --- a/ecpp/time.ecpp +++ b/ecpp/time.ecpp @@ -117,22 +117,7 @@ UserInfo user; } // input arguments sanitization end - const bool sudoer = (request.getMethod () != "GET"); // augtool require sudo privileges? - augtool* augeas = nullptr; - int counter = 0; - - while(augeas == nullptr) - { - augeas = augtool::get_instance (sudoer); - counter++; - if(counter >= 10) - { - break; - } - } - - - + augtool* augeas = augtool::get_instance (); if (!augeas) { std::string err = TRANSLATE_ME ("Cannot communicate with augtool. Is it installed or properly configured?"); if (request.getMethod () == "POST") @@ -210,21 +195,18 @@ UserInfo user; } } - char time_out[32]; - memset(time_out, 0, sizeof(time_out)); - if (calendar_to_datetime (time (NULL), time_out, sizeof(time_out)) == -1) { + char buff[32]; + if (calendar_to_datetime (time (NULL), buff, 32) == -1) { std::string err = TRANSLATE_ME ("calendar_to_datetime () failed."); if (request.getMethod () == "POST") log_error_audit ("Request CREATE time %s %s FAILED", checked_time.c_str (), checked_ntp.c_str ()); http_die ("internal-error", err.c_str ()); } - std::string ntp_out = augeas->get_cmd_out ("get /files/etc/ntp.conf/server[1]"); - if (request.getMethod () == "POST") log_info_audit ("Request CREATE time %s %s SUCCESS", checked_time.c_str (), checked_ntp.c_str ()); { - <$$ utils::json::jsonify ("time", time_out) $>, + <$$ utils::json::jsonify ("time", buff) $>, <$$ utils::json::jsonify ("ntp", ntp_out) $> } diff --git a/packaging/debian/control b/packaging/debian/control index a564408f6..4a8797138 100644 --- a/packaging/debian/control +++ b/packaging/debian/control @@ -33,7 +33,7 @@ Build-Depends: debhelper (>= 9), libmagic-dev, libczmq-dev (>= 3.0.2), libmlm-dev, - libcidr0-dev, + libcidr-dev | libcidr0-dev, libcxxtools-dev, libtntnet-dev, libtntdb-dev, diff --git a/src/shared/augtool.cc b/src/shared/augtool.cc index ad9c2497c..6f5353861 100644 --- a/src/shared/augtool.cc +++ b/src/shared/augtool.cc @@ -23,178 +23,118 @@ * \author Michal Hrusecky * \brief Not yet documented file */ -#include -#include #include -#include #include #include +#include #include #include #include +#include #include "shared/augtool.h" -// execute cmd, returns raw output -std::string augtool::get_cmd_out_raw(const std::string& cmd) -{ - std::lock_guard lock(m_cmd_mutex); - - std::string ret; // empty, default - - if (m_process) { - auto command{cmd}; - if (command.empty() || (command.back() != '\n')) { - command += "\n"; - } - - /// execute the actual command - auto writeSuccess = m_process->write(command); - ret = m_process->readAllStandardOutput(500); - if (!writeSuccess) { - ret.clear(); +//using namespace shared; + +std::string augtool::get_cmd_out(std::string cmd, bool key_value, + std::string sep, + std::function filter) { + std::string in = get_cmd_out_raw(cmd); + std::vector spl = fty::split(in, "\n"); + bool not_first = false; + std::string out; + if(spl.size() >= 3) { + spl.erase(spl.begin()); + spl.pop_back(); + } else { + return out; + } + for(auto i : spl) { + auto pos = i.find_first_of("="); + if(pos == std::string::npos) { + if(key_value) + continue; + if(not_first) + out += sep; + if(filter(i)) + continue; + out += i; + } else { + if(not_first) + out += sep; + if(filter(i.substr(pos+2))) + continue; + out += i.substr(pos+2); } + not_first = true; } - return ret; + return out; } -// execute cmd, returns post-processed output -std::string augtool::get_cmd_out( - const std::string& cmd, - bool key_value, // process only ' = ' output - const std::string& sep, // list separator - std::function filter // returns true to exclude value -) -{ - logDebug("augtool: '{}', key_value: {}, sep: '{}'", cmd, key_value, sep); - - // execute the command - std::string cmdOutput = get_cmd_out_raw(cmd); - // split output into lines - std::vector lines = fty::split(cmdOutput, "\n"); +std::string augtool::get_cmd_out_raw(std::string command) { + std::string ret; + bool err = false; - std::string ret; // empty, default - - // expect at least 3 lines - if (lines.size() >= 3) { - // remove first and last prompts lines ("match...", "augtool>") - lines.erase(lines.begin()); - lines.pop_back(); - - // built ret - for (auto line : lines) { - auto pos = line.find_first_of("="); - if (pos != std::string::npos) { - // extract value (right of '=' leaving 1st space) - line = line.substr(pos + 2); - } - else { - if (key_value) { // key_value only? - continue; // ignore not ' = ' line - } - } - - // apply exclusion filter - if (filter(line)) { - continue; // ignore line - } - - if (line.empty()) { // inconsistent? - logDebug("adding an empty line!"); - } + std::lock_guard lock(mux); + if (init()) { + if(command.empty() || command.back() != '\n') { + command += "\n"; + } - ret += (ret.empty() ? "" : sep) + line; + if(!prc->write(command)) { + err = true; } + + ret = prc->readAllStandardOutput(500); + return err ? "" : ret; } + return ""; +} - logDebug("augtool: '{}', ret: '{}'", cmd, ret); - return ret; +void augtool::run_cmd(std::string cmd) { + get_cmd_out_raw(cmd); } -// returns process instance -augtool* augtool::get_instance(bool sudoer) -{ - static augtool instance_sudoer(true); // privileges escalation - static augtool instance_nopriv(false); // no escalation - - logInfo("Augtool get_instance(), sudoer: {}", sudoer); - - //get the correct instance () - auto instance = sudoer ? &instance_sudoer : &instance_nopriv; - - //Run the init - logInfo("Check that Augeas is initialise"); - - if(!instance->init(sudoer)) { - logError("Augeas could not be initilized"); - return nullptr; - } - - /// refresh instance before returning it - instance->load(); +static std::mutex clear_mux; - return instance; +void augtool::clear() { + std::lock_guard lock(clear_mux); + run_cmd(""); + run_cmd("load"); } -augtool::augtool(bool sudoer) noexcept -{ - init(sudoer); +augtool* augtool::get_instance() { + static augtool inst; + return &inst; } -bool augtool::init(bool sudoer) noexcept +augtool::augtool() { - try { - if (m_process) { // once - return true; - } + logDebug("new Process"); + init(); +} - m_process = (sudoer) - ? new fty::Process("sudo", {"augtool", "-S", "-I/usr/share/fty/lenses", "-e"}) - : new fty::Process("augtool", { "-S", "-I/usr/share/fty/lenses", "-e"}); +bool augtool::init() +{ + if (prc) { + return true; + } - if (!m_process) { - throw std::runtime_error("new failed"); - } + prc = new fty::Process("sudo", {"augtool", "-S", "-I/usr/share/fty/lenses", "-e"}); - if (!m_process->exists()) { - if (auto ret = m_process->run()) { - std::string output = get_cmd_out_raw("help"); - if (output.find("match") == output.npos) { - throw std::runtime_error("unexpected help payload"); - } - } - else { - throw std::runtime_error("run failed (" + ret.error() + ")"); + if(!prc->exists()) { + if (prc->run()) { + std::string nil = get_cmd_out_raw("help"); + if(nil.find("match") == nil.npos) { + logError("augtool returned unexpected output {}", nil); + delete prc; + prc = NULL; + return false; } } - - load(); - - logDebug("augtool init succeeded (sudoer: {})", sudoer); - return true; - } - catch (const std::exception& e) { - logFatal("augtool init (sudoer: {}) caught exception (e: {})", sudoer, e.what()); - } - - if (m_process) { - delete m_process; - m_process = nullptr; } - return false; -} - -bool augtool::initialized() -{ - return (m_process != nullptr); -} - -void augtool::load() -{ - static std::mutex load_mutex; - std::lock_guard lock(load_mutex); - run_cmd(""); - run_cmd("load"); + clear(); + return true; } diff --git a/src/shared/augtool.h b/src/shared/augtool.h index 99df8a20d..843ab9844 100644 --- a/src/shared/augtool.h +++ b/src/shared/augtool.h @@ -32,8 +32,11 @@ class augtool { public: - /// Singleton get_instance method (w/ privileges escalation option) - static augtool* get_instance(bool sudoer); + /// Singleton get_instance method + static augtool* get_instance(); + + /// Runs command without returning anything + void run_cmd(std::string cmd); /// Method returning parsed output of augtool /// @@ -45,20 +48,15 @@ class augtool /// @param sep used to separate individual values /// @param filter values for which it returns true are omitted std::string get_cmd_out( - const std::string& cmd, + std::string cmd, bool key_value = true, - const std::string& sep = "", - std::function filter = [](const std::string) -> bool { return false; } - ); + std::string sep = "", + std::function filter = [](const std::string) -> bool { + return false; + }); /// Return string directly as returned from augtool - std::string get_cmd_out_raw(const std::string& cmd); - - /// Runs command without returning anything - void run_cmd(const std::string& cmd) - { - get_cmd_out_raw(cmd); - } + std::string get_cmd_out_raw(std::string cmd); /// Saves current state void save() @@ -67,13 +65,11 @@ class augtool } protected: - std::mutex m_cmd_mutex; //!< Shared mutex, to protect cmd overlap - fty::Process* m_process{nullptr}; //!< Subprocess itself + std::mutex mux; //!< Shared mutex + fty::Process* prc; //!< Subprocess itself /// Ensures we are in reasonably clean state - augtool(bool sudoer) noexcept; - bool init(bool sudoer) noexcept; - bool initialized(); - - void load(); + void clear(); + augtool(); + bool init(); };