changeset 239:554a542f0a82

Irccd: add experimental authentication support
author David Demelier <markand@malikania.fr>
date Wed, 17 Aug 2016 15:20:50 +0200
parents 772656d4ba8d
children b25176b3bb80
files lib/irccd/CMakeSources.cmake lib/irccd/config.cpp lib/irccd/conn-state-auth.cpp lib/irccd/conn-state-auth.hpp lib/irccd/conn-state-checking.cpp lib/irccd/connection.hpp lib/irccd/irccdctl.cpp lib/irccd/service-transport.cpp lib/irccd/transport.cpp lib/irccd/transport.hpp
diffstat 10 files changed, 464 insertions(+), 92 deletions(-) [+]
line wrap: on
line diff
--- a/lib/irccd/CMakeSources.cmake	Mon Aug 15 15:26:08 2016 +0200
+++ b/lib/irccd/CMakeSources.cmake	Wed Aug 17 15:20:50 2016 +0200
@@ -2,6 +2,7 @@
     HEADERS
     ${CMAKE_CURRENT_LIST_DIR}/alias.hpp
     ${CMAKE_CURRENT_LIST_DIR}/connection.hpp
+    ${CMAKE_CURRENT_LIST_DIR}/conn-state-auth.hpp
     ${CMAKE_CURRENT_LIST_DIR}/conn-state-checking.hpp
     ${CMAKE_CURRENT_LIST_DIR}/conn-state-connecting.hpp
     ${CMAKE_CURRENT_LIST_DIR}/conn-state-disconnected.hpp
@@ -83,6 +84,7 @@
     SOURCES
     ${CMAKE_CURRENT_LIST_DIR}/alias.cpp
     ${CMAKE_CURRENT_LIST_DIR}/connection.cpp
+    ${CMAKE_CURRENT_LIST_DIR}/conn-state-auth.cpp
     ${CMAKE_CURRENT_LIST_DIR}/conn-state-checking.cpp
     ${CMAKE_CURRENT_LIST_DIR}/conn-state-connecting.cpp
     ${CMAKE_CURRENT_LIST_DIR}/conn-state-disconnected.cpp
--- a/lib/irccd/config.cpp	Mon Aug 15 15:26:08 2016 +0200
+++ b/lib/irccd/config.cpp	Wed Aug 17 15:20:50 2016 +0200
@@ -231,6 +231,9 @@
     else
         throw std::invalid_argument("transport: invalid type given: {}"_format(it->value()));
 
