changeset 645:a63d73b456d5

Irccd: add transport_error
author David Demelier <markand@malikania.fr>
date Fri, 23 Mar 2018 21:15:10 +0100
parents aae6d5a2b28d
children e4227aa185c2
files libcommon/CMakeLists.txt libcommon/irccd/ini_util.hpp libirccd/irccd/daemon/server_util.cpp libirccd/irccd/daemon/transport_server.cpp libirccd/irccd/daemon/transport_server.hpp libirccd/irccd/daemon/transport_util.cpp libirccd/irccd/daemon/transport_util.hpp
diffstat 7 files changed, 305 insertions(+), 123 deletions(-) [+]
line wrap: on
line diff
--- a/libcommon/CMakeLists.txt	Fri Mar 23 14:00:03 2018 +0100
+++ b/libcommon/CMakeLists.txt	Fri Mar 23 21:15:10 2018 +0100
@@ -25,6 +25,7 @@
     ${libcommon_SOURCE_DIR}/irccd/config.hpp
     ${libcommon_SOURCE_DIR}/irccd/fs_util.hpp
     ${libcommon_SOURCE_DIR}/irccd/ini.hpp
+    ${libcommon_SOURCE_DIR}/irccd/ini_util.hpp
     ${libcommon_SOURCE_DIR}/irccd/json_util.hpp
     ${libcommon_SOURCE_DIR}/irccd/network_stream.hpp
     ${libcommon_SOURCE_DIR}/irccd/options.hpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libcommon/irccd/ini_util.hpp	Fri Mar 23 21:15:10 2018 +0100
@@ -0,0 +1,97 @@
+/*
+ * ini_util.hpp -- ini utilities
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_COMMON_INI_UTIL_HPP
+#define IRCCD_COMMON_INI_UTIL_HPP
+
+/**
+ * \file ini_util.hpp
+ * \brief Ini utilities.
+ */
+
+#include <boost/optional.hpp>
+
+#include "ini.hpp"
+#include "string_util.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Ini utilities.
+ */
+namespace ini_util {
+
+/**
+ * Get an unsigned integer from the configuration section.
+ *
+ * \param sc the section
+ * \param name the option name
+ * \return the value or none if not able to convert
+ */
+template <typename Int>
+inline boost::optional<Int> get_uint(const ini::section& sc, const std::string& name) noexcept
+{
+    return string_util::to_uint<Int>(sc.get(name).value());
+}
+
+/**
+ * Get an optional string or the default value if not given.
+ *
+ * \param sc the section
+ * \param name the option name
+ * \param def the default value
+ * \return the value or def if not found
+ */
+inline std::string optional_string(const ini::section& sc,
+                                   const std::string& name,
+                                   const std::string& def) noexcept
+{
+    const auto it = sc.find(name);
+
+    if (it == sc.end())
+        return def;
+
+    return it->value();
+}
+
+/**
+ * Get an optional unsigned integer from the configuration section.
+ *
+ * \param sc the section
+ * \param name the option name
+ * \param def the default value
+ * \return the value or none if not able to convert
+ */
+template <typename Int>
+inline boost::optional<Int> optional_uint(const ini::section& sc,
+                                          const std::string& name,
+                                          Int def) noexcept
+{
+    const auto it = sc.find(name);
+
+    if (it == sc.end())
+        return def;
+
+    return string_util::to_uint<Int>(it->value());
+}
+
+} // !ini_util
+
+} // !irccd
+
+#endif // !IRCCD_COMMON_INI_UTIL_HPP
--- a/libirccd/irccd/daemon/server_util.cpp	Fri Mar 23 14:00:03 2018 +0100
+++ b/libirccd/irccd/daemon/server_util.cpp	Fri Mar 23 21:15:10 2018 +0100
@@ -19,7 +19,7 @@
 #include <algorithm>
 
 #include <irccd/config.hpp>
-#include <irccd/ini.hpp>
+#include <irccd/ini_util.hpp>
 #include <irccd/json_util.hpp>
 #include <irccd/string_util.hpp>
 
