text:
#pragma once

#include "terminalpp/detail/element_difference.hpp"
#include "terminalpp/behaviour.hpp"
#include "terminalpp/terminal_state.hpp"
#include "terminalpp/string.hpp"
#include <boost/range/algorithm/for_each.hpp>
#include <type_traits>

namespace terminalpp { namespace detail {

//* =========================================================================
/// \brief A manipulator that initializes the attributes to the default if it
/// has not yet been done.
//* =========================================================================
struct TERMINALPP_EXPORT write_optional_default_attribute
{
    //* =====================================================================
    /// \brief Writes the default attribute to the terminal if necessary.
    //* =====================================================================
    template <class WriteContinuation>
    void operator()(
        terminalpp::behaviour const &beh,
        terminalpp::terminal_state &state,
        WriteContinuation &&cont) const
    {
        if (!state.last_element_)
        {
            detail::default_attribute(beh, cont);
            state.last_element_ = terminalpp::element{};
        }
    }
};

//* =========================================================================
/// \brief A manipulator that converts encoded attribute strings into ANSI
/// protocol bytes.
//* =========================================================================
class TERMINALPP_EXPORT write_element
{
public:
    //* =====================================================================
    /// \brief Constructor
    //* =====================================================================
    constexpr explicit write_element(terminalpp::element const &elem)
      : element_(elem)
    {
    }

    //* =====================================================================
    /// \brief Convert the text and write the result to the continuation
    //* =====================================================================
    template <class WriteContinuation>
    void operator()(
        terminalpp::behaviour const &beh,
        terminalpp::terminal_state &state,
        WriteContinuation &&cont) const
    {
        change_charset(
            state.last_element_->glyph_.charset_,
            element_.glyph_.charset_,
            beh,
            cont);

        change_attribute(
            state.last_element_->attribute_,
            element_.attribute_,
            beh,
            cont);

        if (element_.glyph_.charset_ == charset::utf8)
        {
            write_utf8_glyph(cont);
        }
        else
        {
            write_regular_glyph(cont);
        }

        state.last_element_ = element_;

        advance_cursor_position(state);
    }

private:
    // ======================================================================
    // WRITE_UTF8_GLYPH
    // ======================================================================
    template <class WriteContinuation>
    void write_utf8_glyph(WriteContinuation &&cont) const
    {
        std::size_t const last_utf8_index =
            [this]()
            {
                std::size_t index = 0;

                for (;
                       index < sizeof(element_.glyph_.ucharacter_)
                    && element_.glyph_.ucharacter_[index] != '\0';
                       ++index)
                {
                    if (!(element_.glyph_.ucharacter_[index] & 0x80))
                    {
                        break;
                    }
                }

                return index;
            }();

        terminalpp::bytes const data{
            element_.glyph_.ucharacter_,
            std::max(last_utf8_index, std::size_t{1u})};
        cont(data);
    }

    // ======================================================================
    // WRITE_REGULAR_GLYPH
    // ======================================================================
    template <class WriteContinuation>
    void write_regular_glyph(WriteContinuation &&cont) const
    {
        terminalpp::bytes data{&element_.glyph_.character_, 1};
        cont(data);
    }

    // ======================================================================
    // ADVANCE_CURSOR_POSITION
    // ======================================================================
    void advance_cursor_position(terminal_state &state) const
    {
        if (state.cursor_position_)
        {
            if (++state.cursor_position_->x_ == state.terminal_size_.width_)
            {
                // Terminals differ in their behaviour when reaching the
                // end of the line.  Some wrap to the next line, some bounce
                // against the edge.  To maintain consistency, forget the
                // current cursor position.
                state.cursor_position_ = {};
            }
        }
    }

    terminalpp::element element_;
};

//* =========================================================================
/// \brief A class that is used to stream manipulators together to a
/// terminal.
//* =========================================================================
template <class WriteContinuation>
class terminal_writer
{
public:
    //* =====================================================================
    /// \brief Constructor
    //* =====================================================================
    terminal_writer(
        behaviour const &beh,
        terminal_state &state,
        WriteContinuation &&wc)
      : behaviour_(beh),
        state_(state),
        write_continuation_(std::forward<WriteContinuation>(wc))
    {
    }