+    if ((it = sc.find("password")) != sc.end())
+        transport->setPassword(it->value());
+
     return transport;
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/conn-state-auth.cpp	Wed Aug 17 15:20:50 2016 +0200
@@ -0,0 +1,124 @@
+/*
+ * conn-state-auth.cpp -- connection is authenticating
+ *
+ * Copyright (c) 2013-2016 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.
+ */
+
+#include "conn-state-auth.hpp"
+#include "conn-state-disconnected.hpp"
+#include "conn-state-ready.hpp"
+
+namespace irccd {
+
+void Connection::AuthState::send(Connection &cnt) noexcept
+{
+    try {
+        auto n = cnt.send(m_output.data(), m_output.size());
+
+        if (n == 0) {
+            m_output.clear();
+            throw std::runtime_error("connection lost");
+        }
+
+        m_output.erase(0, n);
+
+        if (m_output.empty())
+            m_auth = Checking;
+    } catch (const std::exception &ex) {
+        cnt.m_state = std::make_unique<DisconnectedState>();
+        cnt.onDisconnect(ex.what());
+    }
+}
+
+void Connection::AuthState::check(Connection &cnt) noexcept
+{
+    cnt.syncInput();
+
+    auto msg = util::nextNetwork(cnt.m_input);
+
+    if (msg.empty())
+        return;
+
+    try {
+        auto doc = nlohmann::json::parse(msg);
+
+        if (!doc.is_object())
+            throw std::invalid_argument("invalid argument");
+
+        auto cmd = doc.find("response");
+
+        if (cmd == doc.end() || !cmd->is_string() || *cmd != "auth")
+            throw std::invalid_argument("authentication result expected");
+
+        auto result = doc.find("result");
+
+        if (result == doc.end() || !result->is_boolean())
+            throw std::invalid_argument("bad protocol");
+
+        if (!*result)
+            throw std::runtime_error("authentication failed");
+
+        cnt.m_state = std::make_unique<ReadyState>();
+    } catch (const std::exception &ex) {
+        cnt.m_state = std::make_unique<DisconnectedState>();
+        cnt.onDisconnect(ex.what());
+    }
+}
+
+Connection::Status Connection::AuthState::status() const noexcept
+{
+    return Authenticating;
+}
+
+void Connection::AuthState::prepare(Connection &cnt, fd_set &in, fd_set &out)
+{
+    switch (m_auth) {
+    case Created:
+        m_auth = Sending;
+        m_output += nlohmann::json({
+            { "command", "auth" },
+            { "password", cnt.m_password }
+        }).dump();
+        m_output += "\r\n\r\n";
+
+        // FALLTHROUGH
+    case Sending:
+        FD_SET(cnt.m_socket.handle(), &out);
+        break;
+    case Checking:
+        FD_SET(cnt.m_socket.handle(), &in);
+        break;
+    default:
+        break;
+    }
+}
+
+void Connection::AuthState::sync(Connection &cnt, fd_set &in, fd_set &out)
+{
+    switch (m_auth) {
+    case Sending:
+        if (FD_ISSET(cnt.m_socket.handle(), &out))
+            send(cnt);
+        break;
+    case Checking:
+        if (FD_ISSET(cnt.m_socket.handle(), &in))
+            check(cnt);
+        break;
+    default:
+        break;
+    }
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/conn-state-auth.hpp	Wed Aug 17 15:20:50 2016 +0200
@@ -0,0 +1,70 @@
+/*
+ * conn-state-auth.hpp -- connection is authenticating
+ *
+ * Copyright (c) 2013-2016 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_CONN_STATE_AUTH_HPP
+#define IRCCD_CONN_STATE_AUTH_HPP
+
+/**
+ * \file conn-state-auth.hpp
+ * \brief Connection is authenticating.
+ */
+
+#include "conn-state.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Authentication in progress.
+ *
+ * This state emit the authentication command and receives the response to see
+ * if authentication succeeded.
+ */
+class Connection::AuthState : public Connection::State {
+private:
+    enum {
+        Created,
+        Sending,
+        Checking
+    } m_auth{Created};
+
+    std::string m_output;
+
+    void send(Connection &cnt) noexcept;
+    void check(Connection &cnt) noexcept;
+
+public:
+
+    /**
+     * \copydoc State::status
+     */
+    Status status() const noexcept override;
+
+    /**
+     * \copydoc State::prepare
+     */
+    void prepare(Connection &cnt, fd_set &in, fd_set &out) override;
+
+    /**
+     * \copydoc State::sync
+     */
+    void sync(Connection &cnt, fd_set &in, fd_set &out) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_CONN_STATE_AUTH_HPP
--- a/lib/irccd/conn-state-checking.cpp	Mon Aug 15 15:26:08 2016 +0200
+++ b/lib/irccd/conn-state-checking.cpp	Wed Aug 17 15:20:50 2016 +0200
@@ -18,6 +18,7 @@
 
 #include <format.h>
 
+#include "conn-state-auth.hpp"
 #include "conn-state-checking.hpp"
 #include "conn-state-disconnected.hpp"
 #include "conn-state-ready.hpp"
@@ -59,7 +60,11 @@
             IRCCD_VERSION_MAJOR, IRCCD_VERSION_MINOR, IRCCD_VERSION_PATCH));
 
     // Successfully connected.
-    cnx.m_stateNext = std::make_unique<ReadyState>();
+    if (cnx.m_password.empty())
+        cnx.m_stateNext = std::make_unique<ReadyState>();
+    else
+        cnx.m_stateNext = std::make_unique<AuthState>();
+
     cnx.onConnect(info);
 }
 
--- a/lib/irccd/connection.hpp	Mon Aug 15 15:26:08 2016 +0200
+++ b/lib/irccd/connection.hpp	Wed Aug 17 15:20:50 2016 +0200
@@ -117,9 +117,11 @@
 private:
     std::string m_input;
     std::string m_output;
