tcp_socket_handler.cpp 10.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
#include <network/tcp_socket_handler.hpp>
#include <network/dns_handler.hpp>

#include <network/poller.hpp>

#include <logger/logger.hpp>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdexcept>
#include <unistd.h>
11
#include <cerrno>
12 13 14 15
#include <cstring>

#ifdef BOTAN_FOUND
# include <botan/hex.h>
16
# include <botan/tls_exceptn.h>
17

18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
namespace
{
    Botan::AutoSeeded_RNG& get_rng()
    {
      static Botan::AutoSeeded_RNG rng{};
      return rng;
    }
    BiboumiTLSPolicy& get_policy()
    {
      static BiboumiTLSPolicy policy{};
      return policy;
    }
    Botan::TLS::Session_Manager_In_Memory& get_session_manager()
    {
      static Botan::TLS::Session_Manager_In_Memory session_manager{get_rng()};
      return session_manager;
    }
}
36 37 38 39 40 41 42 43 44 45
#endif

#ifndef UIO_FASTIOV
# define UIO_FASTIOV 8
#endif

using namespace std::string_literals;
using namespace std::chrono_literals;


46
TCPSocketHandler::TCPSocketHandler(std::shared_ptr<Poller>& poller):
47
  SocketHandler(poller, -1),
48
  use_tls(false)
49 50 51
#ifdef BOTAN_FOUND
  ,credential_manager(this)
#endif
52 53
{}

54 55
TCPSocketHandler::~TCPSocketHandler()
{
56 57
  if (this->poller->is_managing_socket(this->get_socket()))
    this->poller->remove_socket_handler(this->get_socket());
58
  if (this->socket != -1)
louiz’'s avatar
louiz’ committed
59
    {
60 61
      ::close(this->socket);
      this->socket = -1;
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
    }
}

void TCPSocketHandler::on_recv()
{
#ifdef BOTAN_FOUND
  if (this->use_tls)
    this->tls_recv();
  else
#endif
    this->plain_recv();
}

void TCPSocketHandler::plain_recv()
{
  static constexpr size_t buf_size = 4096;
  char buf[buf_size];
  void* recv_buf = this->get_receive_buffer(buf_size);

  if (recv_buf == nullptr)
    recv_buf = buf;

  const ssize_t size = this->do_recv(recv_buf, buf_size);

  if (size > 0)
    {
      if (buf == recv_buf)
        {
          // data needs to be placed in the in_buf string, because no buffer
          // was provided to receive that data directly. The in_buf buffer
          // will be handled in parse_in_buffer()
          this->in_buf += std::string(buf, size);
        }
      this->parse_in_buffer(size);
    }
}

ssize_t TCPSocketHandler::do_recv(void* recv_buf, const size_t buf_size)
{
  ssize_t size = ::recv(this->socket, recv_buf, buf_size, 0);
  if (0 == size)
    {
      this->on_connection_close("");
      this->close();
    }
  else if (-1 == size)
    {
109
      if (this->is_connecting())
110
        log_warning("Error connecting: ", strerror(errno));
111
      else
112
        log_warning("Error while reading from socket: ", strerror(errno));
113 114
      // Remember if we were connecting, or already connected when this
      // happened, because close() sets this->connecting to false
115
      const auto were_connecting = this->is_connecting();
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
      this->close();
      if (were_connecting)
        this->on_connection_failed(strerror(errno));
      else
        this->on_connection_close(strerror(errno));
    }
  return size;
}

