]> granicus.if.org Git - icinga2/commitdiff
Implement config file management for the API
authorMichael Friedrich <michael.friedrich@netways.de>
Tue, 21 Jul 2015 14:10:13 +0000 (16:10 +0200)
committerMichael Friedrich <michael.friedrich@netways.de>
Thu, 23 Jul 2015 15:57:24 +0000 (17:57 +0200)
refs #9083

fixes #9102
fixes #9103
fixes #9104

fixes #9705

17 files changed:
doc/4-configuring-icinga-2.md
etc/icinga2/icinga2.conf
lib/base/utility.cpp
lib/base/utility.hpp
lib/cli/daemonutility.cpp
lib/remote/CMakeLists.txt
lib/remote/configfileshandler.cpp [new file with mode: 0644]
lib/remote/configfileshandler.hpp [new file with mode: 0644]
lib/remote/configmoduleshandler.cpp [new file with mode: 0644]
lib/remote/configmoduleshandler.hpp [new file with mode: 0644]
lib/remote/configmoduleutility.cpp [new file with mode: 0644]
lib/remote/configmoduleutility.hpp [new file with mode: 0644]
lib/remote/configstageshandler.cpp [new file with mode: 0644]
lib/remote/configstageshandler.hpp [new file with mode: 0644]
lib/remote/httpconnection.cpp
lib/remote/httputility.cpp [new file with mode: 0644]
lib/remote/httputility.hpp [new file with mode: 0644]

index a85d83e136d70f4ea26e08f9ebce7291042d02ae..f5686365e3b5608fc7b3f25ae404269663f72488 100644 (file)
@@ -136,16 +136,6 @@ and their generated configuration described in
 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.
index 825ffe19b56496b95bc20a2a1bb313751f7a1d22..e258e7e9aa633169d9fcc64780822d24721530c6 100644 (file)
@@ -48,9 +48,3 @@ include_recursive "repository.d"
  * 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"
index 0c4161140e06b040ad66053f9377e46e9af08687..b1da99df4b75298318a5381f4c78e2ff9f4aa627 100644 (file)
@@ -581,6 +581,39 @@ bool Utility::MkDirP(const String& path, int flags)
        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);
index f616801e20c7011a2571020fb49d460c07b6e011..c1ceb273cfa0fcdb06969b0a39308c2b341324b1 100644 (file)
@@ -123,6 +123,7 @@ public:
 
        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);
@@ -130,10 +131,10 @@ public:
 
 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;
-
 };
 
 }
index c09de03464ef578b165df4bc87aa761572d87f6b..4a1f84dad37eb4aff321b52acc83124980aeacaf 100644 (file)
@@ -85,10 +85,29 @@ bool DaemonUtility::ValidateConfigFiles(const std::vector<std::string>& configs,
        * 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;
 
index 125054940d40a8874307104bc6df74dff44444d5..6d3827f02bf12d999fe2b0cf923410989256995b 100644 (file)
@@ -22,9 +22,11 @@ mkclass_target(zone.ti zone.tcpp zone.thpp)
 
 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
 )
diff --git a/lib/remote/configfileshandler.cpp b/lib/remote/configfileshandler.cpp
new file mode 100644 (file)
index 0000000..a98ec27
--- /dev/null
@@ -0,0 +1,96 @@
+/******************************************************************************
+ * 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");
+       }
+}
+
+
diff --git a/lib/remote/configfileshandler.hpp b/lib/remote/configfileshandler.hpp
new file mode 100644 (file)
index 0000000..4a90664
--- /dev/null
@@ -0,0 +1,42 @@
+/******************************************************************************
+ * 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 */
diff --git a/lib/remote/configmoduleshandler.cpp b/lib/remote/configmoduleshandler.cpp
new file mode 100644 (file)
index 0000000..736d979
--- /dev/null
@@ -0,0 +1,149 @@
+/******************************************************************************
+ * 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);
+}
+
diff --git a/lib/remote/configmoduleshandler.hpp b/lib/remote/configmoduleshandler.hpp
new file mode 100644 (file)
index 0000000..8e670b6
--- /dev/null
@@ -0,0 +1,45 @@
+/******************************************************************************
+ * 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 */
diff --git a/lib/remote/configmoduleutility.cpp b/lib/remote/configmoduleutility.cpp
new file mode 100644 (file)
index 0000000..b927c87
--- /dev/null
@@ -0,0 +1,297 @@
+/******************************************************************************
+ * 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));
+}
+
+
diff --git a/lib/remote/configmoduleutility.hpp b/lib/remote/configmoduleutility.hpp
new file mode 100644 (file)
index 0000000..99f4468
--- /dev/null
@@ -0,0 +1,72 @@
+/******************************************************************************
+ * 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 */
diff --git a/lib/remote/configstageshandler.cpp b/lib/remote/configstageshandler.cpp
new file mode 100644 (file)
index 0000000..24b12ed
--- /dev/null
@@ -0,0 +1,184 @@
+/******************************************************************************
+ * 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);
+}
+
diff --git a/lib/remote/configstageshandler.hpp b/lib/remote/configstageshandler.hpp
new file mode 100644 (file)
index 0000000..ccdb3b7
--- /dev/null
@@ -0,0 +1,45 @@
+/******************************************************************************
+ * 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 */
index 6aee8a19c039584e5c489e89852ac9ee3d2c4b0b..b1056c3e106561c758df4145b324bde1a12fe9c9 100644 (file)
@@ -154,7 +154,14 @@ void HttpConnection::ProcessMessageAsync(HttpRequest& request)
                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();
diff --git a/lib/remote/httputility.cpp b/lib/remote/httputility.cpp
new file mode 100644 (file)
index 0000000..86ddef2
--- /dev/null
@@ -0,0 +1,59 @@
+/******************************************************************************
+ * 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());
+}
+
+
diff --git a/lib/remote/httputility.hpp b/lib/remote/httputility.hpp
new file mode 100644 (file)
index 0000000..48f2736
--- /dev/null
@@ -0,0 +1,45 @@
+/******************************************************************************
+ * 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 */