+    std::string m_password;
 
 public:
     class State;
+    class AuthState;
     class DisconnectedState;
     class ConnectingState;
     class CheckingState;
@@ -160,6 +162,26 @@
     virtual ~Connection();
 
     /**
+     * Get the optional password.
+     *
+     * \return the password
+     */
+    inline const std::string &password() const noexcept
+    {
+        return m_password;
+    }
+
+    /**
+     * Set the optional password
+     *
+     * \param password the password
+     */
+    inline void setPassword(std::string password) noexcept
+    {
+        m_password = std::move(password);
+    }
+
+    /**
      * Convenient wrapper around recv().
      *
      * Must be used in sync() function.
--- a/lib/irccd/irccdctl.cpp	Mon Aug 15 15:26:08 2016 +0200
+++ b/lib/irccd/irccdctl.cpp	Wed Aug 17 15:20:50 2016 +0200
@@ -172,6 +172,11 @@
         readConnectLocal(sc);
     else
         throw std::invalid_argument("invalid type given: " + it->value());
+
+    auto password = sc.find("password");
+
+    if (password != sc.end())
+        m_connection->setPassword(password->value());
 }
 
 /*
@@ -394,7 +399,7 @@
         auto it = std::find_if(m_input.begin(), m_input.end(), [&] (const auto &v) {
             auto rt = v.find("response");
 
-            if (rt != v.end() && rt->is_string() && *rt == id)
+            if (v.count("error") > 0 || (rt != v.end() && rt->is_string() && *rt == id))
                 return true;
 
             return false;
@@ -407,6 +412,11 @@
         }
     }
 
+    auto error = value.find("error");
+
+    if (error != value.end() && error->is_string())
+        throw std::runtime_error(error->template get<std::string>());
+
     return value;
 }
 
--- a/lib/irccd/service-transport.cpp	Mon Aug 15 15:26:08 2016 +0200
+++ b/lib/irccd/service-transport.cpp	Wed Aug 17 15:20:50 2016 +0200
@@ -114,6 +114,16 @@
 {
     using namespace std::placeholders;
 
+    // Transport clients.
+    for (const auto &client : m_clients) {
+        try {
+            client->sync(in, out);
+        } catch (const std::exception &ex) {
+            log::info() << "transport: client disconnected: " << ex.what() << std::endl;
+            handleDie(client);
+        }
+    }
+
     // Transport servers.
     for (const auto &transport : m_servers) {
         if (!FD_ISSET(transport->handle(), &in))
@@ -124,24 +134,7 @@
         std::shared_ptr<TransportClient> client = transport->accept();
         std::weak_ptr<TransportClient> ptr(client);
 
-        // Send some information.
-        auto object = nlohmann::json::object({
-            { "program",    "irccd"                 },
-            { "major",      IRCCD_VERSION_MAJOR     },
-            { "minor",      IRCCD_VERSION_MINOR     },
-            { "patch",      IRCCD_VERSION_PATCH     }
-        });
-
-#if defined(WITH_JS)
-        object.push_back({"javascript", true});
-#endif
-#if defined(WITH_SSL)
-        object.push_back({"ssl", true});
-#endif
-
         try {
-            client->send(object);
-
             // Connect signals.
             client->onCommand.connect(std::bind(&TransportService::handleCommand, this, ptr, _1));
             client->onDie.connect(std::bind(&TransportService::handleDie, this, ptr));
@@ -152,16 +145,6 @@
             log::info() << "transport: client disconnected: " << ex.what() << std::endl;
         }
     }
-
-    // Transport clients.
-    for (const auto &client : m_clients) {
-        try {
-            client->sync(in, out);
-        } catch (const std::exception &ex) {
-            log::info() << "transport: client disconnected: " << ex.what() << std::endl;
-            handleDie(client);
-        }
-    }
 }
 
 void TransportService::add(std::shared_ptr<TransportServer> ts)
@@ -173,9 +156,9 @@
 {
     assert(json.is_object());
 
-    // Asynchronous send.
     for (const auto &client : m_clients)
-        client->send(json);
+        if (client->state() == TransportClient::Ready)
+            client->send(json);
 }
 
 } // !irccd
--- a/lib/irccd/transport.cpp	Mon Aug 15 15:26:08 2016 +0200
+++ b/lib/irccd/transport.cpp	Wed Aug 17 15:20:50 2016 +0200
@@ -28,25 +28,74 @@
  * ------------------------------------------------------------------
  */
 
