You can put your own configuration files in the [conf.d](4-configuring-icinga-2.md#conf-d) directory. This
directive makes sure that all of your own configuration files are included.
- /**
- * The zones.d directory contains configuration files for satellite
- * instances.
- */
- include_zones "etc", "zones.d"
-
-Configuration files for satellite instances are managed in 'zones'. This directive ensures
-that all configuration files in the `zones.d` directory are included and that the `zones`
-attribute for objects defined in this directory is set appropriately.
-
### <a id="constants-conf"></a> constants.conf
The `constants.conf` configuration file can be used to define global constants.
* directory. Each of these files must have the file extension ".conf".
*/
include_recursive "conf.d"
-
-/**
- * The zones.d directory contains configuration files for satellite
- * instances.
- */
-include_zones "etc", "zones.d"
return ret;
}
+void Utility::RemoveDirRecursive(const String& path)
+{
+ std::vector<String> paths;
+ Utility::GlobRecursive(path, "*", boost::bind(&Utility::CollectPaths, _1, boost::ref(paths)), GlobFile | GlobDirectory);
+
+ /* This relies on the fact that GlobRecursive lists the parent directory
+ first before recursing into subdirectories. */
+ std::reverse(paths.begin(), paths.end());
+
+ BOOST_FOREACH(const String& path, paths) {
+ if (remove(path.CStr()) < 0)
+ BOOST_THROW_EXCEPTION(posix_error()
+ << boost::errinfo_api_function("remove")
+ << boost::errinfo_errno(errno)
+ << boost::errinfo_file_name(path));
+ }
+
+#ifndef _WIN32
+ if (rmdir(path.CStr()) < 0)
+#else /* _WIN32 */
+ if (_rmdir(path.CStr()) < 0)
+#endif /* _WIN32 */
+ BOOST_THROW_EXCEPTION(posix_error()
+ << boost::errinfo_api_function("rmdir")
+ << boost::errinfo_errno(errno)
+ << boost::errinfo_file_name(path));
+}
+
+void Utility::CollectPaths(const String& path, std::vector<String>& paths)
+{
+ paths.push_back(path);
+}
+
void Utility::CopyFile(const String& source, const String& target)
{
std::ifstream ifs(source.CStr(), std::ios::binary);
static bool PathExists(const String& path);
+ static void RemoveDirRecursive(const String& path);
static void CopyFile(const String& source, const String& target);
static Value LoadJsonFile(const String& path);
private:
Utility(void);
+ static void CollectPaths(const String& path, std::vector<String>& paths);
static boost::thread_specific_ptr<String> m_ThreadName;
static boost::thread_specific_ptr<unsigned int> m_RandSeed;
-
};
}
* unfortunately moving it there is somewhat non-trivial. */
success = true;
+ String zonesEtcDir = Application::GetZonesDir();
+ if (!zonesEtcDir.IsEmpty() && Utility::PathExists(zonesEtcDir))
+ Utility::Glob(zonesEtcDir + "/*", boost::bind(&IncludeZoneDirRecursive, _1, boost::ref(success)), GlobDirectory);
+
+ if (!success)
+ return false;
+
String zonesVarDir = Application::GetLocalStateDir() + "/lib/icinga2/api/zones";
if (Utility::PathExists(zonesVarDir))
Utility::Glob(zonesVarDir + "/*", boost::bind(&IncludeNonLocalZone, _1, boost::ref(success)), GlobDirectory);
+ if (!success)
+ return false;
+
+ String modulesVarDir = Application::GetLocalStateDir() + "/lib/icinga2/api/modules";
+ if (Utility::PathExists(modulesVarDir)) {
+ std::vector<Expression *> expressions;
+ Utility::Glob(modulesVarDir + "/*/include.conf", boost::bind(&ConfigCompiler::CollectIncludes, boost::ref(expressions), _1, ""), GlobFile);
+ DictExpression expr(expressions);
+ if (!ExecuteExpression(&expr))
+ success = false;
+ }
+
if (!success)
return false;
set(remote_SOURCES
apifunction.cpp apilistener.cpp apilistener.thpp apilistener-sync.cpp
- apiuser.cpp apiuser.thpp authority.cpp base64.cpp endpoint.cpp endpoint.thpp
+ apiuser.cpp apiuser.thpp authority.cpp base64.cpp configfileshandler.cpp
+ configmoduleshandler.cpp configmoduleutility.cpp configstageshandler.cpp
+ endpoint.cpp endpoint.thpp
httpchunkedencoding.cpp httpconnection.cpp httpdemohandler.cpp httphandler.cpp httprequest.cpp httpresponse.cpp
- jsonrpc.cpp jsonrpcconnection.cpp jsonrpcconnection-heartbeat.cpp
+ httputility.cpp jsonrpc.cpp jsonrpcconnection.cpp jsonrpcconnection-heartbeat.cpp
messageorigin.cpp zone.cpp zone.thpp
url.cpp
)
--- /dev/null
+/******************************************************************************
+ * Icinga 2 *
+ * Copyright (C) 2012-2015 Icinga Development Team (http://www.icinga.org) *
+ * *
+ * 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 the Free Software Foundation; either version 2 *
+ * of the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the Free Software Foundation *
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ******************************************************************************/
+
+#include "remote/configfileshandler.hpp"
+#include "remote/configmoduleutility.hpp"
+#include "remote/httputility.hpp"
+#include "base/exception.hpp"
+#include <boost/algorithm/string/join.hpp>
+#include <fstream>
+
+using namespace icinga;
+
+REGISTER_URLHANDLER("/v1/config/files", ConfigFilesHandler);
+
+void ConfigFilesHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response)
+{
+ if (request.RequestMethod == "GET")
+ HandleGet(user, request, response);
+ else
+ response.SetStatus(400, "Bad request");
+}
+
+bool ConfigFilesHandler::CanAlsoHandleUrl(const Url::Ptr& url) const
+{
+ return true;
+}
+
+void ConfigFilesHandler::HandleGet(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response)
+{
+ Dictionary::Ptr params = HttpUtility::FetchRequestParameters(request);
+
+ const std::vector<String>& urlPath = request.RequestUrl->GetPath();
+
+ if (urlPath.size() >= 4)
+ params->Set("module", urlPath[3]);
+
+ if (urlPath.size() >= 5)
+ params->Set("stage", urlPath[4]);
+
+ if (urlPath.size() >= 6) {
+ std::vector<String> tmpPath(urlPath.begin() + 5, urlPath.end());
+ params->Set("path", boost::algorithm::join(tmpPath, "/"));
+ }
+
+ String moduleName = params->Get("module");
+ String stageName = params->Get("stage");
+
+ if (!ConfigModuleUtility::ValidateName(moduleName) || !ConfigModuleUtility::ValidateName(stageName)) {
+ response.SetStatus(403, "Forbidden");
+ return;
+ }
+
+ String relativePath = params->Get("path");
+
+ if (ConfigModuleUtility::ContainsDotDot(relativePath)) {
+ response.SetStatus(403, "Forbidden");
+ return;
+ }
+
+ String path = ConfigModuleUtility::GetModuleDir() + "/" + moduleName + "/" + stageName + "/" + relativePath;
+
+ if (!Utility::PathExists(path)) {
+ response.SetStatus(404, "File not found");
+ return;
+ }
+
+ try {
+ std::ifstream fp(path.CStr(), std::ifstream::in | std::ifstream::binary);
+ fp.exceptions(std::ifstream::badbit);
+
+ String content((std::istreambuf_iterator<char>(fp)), std::istreambuf_iterator<char>());
+ response.SetStatus(200, "OK");
+ response.AddHeader("Content-Type", "application/octet-stream");
+ response.WriteBody(content.CStr(), content.GetLength());
+ } catch (const std::exception& ex) {
+ response.SetStatus(503, "Could not read file");
+ }
+}
+
+
--- /dev/null
+/******************************************************************************
+ * Icinga 2 *
+ * Copyright (C) 2012-2015 Icinga Development Team (http://www.icinga.org) *
+ * *
+ * 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 the Free Software Foundation; either version 2 *
+ * of the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the Free Software Foundation *
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ******************************************************************************/
+
+#ifndef CONFIGFILESHANDLER_H
+#define CONFIGFILESHANDLER_H
+
+#include "remote/httphandler.hpp"
+
+namespace icinga
+{
+
+class I2_REMOTE_API ConfigFilesHandler : public HttpHandler
+{
+public:
+ DECLARE_PTR_TYPEDEFS(ConfigFilesHandler);
+
+ virtual bool CanAlsoHandleUrl(const Url::Ptr& url) const;
+ virtual void HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response);
+
+private:
+ void HandleGet(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response);
+};
+
+}
+
+#endif /* CONFIGFILESHANDLER_H */
--- /dev/null
+/******************************************************************************
+ * Icinga 2 *
+ * Copyright (C) 2012-2015 Icinga Development Team (http://www.icinga.org) *
+ * *
+ * 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 the Free Software Foundation; either version 2 *
+ * of the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the Free Software Foundation *
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ******************************************************************************/
+
+#include "remote/configmoduleshandler.hpp"
+#include "remote/configmoduleutility.hpp"
+#include "remote/httputility.hpp"
+#include "base/exception.hpp"
+
+using namespace icinga;
+
+REGISTER_URLHANDLER("/v1/config/modules", ConfigModulesHandler);
+
+void ConfigModulesHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response)
+{
+ if (request.RequestMethod == "GET")
+ HandleGet(user, request, response);
+ else if (request.RequestMethod == "POST")
+ HandlePost(user, request, response);
+ else if (request.RequestMethod == "DELETE")
+ HandleDelete(user, request, response);
+ else
+ response.SetStatus(400, "Bad request");
+}
+
+bool ConfigModulesHandler::CanAlsoHandleUrl(const Url::Ptr& url) const
+{
+ if (url->GetPath().size() > 4)
+ return false;
+ else
+ return true;
+}
+
+void ConfigModulesHandler::HandleGet(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response)
+{
+ std::vector<String> modules = ConfigModuleUtility::GetModules();
+
+ Array::Ptr results = new Array();
+
+ BOOST_FOREACH(const String& module, modules) {
+ Dictionary::Ptr moduleInfo = new Dictionary();
+ moduleInfo->Set("name", module);
+ moduleInfo->Set("stages", Array::FromVector(ConfigModuleUtility::GetStages(module)));
+ moduleInfo->Set("active-stage", ConfigModuleUtility::GetActiveStage(module));
+ results->Add(moduleInfo);
+ }
+
+ Dictionary::Ptr result = new Dictionary();
+ result->Set("results", results);
+
+ response.SetStatus(200, "OK");
+ HttpUtility::SendJsonBody(response, result);
+}
+
+void ConfigModulesHandler::HandlePost(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response)
+{
+ Dictionary::Ptr params = HttpUtility::FetchRequestParameters(request);
+
+ if (request.RequestUrl->GetPath().size() >= 4)
+ params->Set("module", request.RequestUrl->GetPath()[3]);
+
+ String moduleName = params->Get("module");
+
+ if (!ConfigModuleUtility::ValidateName(moduleName)) {
+ response.SetStatus(403, "Forbidden");
+ return;
+ }
+
+ int code = 200;
+ String status = "Created module.";
+
+ try {
+ ConfigModuleUtility::CreateModule(moduleName);
+ } catch (const std::exception& ex) {
+ code = 501;
+ status = "Error: " + DiagnosticInformation(ex);
+ }
+
+ Dictionary::Ptr result1 = new Dictionary();
+
+ result1->Set("module", moduleName);
+ result1->Set("code", code);
+ result1->Set("status", status);
+
+ Array::Ptr results = new Array();
+ results->Add(result1);
+
+ Dictionary::Ptr result = new Dictionary();
+ result->Set("results", results);
+
+ response.SetStatus(code, (code == 200) ? "OK" : "Error");
+ HttpUtility::SendJsonBody(response, result);
+}
+
+void ConfigModulesHandler::HandleDelete(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response)
+{
+ Dictionary::Ptr params = HttpUtility::FetchRequestParameters(request);
+
+ if (request.RequestUrl->GetPath().size() >= 4)
+ params->Set("module", request.RequestUrl->GetPath()[3]);
+
+ String moduleName = params->Get("module");
+
+ if (!ConfigModuleUtility::ValidateName(moduleName)) {
+ response.SetStatus(403, "Forbidden");
+ return;
+ }
+
+ int code = 200;
+ String status = "Deleted module.";
+
+ try {
+ ConfigModuleUtility::DeleteModule(moduleName);
+ } catch (const std::exception& ex) {
+ code = 501;
+ status = "Error: " + DiagnosticInformation(ex);
+ }
+
+ Dictionary::Ptr result1 = new Dictionary();
+
+ result1->Set("module", moduleName);
+ result1->Set("code", code);
+ result1->Set("status", status);
+
+ Array::Ptr results = new Array();
+ results->Add(result1);
+
+ Dictionary::Ptr result = new Dictionary();
+ result->Set("results", results);
+
+ response.SetStatus(code, (code == 200) ? "OK" : "Error");
+ HttpUtility::SendJsonBody(response, result);
+}
+
--- /dev/null
+/******************************************************************************
+ * Icinga 2 *
+ * Copyright (C) 2012-2015 Icinga Development Team (http://www.icinga.org) *
+ * *
+ * 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 the Free Software Foundation; either version 2 *
+ * of the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the Free Software Foundation *
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ******************************************************************************/
+
+#ifndef CONFIGMODULESHANDLER_H
+#define CONFIGMODULESHANDLER_H
+
+#include "remote/httphandler.hpp"
+
+namespace icinga
+{
+
+class I2_REMOTE_API ConfigModulesHandler : public HttpHandler
+{
+public:
+ DECLARE_PTR_TYPEDEFS(ConfigModulesHandler);
+
+ virtual bool CanAlsoHandleUrl(const Url::Ptr& url) const;
+ virtual void HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response);
+
+private:
+ void HandleGet(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response);
+ void HandlePost(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response);
+ void HandleDelete(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response);
+
+};
+
+}
+
+#endif /* CONFIGMODULESHANDLER_H */
--- /dev/null
+/******************************************************************************
+ * Icinga 2 *
+ * Copyright (C) 2012-2015 Icinga Development Team (http://www.icinga.org) *
+ * *
+ * 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 the Free Software Foundation; either version 2 *
+ * of the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the Free Software Foundation *
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ******************************************************************************/
+
+#include "remote/configmoduleutility.hpp"
+#include "base/application.hpp"
+#include "base/exception.hpp"
+#include "base/scriptglobal.hpp"
+#include "base/utility.hpp"
+#include "boost/foreach.hpp"
+#include <boost/algorithm/string.hpp>
+#include <boost/regex.hpp>
+#include <algorithm>
+#include <fstream>
+
+using namespace icinga;
+
+String ConfigModuleUtility::GetModuleDir(void)
+{
+ return Application::GetLocalStateDir() + "/lib/icinga2/api/modules";
+}
+
+void ConfigModuleUtility::CreateModule(const String& name)
+{
+ String path = GetModuleDir() + "/" + name;
+
+ if (Utility::PathExists(path))
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Module already exists."));
+
+ Utility::MkDirP(path, 0700);
+ WriteModuleConfig(name);
+}
+
+void ConfigModuleUtility::DeleteModule(const String& name)
+{
+ String path = GetModuleDir() + "/" + name;
+
+ if (!Utility::PathExists(path))
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Module does not exist."));
+
+ Utility::RemoveDirRecursive(path);
+ Application::RequestRestart();
+}
+
+std::vector<String> ConfigModuleUtility::GetModules(void)
+{
+ std::vector<String> modules;
+ Utility::Glob(GetModuleDir() + "/*", boost::bind(&ConfigModuleUtility::CollectDirNames, _1, boost::ref(modules)), GlobDirectory);
+ return modules;
+}
+
+void ConfigModuleUtility::CollectDirNames(const String& path, std::vector<String>& dirs)
+{
+ String name = Utility::BaseName(path);
+ dirs.push_back(name);
+}
+
+String ConfigModuleUtility::CreateStage(const String& moduleName, const Dictionary::Ptr& files)
+{
+ String stageName = Utility::NewUniqueID();
+
+ String path = GetModuleDir() + "/" + moduleName;
+
+ if (!Utility::PathExists(path))
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Module does not exist."));
+
+ path += "/" + stageName;
+
+ Utility::MkDirP(path, 0700);
+ WriteStageConfig(moduleName, stageName);
+
+ ObjectLock olock(files);
+ BOOST_FOREACH(const Dictionary::Pair& kv, files) {
+ if (ContainsDotDot(kv.first))
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Path must not contain '..'."));
+
+ String filePath = path + "/" + kv.first;
+
+ Log(LogInformation, "ConfigModuleUtility")
+ << "Updating configuration file: " << filePath;
+
+ //pass the directory and generate a dir tree, if not existing already
+ Utility::MkDirP(Utility::DirName(filePath), 0750);
+ std::ofstream fp(filePath.CStr(), std::ofstream::out | std::ostream::binary | std::ostream::trunc);
+ fp << kv.second;
+ fp.close();
+ }
+
+ return stageName;
+}
+
+void ConfigModuleUtility::WriteModuleConfig(const String& moduleName)
+{
+ String stageName = GetActiveStage(moduleName);
+
+ String includePath = GetModuleDir() + "/" + moduleName + "/include.conf";
+ std::ofstream fpInclude(includePath.CStr(), std::ofstream::out | std::ostream::binary | std::ostream::trunc);
+ fpInclude << "include \"*/include.conf\"\n";
+ fpInclude.close();
+
+ String activePath = GetModuleDir() + "/" + moduleName + "/active.conf";
+ std::ofstream fpActive(activePath.CStr(), std::ofstream::out | std::ostream::binary | std::ostream::trunc);
+ fpActive << "if (!globals.contains(\"ActiveStages\")) {\n"
+ << " globals.ActiveStages = {}\n"
+ << "}\n"
+ << "\n"
+ << "if (globals.contains(\"ActiveStageOverride\")) {\n"
+ << " var arr = ActiveStageOverride.split(\":\")\n"
+ << " if (arr[0] == \"" << moduleName << "\") {\n"
+ << " if (arr.len() < 2) {\n"
+ << " log(LogCritical, \"Config\", \"Invalid value for ActiveStageOverride\")\n"
+ << " } else {\n"
+ << " ActiveStages[\"" << moduleName << "\"] = arr[1]\n"
+ << " }\n"
+ << " }\n"
+ << "}\n"
+ << "\n"
+ << "if (!ActiveStages.contains(\"" << moduleName << "\")) {\n"
+ << " ActiveStages[\"" << moduleName << "\"] = \"" << stageName << "\"\n"
+ << "}\n";
+ fpActive.close();
+}
+
+void ConfigModuleUtility::WriteStageConfig(const String& moduleName, const String& stageName)
+{
+ String path = GetModuleDir() + "/" + moduleName + "/" + stageName + "/include.conf";
+ std::ofstream fp(path.CStr(), std::ofstream::out | std::ostream::binary | std::ostream::trunc);
+ fp << "include \"../active.conf\"\n"
+ << "if (ActiveStages[\"" << moduleName << "\"] == \"" << stageName << "\") {\n"
+ << " include_recursive \"conf.d\"\n"
+ << " include_zones \"" << moduleName << "\", \"zones.d\"\n"
+ << "}\n";
+ fp.close();
+}
+
+void ConfigModuleUtility::ActivateStage(const String& moduleName, const String& stageName)
+{
+ String activeStagePath = GetModuleDir() + "/" + moduleName + "/active-stage";
+ std::ofstream fpActiveStage(activeStagePath.CStr(), std::ofstream::out | std::ostream::binary | std::ostream::trunc);
+ fpActiveStage << stageName;
+ fpActiveStage.close();
+
+ WriteModuleConfig(moduleName);
+}
+
+void ConfigModuleUtility::TryActivateStageCallback(const ProcessResult& pr, const String& moduleName, const String& stageName)
+{
+ String logFile = GetModuleDir() + "/" + moduleName + "/" + stageName + "/startup.log";
+ std::ofstream fpLog(logFile.CStr(), std::ofstream::out | std::ostream::binary | std::ostream::trunc);
+ fpLog << pr.Output;
+ fpLog.close();
+
+ String statusFile = GetModuleDir() + "/" + moduleName + "/" + stageName + "/status";
+ std::ofstream fpStatus(statusFile.CStr(), std::ofstream::out | std::ostream::binary | std::ostream::trunc);
+ fpStatus << pr.ExitStatus;
+ fpStatus.close();
+
+ /* validation went fine, activate stage and reload */
+ if (pr.ExitStatus == 0) {
+ ActivateStage(moduleName, stageName);
+ Application::RequestRestart();
+ } else {
+ Log(LogCritical, "ConfigModuleUtility")
+ << "Config validation failed for module '"
+ << moduleName << "' and stage '" << stageName << "'.";
+ }
+}
+
+void ConfigModuleUtility::AsyncTryActivateStage(const String& moduleName, const String& stageName)
+{
+ // prepare arguments
+ Array::Ptr args = new Array();
+ args->Add(Application::GetExePath("icinga2"));
+ args->Add("daemon");
+ args->Add("--validate");
+ args->Add("--define");
+ args->Add("ActiveStageOverride=" + moduleName + ":" + stageName);
+
+ Process::Ptr process = new Process(Process::PrepareCommand(args));
+ process->SetTimeout(300);
+ process->Run(boost::bind(&TryActivateStageCallback, _1, moduleName, stageName));
+}
+
+void ConfigModuleUtility::DeleteStage(const String& moduleName, const String& stageName)
+{
+ String path = GetModuleDir() + "/" + moduleName + "/" + stageName;
+
+ if (!Utility::PathExists(path))
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Stage does not exist."));
+
+ if (GetActiveStage(moduleName) == stageName)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Active stage cannot be deleted."));
+
+ Utility::RemoveDirRecursive(path);
+}
+
+std::vector<String> ConfigModuleUtility::GetStages(const String& moduleName)
+{
+ std::vector<String> stages;
+ Utility::Glob(GetModuleDir() + "/" + moduleName + "/*", boost::bind(&ConfigModuleUtility::CollectDirNames, _1, boost::ref(stages)), GlobDirectory);
+ return stages;
+}
+
+String ConfigModuleUtility::GetActiveStage(const String& moduleName)
+{
+ String path = GetModuleDir() + "/" + moduleName + "/active-stage";
+
+ std::ifstream fp;
+ fp.open(path.CStr());
+
+ String stage;
+ std::getline(fp, stage.GetData());
+ stage.Trim();
+
+ fp.close();
+
+ if (fp.fail())
+ return "";
+
+ return stage;
+}
+
+
+std::vector<std::pair<String, bool> > ConfigModuleUtility::GetFiles(const String& moduleName, const String& stageName)
+{
+ std::vector<std::pair<String, bool> > paths;
+ Utility::GlobRecursive(GetModuleDir() + "/" + moduleName + "/" + stageName, "*", boost::bind(&ConfigModuleUtility::CollectPaths, _1, boost::ref(paths)), GlobDirectory | GlobFile);
+
+ return paths;
+}
+
+void ConfigModuleUtility::CollectPaths(const String& path, std::vector<std::pair<String, bool> >& paths)
+{
+#ifndef _WIN32
+ struct stat statbuf;
+ int rc = lstat(path.CStr(), &statbuf);
+ if (rc < 0)
+ BOOST_THROW_EXCEPTION(posix_error()
+ << boost::errinfo_api_function("lstat")
+ << boost::errinfo_errno(errno)
+ << boost::errinfo_file_name(path));
+#else /* _WIN32 */
+ struct _stat statbuf;
+ int rc = _stat(path.CStr(), &statbuf);
+ if (rc < 0)
+ BOOST_THROW_EXCEPTION(posix_error()
+ << boost::errinfo_api_function("_stat")
+ << boost::errinfo_errno(errno)
+ << boost::errinfo_file_name(path));
+#endif /* _WIN32 */
+
+ paths.push_back(std::make_pair(path, S_ISDIR(statbuf.st_mode)));
+}
+
+bool ConfigModuleUtility::ContainsDotDot(const String& path)
+{
+ std::vector<String> tokens;
+ boost::algorithm::split(tokens, path, boost::is_any_of("/\\"));
+
+ BOOST_FOREACH(const String& part, tokens) {
+ if (part == "..")
+ return true;
+ }
+
+ return false;
+}
+
+bool ConfigModuleUtility::ValidateName(const String& name)
+{
+ if (name.IsEmpty())
+ return false;
+
+ /* check for path injection */
+ if (ContainsDotDot(name))
+ return false;
+
+ boost::regex expr("^[^a-zA-Z0-9_\\-]*$", boost::regex::icase);
+ boost::smatch what;
+ return (!boost::regex_search(name.GetData(), what, expr));
+}
+
+
--- /dev/null
+/******************************************************************************
+ * Icinga 2 *
+ * Copyright (C) 2012-2015 Icinga Development Team (http://www.icinga.org) *
+ * *
+ * 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 the Free Software Foundation; either version 2 *
+ * of the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the Free Software Foundation *
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ******************************************************************************/
+
+#ifndef CONFIGMODULEUTILITY_H
+#define CONFIGMODULEUTILITY_H
+
+#include "remote/i2-remote.hpp"
+#include "base/application.hpp"
+#include "base/dictionary.hpp"
+#include "base/process.hpp"
+#include "base/string.hpp"
+#include <vector>
+
+namespace icinga
+{
+
+/**
+ * Helper functions.
+ *
+ * @ingroup remote
+ */
+class I2_REMOTE_API ConfigModuleUtility
+{
+
+public:
+ static String GetModuleDir(void);
+
+ static void CreateModule(const String& name);
+ static void DeleteModule(const String& name);
+ static std::vector<String> GetModules(void);
+
+ static String CreateStage(const String& moduleName, const Dictionary::Ptr& files);
+ static void DeleteStage(const String& moduleName, const String& stageName);
+ static std::vector<String> GetStages(const String& moduleName);
+ static String GetActiveStage(const String& moduleName);
+ static void ActivateStage(const String& moduleName, const String& stageName);
+ static void AsyncTryActivateStage(const String& moduleName, const String& stageName);
+
+ static std::vector<std::pair<String, bool> > GetFiles(const String& moduleName, const String& stageName);
+
+ static bool ContainsDotDot(const String& path);
+ static bool ValidateName(const String& name);
+
+private:
+ static void CollectDirNames(const String& path, std::vector<String>& dirs);
+ static void CollectPaths(const String& path, std::vector<std::pair<String, bool> >& paths);
+
+ static void WriteModuleConfig(const String& moduleName);
+ static void WriteStageConfig(const String& moduleName, const String& stageName);
+
+ static void TryActivateStageCallback(const ProcessResult& pr, const String& moduleName, const String& stageName);
+};
+
+}
+
+#endif /* CONFIGMODULEUTILITY_H */
--- /dev/null
+/******************************************************************************
+ * Icinga 2 *
+ * Copyright (C) 2012-2015 Icinga Development Team (http://www.icinga.org) *
+ * *
+ * 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 the Free Software Foundation; either version 2 *
+ * of the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the Free Software Foundation *
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ******************************************************************************/
+
+#include "remote/configstageshandler.hpp"
+#include "remote/configmoduleutility.hpp"
+#include "remote/httputility.hpp"
+#include "base/application.hpp"
+#include "base/exception.hpp"
+#include <boost/foreach.hpp>
+
+using namespace icinga;
+
+REGISTER_URLHANDLER("/v1/config/stages", ConfigStagesHandler);
+
+void ConfigStagesHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response)
+{
+ if (request.RequestMethod == "GET")
+ HandleGet(user, request, response);
+ else if (request.RequestMethod == "POST")
+ HandlePost(user, request, response);
+ else if (request.RequestMethod == "DELETE")
+ HandleDelete(user, request, response);
+ else
+ response.SetStatus(400, "Bad request");
+}
+
+bool ConfigStagesHandler::CanAlsoHandleUrl(const Url::Ptr& url) const
+{
+ if (url->GetPath().size() > 5)
+ return false;
+ else
+ return true;
+}
+
+void ConfigStagesHandler::HandleGet(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response)
+{
+ Dictionary::Ptr params = HttpUtility::FetchRequestParameters(request);
+
+ if (request.RequestUrl->GetPath().size() >= 4)
+ params->Set("module", request.RequestUrl->GetPath()[3]);
+
+ if (request.RequestUrl->GetPath().size() >= 5)
+ params->Set("stage", request.RequestUrl->GetPath()[4]);
+
+ String moduleName = params->Get("module");
+ String stageName = params->Get("stage");
+
+ if (!ConfigModuleUtility::ValidateName(moduleName) || !ConfigModuleUtility::ValidateName(stageName)) {
+ response.SetStatus(403, "Forbidden");
+ return;
+ }
+
+ Array::Ptr results = new Array();
+
+ std::vector<std::pair<String, bool> > paths = ConfigModuleUtility::GetFiles(moduleName, stageName);
+
+ String prefixPath = ConfigModuleUtility::GetModuleDir() + "/" + moduleName + "/" + stageName + "/";
+
+ typedef std::pair<String, bool> kv_pair;
+ BOOST_FOREACH(const kv_pair& kv, paths) {
+ Dictionary::Ptr stageInfo = new Dictionary();
+ stageInfo->Set("type", (kv.second ? "directory" : "file"));
+ stageInfo->Set("name", kv.first.SubStr(prefixPath.GetLength()));
+ results->Add(stageInfo);
+ }
+
+ Dictionary::Ptr result = new Dictionary();
+ result->Set("results", results);
+
+ response.SetStatus(200, "OK");
+ HttpUtility::SendJsonBody(response, result);
+}
+
+void ConfigStagesHandler::HandlePost(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response)
+{
+ Dictionary::Ptr params = HttpUtility::FetchRequestParameters(request);
+
+ if (request.RequestUrl->GetPath().size() >= 4)
+ params->Set("module", request.RequestUrl->GetPath()[3]);
+
+ String moduleName = params->Get("module");
+
+ if (!ConfigModuleUtility::ValidateName(moduleName)) {
+ response.SetStatus(403, "Forbidden");
+ return;
+ }
+
+ Dictionary::Ptr files = params->Get("files");
+
+ int code = 200;
+ String status = "Created stage.";
+ String stageName;
+
+ try {
+ if (!files)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Parameter 'files' must be specified."));
+
+ stageName = ConfigModuleUtility::CreateStage(moduleName, files);
+
+ /* validate the config. on success, activate stage and reload */
+ ConfigModuleUtility::AsyncTryActivateStage(moduleName, stageName);
+ } catch (const std::exception& ex) {
+ code = 501;
+ status = "Error: " + DiagnosticInformation(ex);
+ }
+
+ Dictionary::Ptr result1 = new Dictionary();
+
+ result1->Set("module", moduleName);
+ result1->Set("stage", stageName);
+ result1->Set("code", code);
+ result1->Set("status", status);
+
+ Array::Ptr results = new Array();
+ results->Add(result1);
+
+ Dictionary::Ptr result = new Dictionary();
+ result->Set("results", results);
+
+ response.SetStatus(code, (code == 200) ? "OK" : "Error");
+ HttpUtility::SendJsonBody(response, result);
+}
+
+void ConfigStagesHandler::HandleDelete(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response)
+{
+ Dictionary::Ptr params = HttpUtility::FetchRequestParameters(request);
+
+ if (request.RequestUrl->GetPath().size() >= 4)
+ params->Set("module", request.RequestUrl->GetPath()[3]);
+
+ if (request.RequestUrl->GetPath().size() >= 5)
+ params->Set("stage", request.RequestUrl->GetPath()[4]);
+
+ String moduleName = params->Get("module");
+ String stageName = params->Get("stage");
+
+ if (!ConfigModuleUtility::ValidateName(moduleName) || !ConfigModuleUtility::ValidateName(stageName)) {
+ response.SetStatus(403, "Forbidden");
+ return;
+ }
+
+ int code = 200;
+ String status = "Deleted stage.";
+
+ try {
+ ConfigModuleUtility::DeleteStage(moduleName, stageName);
+ } catch (const std::exception& ex) {
+ code = 501;
+ status = "Error: " + DiagnosticInformation(ex);
+ }
+
+ Dictionary::Ptr result1 = new Dictionary();
+
+ result1->Set("module", moduleName);
+ result1->Set("stage", stageName);
+ result1->Set("code", code);
+ result1->Set("status", status);
+
+ Array::Ptr results = new Array();
+ results->Add(result1);
+
+ Dictionary::Ptr result = new Dictionary();
+ result->Set("results", results);
+
+ response.SetStatus(code, (code == 200) ? "OK" : "Error");
+ HttpUtility::SendJsonBody(response, result);
+}
+
--- /dev/null
+/******************************************************************************
+ * Icinga 2 *
+ * Copyright (C) 2012-2015 Icinga Development Team (http://www.icinga.org) *
+ * *
+ * 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 the Free Software Foundation; either version 2 *
+ * of the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the Free Software Foundation *
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ******************************************************************************/
+
+#ifndef CONFIGSTAGESHANDLER_H
+#define CONFIGSTAGESHANDLER_H
+
+#include "remote/httphandler.hpp"
+
+namespace icinga
+{
+
+class I2_REMOTE_API ConfigStagesHandler : public HttpHandler
+{
+public:
+ DECLARE_PTR_TYPEDEFS(ConfigStagesHandler);
+
+ virtual bool CanAlsoHandleUrl(const Url::Ptr& url) const;
+ virtual void HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response);
+
+private:
+ void HandleGet(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response);
+ void HandlePost(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response);
+ void HandleDelete(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response);
+
+};
+
+}
+
+#endif /* CONFIGSTAGESHANDLER_H */
String msg = "<h1>Unauthorized</h1>";
response.WriteBody(msg.CStr(), msg.GetLength());
} else {
- HttpHandler::ProcessRequest(user, request, response);
+ try {
+ HttpHandler::ProcessRequest(user, request, response);
+ } catch (const std::exception& ex) {
+ response.SetStatus(503, "Unhandled exception");
+ response.AddHeader("Content-Type", "text/plain");
+ String errorInfo = DiagnosticInformation(ex);
+ response.WriteBody(errorInfo.CStr(), errorInfo.GetLength());
+ }
}
response.Finish();
--- /dev/null
+/******************************************************************************
+ * Icinga 2 *
+ * Copyright (C) 2012-2015 Icinga Development Team (http://www.icinga.org) *
+ * *
+ * 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 the Free Software Foundation; either version 2 *
+ * of the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the Free Software Foundation *
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ******************************************************************************/
+
+#include "remote/httputility.hpp"
+#include "base/json.hpp"
+#include <boost/foreach.hpp>
+
+using namespace icinga;
+
+Dictionary::Ptr HttpUtility::FetchRequestParameters(HttpRequest& request)
+{
+ Dictionary::Ptr result;
+
+ String body;
+ char buffer[1024];
+ size_t count;
+
+ while ((count = request.ReadBody(buffer, sizeof(buffer))) > 0)
+ body += String(buffer, buffer + count);
+
+ if (!body.IsEmpty())
+ result = JsonDecode(body);
+
+ if (!result)
+ result = new Dictionary();
+
+ typedef std::pair<String, Value> kv_pair;
+ BOOST_FOREACH(const kv_pair& kv, request.RequestUrl->GetQuery()) {
+ result->Set(kv.first, kv.second);
+ }
+
+ return result;
+}
+
+void HttpUtility::SendJsonBody(HttpResponse& response, const Value& val)
+{
+ response.AddHeader("Content-Type", "application/json");
+
+ String body = JsonEncode(val);
+ response.WriteBody(body.CStr(), body.GetLength());
+}
+
+
--- /dev/null
+/******************************************************************************
+ * Icinga 2 *
+ * Copyright (C) 2012-2015 Icinga Development Team (http://www.icinga.org) *
+ * *
+ * 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 the Free Software Foundation; either version 2 *
+ * of the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the Free Software Foundation *
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ******************************************************************************/
+
+#ifndef HTTPUTILITY_H
+#define HTTPUTILITY_H
+
+#include "remote/httprequest.hpp"
+#include "remote/httpresponse.hpp"
+#include "base/dictionary.hpp"
+
+namespace icinga
+{
+
+/**
+ * Helper functions.
+ *
+ * @ingroup remote
+ */
+class I2_REMOTE_API HttpUtility
+{
+
+public:
+ static Dictionary::Ptr FetchRequestParameters(HttpRequest& request);
+ static void SendJsonBody(HttpResponse& response, const Value& val);
+};
+
+}
+
+#endif /* HTTPUTILITY_H */