@@ -31,39 +31,12 @@
 
 namespace {
 
-// TODO: ini_util
-std::string optional_string(const ini::section& sc,
-                            const std::string& name,
-                            const std::string& def)
-{
-    const auto it = sc.find(name);
-
-    if (it == sc.end())
-        return def;
-
-    return it->value();
-}
-
-// TODO: ini_util
-template <typename Int>
-boost::optional<Int> optional_uint(const ini::section& sc,
-                                   const std::string& name,
-                                   Int def)
-{
-    const auto it = sc.find(name);
-
-    if (it == sc.end())
-        return def;
-
-    return string_util::to_uint<Int>(it->value());
-}
-
 void from_config_load_identity(server& sv, const ini::section& sc)
 {
-    const auto username = optional_string(sc, "username", sv.username());
-    const auto realname = optional_string(sc, "realname", sv.realname());
-    const auto nickname = optional_string(sc, "nickname", sv.nickname());
-    const auto ctcp_version = optional_string(sc, "ctcp-version", sv.ctcp_version());
+    const auto username = ini_util::optional_string(sc, "username", sv.username());
+    const auto realname = ini_util::optional_string(sc, "realname", sv.realname());
+    const auto nickname = ini_util::optional_string(sc, "nickname", sv.nickname());
+    const auto ctcp_version = ini_util::optional_string(sc, "ctcp-version", sv.ctcp_version());
 
     if (username.empty())
         throw server_error(server_error::invalid_username);
@@ -117,10 +90,10 @@
 
 void from_config_load_numeric_parameters(server& sv, const ini::section& sc)
 {
-    const auto port = optional_uint<std::uint16_t>(sc, "port", sv.port());
-    const auto ping_timeout = optional_uint<uint16_t>(sc, "ping-timeout", sv.ping_timeout());
-    const auto reco_tries = optional_uint<uint8_t>(sc, "reconnect-tries", sv.reconnect_tries());
-    const auto reco_timeout = optional_uint<uint16_t>(sc, "reconnect-delay", sv.reconnect_delay());
+    const auto port = ini_util::optional_uint<std::uint16_t>(sc, "port", sv.port());
+    const auto ping_timeout = ini_util::optional_uint<uint16_t>(sc, "ping-timeout", sv.ping_timeout());
+    const auto reco_tries = ini_util::optional_uint<uint8_t>(sc, "reconnect-tries", sv.reconnect_tries());
+    const auto reco_timeout = ini_util::optional_uint<uint16_t>(sc, "reconnect-delay", sv.reconnect_delay());
 
     if (!port)
         throw server_error(server_error::invalid_port);
@@ -139,8 +112,8 @@
 
 void from_config_load_options(server& sv, const ini::section& sc)
 {
-    const auto password = optional_string(sc, "password", "");
-    const auto command_char = optional_string(sc, "command-char", sv.command_char());
+    const auto password = ini_util::optional_string(sc, "password", "");
+    const auto command_char = ini_util::optional_string(sc, "command-char", sv.command_char());
 
     sv.set_password(password);
     sv.set_command_char(command_char);
--- a/libirccd/irccd/daemon/transport_server.cpp	Fri Mar 23 14:00:03 2018 +0100
+++ b/libirccd/irccd/daemon/transport_server.cpp	Fri Mar 23 21:15:10 2018 +0100
@@ -19,6 +19,7 @@
 #include <irccd/sysconfig.hpp>
 
 #include <cassert>
+#include <system_error>
 
 #include <irccd/json_util.hpp>
 
@@ -101,4 +102,57 @@
     });
 }
 
+transport_error::transport_error(error code) noexcept
+    : system_error(make_error_code(code))
+{
+}
+
+const std::error_category& transport_category() noexcept
+{
+    static const class category : public std::error_category {
+    public:
+        const char* name() const noexcept override
+        {
+            return "transport";
+        }
+
+        std::string message(int e) const override
+        {
+            switch (static_cast<transport_error::error>(e)) {
+            case transport_error::auth_required:
+                return "authentication required";
+            case transport_error::invalid_auth:
+                return "invalid authentication";
+            case transport_error::invalid_port:
+                return "invalid port";
+            case transport_error::invalid_address:
+                return "invalid address";
+            case transport_error::invalid_hostname:
+                return "invalid hostname";
+            case transport_error::invalid_path:
+                return "invalid socket path";
+            case transport_error::invalid_family:
+                return "invalid family";
+            case transport_error::invalid_certificate:
+                return "invalid certificate";
+            case transport_error::invalid_private_key:
+                return "invalid private key";
+            case transport_error::ssl_disabled:
+                return "ssl is not enabled";
+            case transport_error::not_supported:
+                return "transport not supported";
+            default:
+                return "no error";
+            }
+        }
+    } category;
+
+    return category;
+};
+
+std::error_code make_error_code(transport_error::error e) noexcept
+{
+    return {static_cast<int>(e), transport_category()};
+}
+
 } // !irccd