-void TransportClient::parse(const std::string &message)
+void TransportClient::error(const std::string &msg)
+{
+    m_state = Closing;
+
+    send({{ "error", msg }});
+}
+
+void TransportClient::flush() noexcept
 {
-    auto document = nlohmann::json::parse(message);
+    for (std::size_t pos; (pos = m_input.find("\r\n\r\n")) != std::string::npos; ) {
+        auto message = m_input.substr(0, pos);
+
+        m_input.erase(m_input.begin(), m_input.begin() + pos + 4);
+
+        try {
+            auto document = nlohmann::json::parse(message);
 
-    if (document.is_object())
-        onCommand(document);
+            if (!document.is_object())
+                error("invalid argument");
+            else
+                onCommand(document);
+        } catch (const std::exception &ex) {
+            error(ex.what());
+        }
+    }
 }
 
-unsigned TransportClient::recv(char *buffer, unsigned length)
+void TransportClient::authenticate() noexcept
 {
-    return m_socket.recv(buffer, length);
+    auto pos = m_input.find("\r\n\r\n");
+
+    if (pos == std::string::npos)
+        return;
+
+    auto msg = m_input.substr(0, pos);
+
+    m_input.erase(m_input.begin(), m_input.begin() + pos + 4);
+
+    try {
+        auto doc = nlohmann::json::parse(msg);
+
+        if (!doc.is_object())
+            error("invalid argument");
+
+        auto cmd = doc.find("command");
+
+        if (cmd == doc.end() || !cmd->is_string() || *cmd != "auth")
+            error("authentication required");
+
+        auto pw = doc.find("password");
+        auto result = true;
+
+        if (pw == doc.end() || !pw->is_string() || *pw != m_parent.password()) {
+            m_state = Closing;
+            result = false;
+        } else
+            m_state = Ready;
+
+        send({
+            { "response", "auth" },
+            { "result", result }
+        });
+    } catch (const std::exception &ex) {
+        error(ex.what());
+    }
 }
 
-unsigned TransportClient::send(const char *buffer, unsigned length)
-{
-    return m_socket.send(buffer, length);
-}
-
-void TransportClient::syncInput()
+void TransportClient::recv() noexcept
 {
     try {
         std::string buffer;
@@ -63,7 +112,7 @@
     }
 }
 
-void TransportClient::syncOutput()
+void TransportClient::send() noexcept
 {
     try {
         auto ns = send(&m_output[0], m_output.size());
@@ -77,32 +126,101 @@
     }
 }
 