    //* =====================================================================
    /// \brief Streams a manipulator to the writer, which executes it on the
    /// current state.
    //* =====================================================================
    template <
        typename Manip,
        typename = typename std::enable_if<
            !std::is_convertible<typename std::remove_cv<Manip>::type, terminalpp::element>::value
         && !std::is_convertible<typename std::remove_cv<Manip>::type, terminalpp::string>::value
        >::type
    >
    terminal_writer const &operator<<(Manip &&manip) const
    {
        manip(behaviour_, state_, write_continuation_);
        return *this;
    }

    //* =====================================================================
    /// \brief Streams an attributed text element to the writer, which sends
    /// the equivalent representation in ANSI protocol bytes to the
    /// continuation.
    //* =====================================================================
    terminal_writer const &operator<<(terminalpp::element const &elem) const
    {
        *this << write_optional_default_attribute();
        return *this << detail::write_element(elem);
    }

    //* =====================================================================
    /// \brief Streams attributed text elements to the writer, which sends
    /// their equivalent representation in ANSI protocol bytes to the
    /// continuation.
    //* =====================================================================
    terminal_writer const &operator<<(terminalpp::string const &text) const
    {
        *this << write_optional_default_attribute();

        boost::for_each(
            text,
            [this](terminalpp::element const &elem)
            {
                *this << detail::write_element(elem);
            });

        return *this;
    }

private:
    behaviour const &behaviour_;
    terminal_state &state_;
    WriteContinuation write_continuation_;
};

}}

max_stars_repo_name: ExternalRepositories/terminalpp

max_issues_repo_name: ExternalRepositories/terminalpp

max_forks_repo_name: ExternalRepositories/terminalpp