--- a/libirccd/irccd/daemon/transport_server.hpp	Fri Mar 23 14:00:03 2018 +0100
+++ b/libirccd/irccd/daemon/transport_server.hpp	Fri Mar 23 21:15:10 2018 +0100
@@ -129,6 +129,68 @@
     }
 };
 
+class transport_error : public std::system_error {
+public:
+    enum error {
+        //!< Authentication is required.
+        auth_required,
+
+        //!< Authentication was invalid.
+        invalid_auth,
+
+        //!< Invalid TCP/IP port.
+        invalid_port,
+
+        //!< Invalid TCP/IP address.
+        invalid_address,
+
+        //!< The specified host was invalid.
+        invalid_hostname,
+
+        //!< Invalid unix local path.
+        invalid_path,
+
+        //!< Invalid IPv4/IPv6 family.
+        invalid_family,
+
+        //!< Invalid certificate given.
+        invalid_certificate,
+
+        //!< Invalid private key given.
+        invalid_private_key,
+
+        //!< SSL was requested but is disabled.
+        ssl_disabled,
+
+        //!< Kind of transport not supported on this platform.
+        not_supported
+    };
+
+    transport_error(error err) noexcept;
+};
+
+/**
+ * Get the transport error category singleton.
+ *
+ * \return the singleton
+ */
+const std::error_category& transport_category() noexcept;
+
+/**
+ * Create a boost::system::error_code from server_error::error enum.
+ *
+ * \param e the error code
+ */
+std::error_code make_error_code(transport_error::error e) noexcept;
+
 } // !irccd
 
+namespace std {
+
+template <>
+struct is_error_code_enum<irccd::transport_error::error> : public std::true_type {
+};
+
+} // !std
+
 #endif // !IRCCD_DAEMON_TRANSPORT_SERVER_HPP
--- a/libirccd/irccd/daemon/transport_util.cpp	Fri Mar 23 14:00:03 2018 +0100
+++ b/libirccd/irccd/daemon/transport_util.cpp	Fri Mar 23 21:15:10 2018 +0100
@@ -20,7 +20,7 @@
 
 #include <cassert>
 
-#include <irccd/ini.hpp>
+#include <irccd/ini_util.hpp>
 #include <irccd/string_util.hpp>
 
 #include <irccd/daemon/ip_transport_server.hpp>
@@ -35,39 +35,18 @@
 
 #include "transport_util.hpp"
 
