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/auto_rng.h>
17
# include <botan/tls_exceptn.h>
18 19
# include <config/config.hpp>
# include <utils/dirname.hpp>
20

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

#ifndef UIO_FASTIOV
# define UIO_FASTIOV 8
#endif

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


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

52 53
TCPSocketHandler::~TCPSocketHandler()
{
54 55
  if (this->poller->is_managing_socket(this->get_socket()))
    this->poller->remove_socket_handler(this->get_socket());
56
  if (this->socket != -1)
louiz’'s avatar
louiz’ committed
57
    {
58 59
      ::close(this->socket);
      this->socket = -1;
60 61 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
    }
}

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)
    {
107
      if (this->is_connecting())
108
        log_warning("Error connecting: ", strerror(errno));
109
      else
110
        log_warning("Error while reading from socket: ", strerror(errno));
111 112
      // Remember if we were connecting, or already connected when this
      // happened, because close() sets this->connecting to false
113
      const auto were_connecting = this->is_connecting();
114 115 116 117 118 119 120 121 122 123 124 125
      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] = {};
126
  struct msghdr msg{};
127 128
  msg.msg_iov = msg_iov;
  msg.msg_iovlen = 0;
129
  for (const std::string& s: this->out_buf)
130 131 132 133
    {
      // 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
134 135
      msg.msg_iovlen++;
      if (msg.msg_iovlen == UIO_FASTIOV)
136 137 138 139 140
        break;
    }
  ssize_t res = ::sendmsg(this->socket, &msg, MSG_NOSIGNAL);
  if (res < 0)
    {
141
      log_error("sendmsg failed: ", strerror(errno));
142 143 144 145 146 147
      this->on_connection_close(strerror(errno));
      this->close();
    }
  else
    {
      // remove all the strings that were successfully sent.
148 149
      auto it = this->out_buf.begin();
      while (it != this->out_buf.end())
150
        {
151
          if (static_cast<size_t>(res) >= it->size())
152
            {
153 154
              res -= it->size();
              ++it;
155 156 157 158 159 160
            }
          else
            {
              // If one string has partially been sent, we use substr to
              // crop it
              if (res > 0)
161
                *it = it->substr(res, std::string::npos);
162 163 164
              break;
            }
        }
165
      this->out_buf.erase(this->out_buf.begin(), it);
166 167 168 169 170 171 172
      if (this->out_buf.empty())
        this->poller->stop_watching_send_events(this);
    }
}

void TCPSocketHandler::close()
{
173
  if (this->is_connected() || this->is_connecting())
174 175 176 177 178 179 180 181
    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();
182 183
}

184 185 186 187
void TCPSocketHandler::send_data(std::string&& data)
{
#ifdef BOTAN_FOUND
  if (this->use_tls)
188 189 190 191 192 193 194
    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 ;
    }
195 196 197 198 199 200 201 202 203 204
  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));
205
  if (this->is_connected())
206 207 208 209 210
    this->poller->watch_send_events(this);
}

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

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

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

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

#ifdef BOTAN_FOUND
louiz’'s avatar
louiz’ committed
231
void TCPSocketHandler::start_tls(const std::string& address, const std::string& port_string)
232
{
louiz’'s avatar
louiz’ committed
233 234
  auto port = std::min(std::stoul(port_string), static_cast<unsigned long>(std::numeric_limits<uint16_t>::max()));
  Botan::TLS::Server_Information server_info(address, "irc", static_cast<uint16_t>(port));
235 236 237 238 239
  auto policy_directory = Config::get("policy_directory", utils::dirname(Config::get_filename()));
  if (!policy_directory.empty() && policy_directory[policy_directory.size()-1] != '/')
    policy_directory += '/';
  this->policy.load(policy_directory + "policy.txt");
  this->policy.load(policy_directory + address + ".policy.txt");
240
  this->tls = std::make_unique<Botan::TLS::Client>(
241
      *this,
242
      get_session_manager(), this->credential_manager, this->policy,
243
      get_rng(), server_info, Botan::TLS::Protocol_Version::latest_tls_version());
244 245 246 247 248
}

void TCPSocketHandler::tls_recv()
{
  static constexpr size_t buf_size = 4096;
249
  Botan::byte recv_buf[buf_size];
250 251 252 253 254

  const ssize_t size = this->do_recv(recv_buf, buf_size);
  if (size > 0)
    {
      const bool was_active = this->tls->is_active();
255
      try {
256
        this->tls->received_data(recv_buf, static_cast<size_t>(size));
257 258 259 260 261 262 263 264
      } 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 ;
      }
265 266 267 268 269 270 271
      if (!was_active && this->tls->is_active())
        this->on_tls_activated();
    }
}

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

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

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

307
void TCPSocketHandler::tls_alert(Botan::TLS::Alert alert)
308
{
309
  log_debug("tls_alert: ", alert.type_string());
310 311
}

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

324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346
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);
    }
}

347 348
void TCPSocketHandler::on_tls_activated()
{
349
  this->send_data({});
350 351 352
}

#endif // BOTAN_FOUND