text:
/*
 * Copyright 2014 Facebook, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <thrift/lib/cpp/transport/TSSLSocket.h>

#include <folly/String.h>
#include <folly/portability/OpenSSL.h>
#include <folly/portability/Sockets.h>

#include <boost/lexical_cast.hpp>
#include <boost/scoped_array.hpp>
#include <errno.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <openssl/ssl.h>
#include <openssl/x509v3.h>
#include <mutex>
#include <string>
#include <vector>

using boost::lexical_cast;
using boost::scoped_array;
using std::exception;
using std::list;
using std::shared_ptr;
using std::string;
using std::vector;

struct CRYPTO_dynlock_value {
  std::mutex mutex;
};

namespace apache { namespace thrift { namespace transport {

using folly::SSLContext;
using folly::PasswordCollector;

// TSSLSocket implementation
TSSLSocket::TSSLSocket(const shared_ptr<SSLContext>& ctx):
  TVirtualTransport<TSSLSocket, TSocket>(),
  server_(false), ssl_(nullptr), ctx_(ctx) {
}

TSSLSocket::TSSLSocket(const shared_ptr<SSLContext>& ctx, int socket):
  TVirtualTransport<TSSLSocket, TSocket>(socket),
  server_(false), ssl_(nullptr), ctx_(ctx) {
}

TSSLSocket::TSSLSocket(const shared_ptr<SSLContext>& ctx,
                       const string& host, int port) :
  TVirtualTransport<TSSLSocket, TSocket>(host, port),
  server_(false), ssl_(nullptr), ctx_(ctx) {
}

TSSLSocket::TSSLSocket(const shared_ptr<SSLContext>& ctx,
                       const folly::SocketAddress& address) :
  TVirtualTransport<TSSLSocket, TSocket>(address),
  server_(false), ssl_(nullptr), ctx_(ctx) {
}

TSSLSocket::~TSSLSocket() {
  close();
}

bool TSSLSocket::isOpen() {
  if (ssl_ == nullptr || !TSocket::isOpen()) {
    return false;
  }
  int shutdown = SSL_get_shutdown(ssl_);
  bool shutdownReceived = (shutdown & SSL_RECEIVED_SHUTDOWN);
  bool shutdownSent     = (shutdown & SSL_SENT_SHUTDOWN);
  if (shutdownReceived && shutdownSent) {
    return false;
  }
  return true;
}

bool TSSLSocket::peek() {
  if (!isOpen()) {
    return false;
  }
  checkHandshake();
  int rc;
  uint8_t byte;
  rc = SSL_peek(ssl_, &byte, 1);
  if (rc < 0) {
    throw TSSLException("SSL_peek: " + ctx_->getErrors());
  }
  if (rc == 0) {
    ERR_clear_error();
  }
  return (rc > 0);
}

void TSSLSocket::open() {
  if (isOpen() || server()) {
    throw TTransportException(TTransportException::ALREADY_OPEN);
  }
  TSocket::open();
}

void TSSLSocket::close() {
  if (ssl_ != nullptr) {
    int rc = SSL_shutdown(ssl_);
    // "According to the TLS standard, it is acceptable for an application to
    // only send its shutdown alert and then close the underlying connection
    // without waiting for the peer's response", so we don't need to call
    // SSL_shutdown a second time.
    if (rc < 0) {
      string errMsg = "SSL_shutdown: " + ctx_->getErrors();
      GlobalOutput(errMsg.c_str());
    }
    SSL_free(ssl_);
    ssl_ = nullptr;
    ERR_remove_state(0);
  }
  TSocket::close();
}

uint32_t TSSLSocket::read(uint8_t* buf, uint32_t len) {
  checkHandshake();
  int32_t bytes = 0;
  for (int32_t retries = 0; retries < maxRecvRetries_; retries++){
    bytes = SSL_read(ssl_, buf, len);
    if (bytes >= 0)
      break;
    int errnoCopy = errno;
    if (SSL_get_error(ssl_, bytes) == SSL_ERROR_SYSCALL) {
      if (ERR_get_error() == 0 && errnoCopy == EINTR) {
        continue;
      }
    }
    throw TSSLException("SSL_read: " + ctx_->getErrors(errnoCopy));
  }
  return bytes;
}

void TSSLSocket::write(const uint8_t* buf, uint32_t len) {
  checkHandshake();
  // loop in case SSL_MODE_ENABLE_PARTIAL_WRITE is set in SSL_CTX.
  uint32_t written = 0;
  while (written < len) {
    int32_t bytes = SSL_write(ssl_, &buf[written], len - written);
    if (bytes <= 0) {
      throw TSSLException("SSL_write: " + ctx_->getErrors());
    }
    written += bytes;
  }
}

void TSSLSocket::flush() {
  // Don't throw exception if not open. Thrift servers close socket twice.
  if (ssl_ == nullptr) {
    return;
  }
  checkHandshake();
  BIO* bio = SSL_get_wbio(ssl_);
  if (bio == nullptr) {
    throw TSSLException("SSL_get_wbio returns nullptr");
  }
  if (BIO_flush(bio) != 1) {
    throw TSSLException("BIO_flush: " + ctx_->getErrors());
  }
}

void TSSLSocket::checkHandshake() {
  if (!TSocket::isOpen()) {
    throw TTransportException(TTransportException::NOT_OPEN,
                              "underlying socket not open in checkHandshake");
  }
  if (ssl_ != nullptr) {
    return;
  }
  ssl_ = ctx_->createSSL();
  SSL_set_fd(ssl_, socket_);
  int rc;
  if (server()) {
    rc = SSL_accept(ssl_);
  } else {
    rc = SSL_connect(ssl_);
  }
  if (rc <= 0) {
    string errors = ctx_->getErrors();
    const char* prefix = server() ? "SSL_accept: " : "SSL_connect: ";
    throw TSSLException(prefix + errors);
  }
  if (SSL_get_verify_mode(ssl_) & SSL_VERIFY_PEER) {
    verifyCertificate();
  }
}

void TSSLSocket::verifyCertificate() {

  // verify authentication result
  long rc = SSL_get_verify_result(ssl_);
  if (rc != X509_V_OK) {
    throw TSSLException(string("SSL_get_verify_result(), ") +
                        X509_verify_cert_error_string(rc));
  }

  // nothing left to do if we're not checking the certificate name of the peer
  if (!validatePeerName(ssl_)) {
    throw TSSLException("verifyCertificate: name verification failed");
  }
}

bool TSSLSocket::validatePeerName(SSL* ssl) {
  if (!ctx_->checkPeerName()) {
    return true;
  }

  X509* cert = SSL_get_peer_certificate(ssl);
  if (cert == nullptr) {
    throw TSSLException("verifyCertificate: certificate not present");
  }
  STACK_OF(GENERAL_NAME)* alternatives = nullptr;
  bool verified = false;
  string host;
  if (!ctx_->peerFixedName().empty()) {
    host = ctx_->peerFixedName();
  }

  try {
    // only consider alternatives if we're not matching against a fixed name
    if (ctx_->peerFixedName().empty()) {
      alternatives = (STACK_OF(GENERAL_NAME)*)
        X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr);
    }
    if (alternatives != nullptr) {
      // verify subjectAlternativeName
      const int count = sk_GENERAL_NAME_num(alternatives);
      for (int i = 0; !verified && i < count; i++) {
        const GENERAL_NAME* name = sk_GENERAL_NAME_value(alternatives, i);
        if (name == nullptr) {
          continue;
        }
        char* data = (char*)ASN1_STRING_get0_data(name->d.ia5);
        int length = ASN1_STRING_length(name->d.ia5);
        switch (name->type) {
          case GEN_DNS:
            if (host.empty()) {
              host = (server() ? getPeerHost() : getHost());
            }
            if (SSLContext::matchName(host.c_str(), data, length)) {
              verified = true;
            }
            break;
          case GEN_IPADD: {
            const folly::SocketAddress* remaddr = getPeerAddress();
            if (remaddr->getFamily() == AF_INET &&
                length == sizeof(in_addr)) {
              in_addr addr = remaddr->getIPAddress().asV4().toAddr();
              if (!memcmp(&addr, data, length)) {
                verified = true;
              }
            } else if (remaddr->getFamily() == AF_INET6 &&
                       length == sizeof(in6_addr)) {
              in6_addr addr = remaddr->getIPAddress().asV6().toAddr();
              if (!memcmp(&addr, data, length)) {
                verified = true;
              }
            }
            break;
          }
        }
      }
      sk_GENERAL_NAME_pop_free(alternatives, GENERAL_NAME_free);
      alternatives = nullptr;
    }

    if (verified) {
      X509_free(cert);
      return true;
    }

    // verify commonName
    X509_NAME* name = X509_get_subject_name(cert);
    if (name != nullptr) {
      X509_NAME_ENTRY* entry;
      unsigned char* utf8;
      int last = -1;
      while (!verified) {
        last = X509_NAME_get_index_by_NID(name, NID_commonName, last);
        if (last == -1)
          break;
        entry = X509_NAME_get_entry(name, last);
        if (entry == nullptr)
          continue;
        ASN1_STRING* common = X509_NAME_ENTRY_get_data(entry);
        int size = ASN1_STRING_to_UTF8(&utf8, common);
        string fart((char*)utf8, size);
        if (host.empty()) {
          host = (server() ? getPeerHost() : getHost());
        }
        if (SSLContext::matchName(host.c_str(), (char*)utf8, size)) {
          verified = true;
        }
        OPENSSL_free(utf8);
      }
    }

    X509_free(cert);
    cert = nullptr;
  } catch (const exception& e) {
    if (alternatives) {
      sk_GENERAL_NAME_pop_free(alternatives, GENERAL_NAME_free);
    }
    if (cert) {
      X509_free(cert);
    }
    throw;
  }

  return verified;
}

// ---------------------------------------------------------------------
// TSSLSocketFactory implementation
// ---------------------------------------------------------------------

TSSLSocketFactory::TSSLSocketFactory(const shared_ptr<SSLContext>& context)
    : ctx_(context),
      server_(false) {
}

TSSLSocketFactory::~TSSLSocketFactory() {
}

shared_ptr<TSSLSocket> TSSLSocketFactory::createSocket() {
  shared_ptr<TSSLSocket> ssl(new TSSLSocket(ctx_));
  ssl->server(server());
  return ssl;
}

shared_ptr<TSSLSocket> TSSLSocketFactory::createSocket(int socket) {
  shared_ptr<TSSLSocket> ssl(new TSSLSocket(ctx_, socket));
  ssl->server(server());
  return ssl;
}

shared_ptr<TSSLSocket> TSSLSocketFactory::createSocket(const string& host,
                                                       int port) {
  shared_ptr<TSSLSocket> ssl(new TSSLSocket(ctx_, host, port));
  ssl->server(server());
  return ssl;
}

}}}

ext: cpp

max_stars_repo_name: CacheboxInc/fbthrift

max_issues_repo_name: CacheboxInc/fbthrift

max_forks_repo_name: CacheboxInc/fbthrift