+using namespace boost::asio;
+using namespace boost::asio::ip;
+
 namespace irccd {
 
 namespace transport_util {
 
 namespace {
 
-std::unique_ptr<transport_server> load_transport_ip(boost::asio::io_service& service,
-                                                    const ini::section& sc)
+tcp from_config_load_ip_protocol(const ini::section& sc)
 {
-    assert(sc.key() == "transport");
-
-    std::unique_ptr<transport_server> transport;
-    ini::section::const_iterator it;
-
-    // Port.
-    if ((it = sc.find("port")) == sc.cend())
-        throw std::invalid_argument("missing 'port' parameter");
-
-    auto port = string_util::to_uint<std::uint16_t>(it->value());
-
-    if (!port)
-        throw std::invalid_argument("invalid port number");
-
-    // Address.
-    std::string address = "*";
-
-    if ((it = sc.find("address")) != sc.end())
-        address = it->value();
-
-    // 0011
-    //    ^ define IPv4
-    //   ^  define IPv6
-    auto mode = 1U;
+    bool ipv4 = true, ipv6 = false;
 
     /*
      * Documentation stated family but code checked for 'domain' option.
@@ -76,78 +55,93 @@
      *
      * See #637
      */
+    ini::section::const_iterator it;
+
     if ((it = sc.find("domain")) != sc.end() || (it = sc.find("family")) != sc.end()) {
-        mode = 0U;
+        ipv4 = ipv6 = false;
 
         for (const auto& v : *it) {
             if (v == "ipv4")
-                mode |= (1U << 0);
+                ipv4 = true;
             if (v == "ipv6")
-                mode |= (1U << 1);
+                ipv6 = true;
         }
     }
 
-    if (mode == 0U)
-        throw std::invalid_argument("family must at least have ipv4 or ipv6");
+    if (!ipv4 && !ipv6)
+        throw transport_error(transport_error::invalid_family);
+
+    return ipv4 ? tcp::v4() : tcp::v6();
+}
+
+tcp::endpoint from_config_load_ip_endpoint(const ini::section& sc)
+{
+    const auto port = ini_util::get_uint<std::uint16_t>(sc, "port");
+    const auto address = ini_util::optional_string(sc, "address", "*");
 
-    auto protocol = (mode & 0x2U)
-        ? boost::asio::ip::tcp::v4()
-        : boost::asio::ip::tcp::v6();
+    if (!port)
+        throw transport_error(transport_error::invalid_port);
+    if (address.empty())
+        throw transport_error(transport_error::invalid_address);
 
-    // Optional SSL.
-    std::string pkey;
-    std::string cert;
+    const auto protocol = from_config_load_ip_protocol(sc);
+
+    return address == "*"
+        ? tcp::endpoint(protocol, *port)
+        : tcp::endpoint(address::from_string(address), *port);
+}
 
-    if ((it = sc.find("ssl")) != sc.end() && string_util::is_boolean(it->value())) {
-        if ((it = sc.find("certificate")) == sc.end())
-            throw std::invalid_argument("missing 'certificate' parameter");
+tcp::acceptor from_config_load_ip_acceptor(io_service& service, const ini::section& sc)
+{
+    return tcp::acceptor(service, from_config_load_ip_endpoint(sc), true);
+}
+
+std::unique_ptr<transport_server> from_config_load_ip(io_service& service, const ini::section& sc)
+{
+    assert(sc.key() == "transport");
+
+    auto acceptor = from_config_load_ip_acceptor(service, sc);
 
-        cert = it->value();
+    if (string_util::is_boolean(sc.get("ssl").value())) {
+#if !defined(HAVE_SSL)
+        throw transport_error(transport_error::ssl_disabled);
+#else
+        const auto key = sc.get("key").value();
+        const auto cert = sc.get("certificate").value();
 
-        if ((it = sc.find("key")) == sc.end())
-            throw std::invalid_argument("missing 'key' parameter");
+        if (key.empty())
+            throw transport_error(transport_error::invalid_private_key);
+        if (cert.empty())
+            throw transport_error(transport_error::invalid_certificate);
 
-        pkey = it->value();
+        boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23);
+
+        ctx.use_private_key_file(key, boost::asio::ssl::context::pem);
+        ctx.use_certificate_file(cert, boost::asio::ssl::context::pem);
+
+        return std::make_unique<tls_transport_server>(std::move(acceptor), std::move(ctx));
+#endif
     }
 
-    auto endpoint = (address == "*")
-        ? boost::asio::ip::tcp::endpoint(protocol, *port)
-        : boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), *port);
-
-    boost::asio::ip::tcp::acceptor acceptor(service, endpoint, true);
-
-    if (pkey.empty())
-        return std::make_unique<ip_transport_server>(std::move(acceptor));
-
-#if defined(HAVE_SSL)
-    boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23);
-
-    ctx.use_private_key_file(pkey, boost::asio::ssl::context::pem);
-    ctx.use_certificate_file(cert, boost::asio::ssl::context::pem);
-
-    return std::make_unique<tls_transport_server>(std::move(acceptor), std::move(ctx));
-#else
-    throw std::invalid_argument("SSL disabled");
-#endif
+    return std::make_unique<ip_transport_server>(std::move(acceptor));
 }
 