void TCPSocketHandler::on_send()
{
  struct iovec msg_iov[UIO_FASTIOV] = {};
  struct msghdr msg{nullptr, 0,
      msg_iov,
      0, nullptr, 0, 0};
131
  for (const std::string& s: this->out_buf)
132 133 134 135
    {
      // unconsting the content of s is ok, sendmsg will never modify it
      msg_iov[msg.msg_iovlen].iov_base = const_cast<char*>(s.data());
      msg_iov[msg.msg_iovlen].iov_len = s.size();
louiz’'s avatar
louiz’ committed
136 137
      msg.msg_iovlen++;
      if (msg.msg_iovlen == UIO_FASTIOV)
138 139 140 141 142
        break;
    }
  ssize_t res = ::sendmsg(this->socket, &msg, MSG_NOSIGNAL);
  if (res < 0)
    {
143
      log_error("sendmsg failed: ", strerror(errno));
144 145 146 147 148 149
      this->on_connection_close(strerror(errno));
      this->close();
    }
  else
    {
      // remove all the strings that were successfully sent.
150 151
      auto it = this->out_buf.begin();
      while (it != this->out_buf.end())
152
        {
153
          if (static_cast<size_t>(res) >= it->size())
154
            {
155 156
              res -= it->size();
              ++it;
157 158 159 160 161 162
            }
          else
            {
              // If one string has partially been sent, we use substr to
              // crop it
              if (res > 0)
163
                *it = it->substr(res, std::string::npos);
164 165 166
              break;
            }
        }
167
      this->out_buf.erase(this->out_buf.begin(), it);
168 169 170 171 172 173 174
      if (this->out_buf.empty())
        this->poller->stop_watching_send_events(this);
    }
}

void TCPSocketHandler::close()
{
175
  if (this->is_connected() || this->is_connecting())
176 177 178 179 180 181 182 183
    this->poller->remove_socket_handler(this->get_socket());
  if (this->socket != -1)
    {
      ::close(this->socket);
      this->socket = -1;
    }
  this->in_buf.clear();
  this->out_buf.clear();
184 185
}

186 187 188 189
void TCPSocketHandler::send_data(std::string&& data)
{
#ifdef BOTAN_FOUND
  if (this->use_tls)
190 191 192 193 194 195 196
    try {
      this->tls_send(std::move(data));
    } catch (const Botan::TLS::TLS_Exception& e) {
      this->on_connection_close("TLS error: "s + e.what());
      this->close();
      return ;
    }
197 198 199 200 201 202 203 204 205 206
  else
#endif
    this->raw_send(std::move(data));
}

void TCPSocketHandler::raw_send(std::string&& data)
{
  if (data.empty())
    return ;
  this->out_buf.emplace_back(std::move(data));
207
  if (this->is_connected())
208 209 210 211 212
    this->poller->watch_send_events(this);
}

void TCPSocketHandler::send_pending_data()
{
213
  if (this->is_connected() && !this->out_buf.empty())
214 215 216
    this->poller->watch_send_events(this);
}

217 218 219 220 221
bool TCPSocketHandler::is_using_tls() const
{
  return this->use_tls;
}

222
void* TCPSocketHandler::get_receive_buffer(const size_t) const
223
{
224
  return nullptr;
225 226
}

227
void TCPSocketHandler::consume_in_buffer(const std::size_t size)
228
{
229
  this->in_buf = this->in_buf.substr(size, std::string::npos);
230 231 232
}

#ifdef BOTAN_FOUND
233
void TCPSocketHandler::start_tls(const std::string& address, const std::string& port)
234
{
235
  Botan::TLS::Server_Information server_info(address, "irc", std::stoul(port));
236
  this->tls = std::make_unique<Botan::TLS::Client>(
237 238 239 240 241 242 243 244
# if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,11,32)
      *this,
# else
      [this](const Botan::byte* data, size_t size) { this->tls_emit_data(data, size); },
      [this](const Botan::byte* data, size_t size) { this->tls_record_received(0, data, size); },
      [this](Botan::TLS::Alert alert, const Botan::byte*, size_t) { this->tls_alert(alert); },
      [this](const Botan::TLS::Session& session) { return this->tls_session_established(session); },
# endif
245 246
      get_session_manager(), this->credential_manager, get_policy(),
      get_rng(), server_info, Botan::TLS::Protocol_Version::latest_tls_version());
247 248 249 250 251
}