+unsigned TransportClient::recv(void *buffer, unsigned length)
+{
+    return m_socket.recv(buffer, length);
+}
+
+unsigned TransportClient::send(const void *buffer, unsigned length)
+{
+    return m_socket.send(buffer, length);
+}
+
+TransportClient::TransportClient(TransportServer &parent, net::TcpSocket socket)
+    : m_parent(parent)
+    , m_socket(std::move(socket))
+{
+    assert(m_socket.isOpen());
+
+    m_socket.set(net::option::SockBlockMode(false));
+
+    // Send some information.
+    auto object = nlohmann::json::object({
+        { "program",    "irccd"                 },
+        { "major",      IRCCD_VERSION_MAJOR     },
+        { "minor",      IRCCD_VERSION_MINOR     },
+        { "patch",      IRCCD_VERSION_PATCH     }
+    });
+
+#if defined(WITH_JS)
+    object.push_back({"javascript", true});
+#endif
+#if defined(WITH_SSL)
+    object.push_back({"ssl", true});
+#endif
+
+    send(object);
+}
+
 void TransportClient::prepare(fd_set &in, fd_set &out, net::Handle &max)
 {
     if (m_socket.handle() > max)
         max = m_socket.handle();
 
-    FD_SET(m_socket.handle(), &in);
+    switch (m_state) {
+    case Greeting:
+        FD_SET(m_socket.handle(), &out);
+        break;
+    case Authenticating:
+        FD_SET(m_socket.handle(), &in);
+        break;
+    case Ready:
+        FD_SET(m_socket.handle(), &in);
 
-    if (!m_output.empty())
-        FD_SET(m_socket.handle(), &out);
+        if (!m_output.empty())
+            FD_SET(m_socket.handle(), &out);
+        break;
+    case Closing:
+        if (!m_output.empty())
+            FD_SET(m_socket.handle(), &out);
+        else
+            onDie();
+        break;
+    default:
+        break;
+    }
 }
 
 void TransportClient::sync(fd_set &in, fd_set &out)
 {
-    // Do some I/O.
-    if (FD_ISSET(m_socket.handle(), &in))
-        syncInput();
-    if (FD_ISSET(m_socket.handle(), &out))
-        syncOutput();
+    switch (m_state) {
+    case Greeting:
+        send();
+
+        if (m_output.empty())
+            m_state = m_parent.password().empty() ? Ready : Authenticating;
+
+        break;
+    case Authenticating:
+        if (FD_ISSET(m_socket.handle(), &in))
+            recv();
 
-    // Flush the queue.
-    for (std::size_t pos; (pos = m_input.find("\r\n\r\n")) != std::string::npos; ) {
-        auto message = m_input.substr(0, pos);
+        authenticate();
+        break;
+    case Ready:
+        if (FD_ISSET(m_socket.handle(), &in))
+            recv();
+        if (FD_ISSET(m_socket.handle(), &out))
+            send();
 
-        m_input.erase(m_input.begin(), m_input.begin() + pos + 4);
-
-        parse(message);
+        flush();
+        break;
+    case Closing:
+        if (FD_ISSET(m_socket.handle(), &out))
+            send();
+        break;
+    default:
+        break;
     }
 }
 
@@ -135,8 +253,9 @@
 
 TransportClientTls::TransportClientTls(const std::string &pkey,
                                        const std::string &cert,
+                                       TransportServer &server,
                                        net::TcpSocket socket)
-    : TransportClient(std::move(socket))
+    : TransportClient(server, std::move(socket))
     , m_ssl(m_socket)
 {
     m_ssl.setPrivateKey(pkey);
@@ -145,7 +264,7 @@
     handshake();
 }
 
-unsigned TransportClientTls::recv(char *buffer, unsigned length)
+unsigned TransportClientTls::recv(void *buffer, unsigned length)
 {
     unsigned nread = 0;
 
@@ -160,7 +279,7 @@
     return nread;
 }
 
-unsigned TransportClientTls::send(const char *buffer, unsigned length)
+unsigned TransportClientTls::send(const void *buffer, unsigned length)
 {
     unsigned nsent = 0;
 
@@ -256,7 +375,7 @@
 
 std::unique_ptr<TransportClient> TransportServerTls::accept()
 {
-    return std::make_unique<TransportClientTls>(m_privatekey, m_cert, m_socket.accept());
+    return std::make_unique<TransportClientTls>(m_privatekey, m_cert, *this, m_socket.accept());
 }
 
 /*
--- a/lib/irccd/transport.hpp	Mon Aug 15 15:26:08 2016 +0200
+++ b/lib/irccd/transport.hpp	Wed Aug 17 15:20:50 2016 +0200
@@ -36,6 +36,8 @@
 
 namespace irccd {
 
+class TransportServer;
+
 /**
  * \class TransportClient
  * \brief Client connected to irccd.
@@ -45,6 +47,16 @@
 class TransportClient {
 public:
     /**
+     * \brief Client state
+     */
+    enum State {
+        Greeting,               //!< client is getting irccd info
+        Authenticating,         //!< client requires authentication
+        Ready,                  //!< client is ready to use
+        Closing                 //!< client must disconnect
+    };
+
+    /**
      * Signal: onCommand
      * ----------------------------------------------------------
      *
@@ -61,19 +73,28 @@
      */
     Signal<> onDie;
 
+private:
+    void error(const std::string &msg);
+    void flush() noexcept;
+    void authenticate() noexcept;
+
 protected:
+    State m_state{Greeting};    //!< current client state
+    TransportServer &m_parent;  //!< parent transport server
     net::TcpSocket m_socket;    //!< socket
     std::string m_input;        //!< input buffer
     std::string m_output;       //!< output buffer
 
     /**
-     * Parse input buffer.
-     *
-     * \param buffer the buffer.
+     * Fill the input buffer with available data.
      */
-    void parse(const std::string &buffer);
+    void recv() noexcept;
 
-protected:
+    /**
+     * Flush the output buffer from available pending data.
+     */
+    void send() noexcept;
+
     /**
      * Try to receive some data into the given buffer.
      *
@@ -81,7 +102,7 @@
      * \param length the buffer length
      * \return the number of bytes received
      */
-    IRCCD_EXPORT virtual unsigned recv(char *buffer, unsigned length);
+    IRCCD_EXPORT virtual unsigned recv(void *buffer, unsigned length);
 
     /**
      * Try to send some data into the given buffer.
@@ -90,21 +111,17 @@
      * \param length the buffer length
      * \return the number of bytes sent
      */
-    IRCCD_EXPORT virtual unsigned send(const char *buffer, unsigned length);
+    IRCCD_EXPORT virtual unsigned send(const void *buffer, unsigned length);
 
 public:
     /**
      * Create a transport client from the socket.
      *
      * \pre socket must be valid
+     * \param parent the parent server
+     * \param socket the new socket
      */
-    inline TransportClient(net::TcpSocket socket)
-        : m_socket(std::move(socket))
-    {
-        assert(m_socket.isOpen());
-
-        m_socket.set(net::option::SockBlockMode(false));
-    }
+    IRCCD_EXPORT TransportClient(TransportServer &parent, net::TcpSocket socket);
 
     /**
      * Virtual destructor defaulted.
@@ -112,18 +129,14 @@
     virtual ~TransportClient() = default;
 
     /**
-     * Convenient wrapper around recv().
+     * Get the client state.
      *
-     * Must be used in sync() function.
+     * \return the client state
      */
-    IRCCD_EXPORT void syncInput();
-
-    /**
-     * Convenient wrapper around send().
-     *
-     * Must be used in sync() function.
-     */
-    IRCCD_EXPORT void syncOutput();
+    inline State state() const noexcept
+    {
+        return m_state;
+    }
 
     /**
      * Append some data to the output queue.
@@ -168,12 +181,12 @@
     /**
      * \copydoc TransportClient::recv
      */
-    unsigned recv(char *buffer, unsigned length) override;
+    unsigned recv(void *buffer, unsigned length) override;
 
     /**
      * \copydoc TransportClient::send
      */
-    unsigned send(const char *buffer, unsigned length) override;
+    unsigned send(const void *buffer, unsigned length) override;
 
 public:
     /**
@@ -183,9 +196,12 @@
      * \param pkey the private key
      * \param cert the certificate file
      * \param socket the accepted socket
+     * \param parent the parent server
+     * \param socket the new socket
      */
     IRCCD_EXPORT TransportClientTls(const std::string &pkey,
                                     const std::string &cert,
+                                    TransportServer &server,
                                     net::TcpSocket socket);
 
     /**
@@ -227,10 +243,8 @@
     TransportServer &operator=(TransportServer &&) = delete;
 
 protected:
-    /**
-     * The socket handle.
-     */
     net::TcpSocket m_socket;
+    std::string m_password;
 
 public:
     /**
@@ -252,6 +266,26 @@
     }
 
     /**
+     * Get the password.
+     *
+     * \return the password
+     */
+    inline const std::string &password() const noexcept
+    {
+        return m_password;
+    }
+
+    /**
+     * Set an optional password.
+     *
+     * \return the password
+     */
+    inline void setPassword(std::string password) noexcept
+    {
+        m_password = std::move(password);
+    }
+
+    /**
      * Destructor defaulted.
      */
     virtual ~TransportServer() = default;
@@ -263,7 +297,7 @@
      */
     virtual std::unique_ptr<TransportClient> accept()
     {
-        return std::make_unique<TransportClient>(m_socket.accept());
+        return std::make_unique<TransportClient>(*this, m_socket.accept());
     }
 };