-std::unique_ptr<transport_server> load_transport_unix(boost::asio::io_service& service,
-                                                      const ini::section& sc)
+std::unique_ptr<transport_server> from_config_load_unix(io_service& service, const ini::section& sc)
 {
     assert(sc.key() == "transport");
 
 #if !defined(IRCCD_SYSTEM_WINDOWS)
     using boost::asio::local::stream_protocol;
 
-    ini::section::const_iterator it = sc.find("path");
+    const auto path = sc.get("path").value();
 
-    if (it == sc.end())
-        throw std::invalid_argument("missing 'path' parameter");
+    if (path.empty())
+        throw transport_error(transport_error::invalid_path);
 
     // Remove the file first.
-    std::remove(it->value().c_str());
+    std::remove(path.c_str());
 
-    stream_protocol::endpoint endpoint(it->value());
+    stream_protocol::endpoint endpoint(path);
     stream_protocol::acceptor acceptor(service, std::move(endpoint));
 
     return std::make_unique<local_transport_server>(std::move(acceptor));
@@ -155,32 +149,32 @@
     (void)service;
     (void)sc;
 
-    throw std::invalid_argument("unix transports not supported on on this platform");
+    throw transport_error(transport_error::not_supported);
 #endif
 }
 
 } // !namespace
 
-std::unique_ptr<transport_server> from_config(boost::asio::io_service& service, const ini::section& sc)
+std::unique_ptr<transport_server> from_config(io_service& service, const ini::section& sc)
 {
     assert(sc.key() == "transport");
 
-    std::unique_ptr<transport_server> transport;
-    ini::section::const_iterator it = sc.find("type");
+    const auto type = sc.get("type").value();
+    const auto password = sc.get("password").value();
 
-    if (it == sc.end())
-        throw std::invalid_argument("missing 'type' parameter");
+    if (type.empty())
+        throw transport_error(transport_error::not_supported);
+
+    std::unique_ptr<transport_server> transport;
 
-    if (it->value() == "ip")
-        transport = load_transport_ip(service, sc);
-    else if (it->value() == "unix")
-        transport = load_transport_unix(service, sc);
+    if (type == "ip")
+        transport = from_config_load_ip(service, sc);
+    else if (type == "unix")
+        transport = from_config_load_unix(service, sc);
     else
-        throw std::invalid_argument(string_util::sprintf("invalid type given: %s", it->value()));
-
+        throw transport_error(transport_error::not_supported);
 
-    if ((it = sc.find("password")) != sc.end())
-        transport->set_password(it->value());
+    transport->set_password(password);
 
     return transport;
 }
--- a/libirccd/irccd/daemon/transport_util.hpp	Fri Mar 23 14:00:03 2018 +0100
+++ b/libirccd/irccd/daemon/transport_util.hpp	Fri Mar 23 21:15:10 2018 +0100
@@ -48,6 +48,7 @@
  *
  * \param service the IO service
  * \param sc the configuration
+ * \throw transport_error on errors
  * \return the transport
  */
 std::unique_ptr<transport_server> from_config(boost::asio::io_service& service,