void TCPSocketHandler::tls_recv()
{
  static constexpr size_t buf_size = 4096;
252
  Botan::byte recv_buf[buf_size];
253 254 255 256 257

  const ssize_t size = this->do_recv(recv_buf, buf_size);
  if (size > 0)
    {
      const bool was_active = this->tls->is_active();
258
      try {
259
        this->tls->received_data(recv_buf, static_cast<size_t>(size));
260 261 262 263 264 265 266 267
      } catch (const Botan::TLS::TLS_Exception& e) {
        // May happen if the server sends malformed TLS data (buggy server,
        // or more probably we are just connected to a server that sends
        // plain-text)
        this->on_connection_close("TLS error: "s + e.what());
        this->close();
        return ;
      }
268 269 270 271 272 273 274
      if (!was_active && this->tls->is_active())
        this->on_tls_activated();
    }
}

void TCPSocketHandler::tls_send(std::string&& data)
{
275 276 277
  // We may not be connected yet, or the tls session has
  // not yet been negociated
  if (this->tls && this->tls->is_active())
278 279 280 281
    {
      const bool was_active = this->tls->is_active();
      if (!this->pre_buf.empty())
        {
282 283
          this->tls->send(this->pre_buf.data(), this->pre_buf.size());
          this->pre_buf.clear();
284 285 286 287 288 289 290 291
        }
      if (!data.empty())
        this->tls->send(reinterpret_cast<const Botan::byte*>(data.data()),
                       data.size());
      if (!was_active && this->tls->is_active())
        this->on_tls_activated();
    }
  else
292 293 294
    this->pre_buf.insert(this->pre_buf.end(),
                         std::make_move_iterator(data.begin()),
                         std::make_move_iterator(data.end()));
295 296
}

297
void TCPSocketHandler::tls_record_received(uint64_t, const Botan::byte *data, size_t size)
298 299 300 301 302 303 304
{
  this->in_buf += std::string(reinterpret_cast<const char*>(data),
                              size);
  if (!this->in_buf.empty())
    this->parse_in_buffer(size);
}

305
void TCPSocketHandler::tls_emit_data(const Botan::byte *data, size_t size)
306 307 308 309
{
  this->raw_send(std::string(reinterpret_cast<const char*>(data), size));
}

310
void TCPSocketHandler::tls_alert(Botan::TLS::Alert alert)
311
{
312
  log_debug("tls_alert: ", alert.type_string());
313 314
}

315
bool TCPSocketHandler::tls_session_established(const Botan::TLS::Session& session)
316
{
317 318 319
  log_debug("Handshake with ", session.server_info().hostname(), " complete.",
            " Version: ", session.version().to_string(),
            " using ", session.ciphersuite().to_string());
320
  if (!session.session_id().empty())
321
    log_debug("Session ID ", Botan::hex_encode(session.session_id()));
322
  if (!session.session_ticket().empty())
323
    log_debug("Session ticket ", Botan::hex_encode(session.session_ticket()));
324 325 326
  return true;
}

327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351
#if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,11,34)
void TCPSocketHandler::tls_verify_cert_chain(const std::vector<Botan::X509_Certificate>& cert_chain,
                                             const std::vector<std::shared_ptr<const Botan::OCSP::Response>>& ocsp_responses,
                                             const std::vector<Botan::Certificate_Store*>& trusted_roots,
                                             Botan::Usage_Type usage, const std::string& hostname,
                                             const Botan::TLS::Policy& policy)
{
  log_debug("Checking remote certificate for hostname ", hostname);
  try
    {
      Botan::TLS::Callbacks::tls_verify_cert_chain(cert_chain, ocsp_responses, trusted_roots, usage, hostname, policy);
      log_debug("Certificate is valid");
    }
  catch (const std::exception& tls_exception)
    {
      log_warning("TLS certificate check failed: ", tls_exception.what());
      std::exception_ptr exception_ptr{};
      if (this->abort_on_invalid_cert())
        exception_ptr = std::current_exception();

      check_tls_certificate(cert_chain, hostname, this->credential_manager.get_trusted_fingerprint(), exception_ptr);
    }
}
#endif

352 353
void TCPSocketHandler::on_tls_activated()
{
354
  this->send_data({});
355 356 357
}

#endif // BOTAN_FOUND