/** @file

  A brief file description

  @section license License

  Licensed to the Apache Software Foundation (ASF) under one
  or more contributor license agreements.  See the NOTICE file
  distributed with this work for additional information
  regarding copyright ownership.  The ASF licenses this file
  to you 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.
 */

/*************************** -*- Mod: C++ -*- ******************************
  SSLConfig.cc
   Created On      : 07/20/2000

   Description:
   SSL Configurations
 ****************************************************************************/

#include "P_SSLConfig.h"

#include <cstring>
#include <cmath>

#include "tscore/ink_config.h"
#include "tscore/ink_platform.h"
#include "tscore/I_Layout.h"
#include "records/I_RecHttp.h"

#include "HttpConfig.h"

#include "P_Net.h"
#include "P_SSLClientUtils.h"
#include "P_SSLCertLookup.h"
#include "P_SSLSNI.h"
#include "SSLDiags.h"
#include "SSLSessionCache.h"
#include "SSLSessionTicket.h"
#include "YamlSNIConfig.h"

int SSLConfig::configid                                     = 0;
int SSLCertificateConfig::configid                          = 0;
int SSLTicketKeyConfig::configid                            = 0;
int SSLConfigParams::ssl_maxrecord                          = 0;
int SSLConfigParams::ssl_misc_max_iobuffer_size_index       = 8;
bool SSLConfigParams::ssl_allow_client_renegotiation        = false;
bool SSLConfigParams::ssl_ocsp_enabled                      = false;
int SSLConfigParams::ssl_ocsp_cache_timeout                 = 3600;
int SSLConfigParams::ssl_ocsp_request_timeout               = 10;
int SSLConfigParams::ssl_ocsp_update_period                 = 60;
int SSLConfigParams::ssl_handshake_timeout_in               = 0;
size_t SSLConfigParams::session_cache_number_buckets        = 1024;
bool SSLConfigParams::session_cache_skip_on_lock_contention = false;
size_t SSLConfigParams::session_cache_max_bucket_size       = 100;
init_ssl_ctx_func SSLConfigParams::init_ssl_ctx_cb          = nullptr;
load_ssl_file_func SSLConfigParams::load_ssl_file_cb        = nullptr;
IpMap *SSLConfigParams::proxy_protocol_ipmap                = nullptr;

const uint32_t EARLY_DATA_DEFAULT_SIZE               = 16384;
uint32_t SSLConfigParams::server_max_early_data      = 0;
uint32_t SSLConfigParams::server_recv_max_early_data = EARLY_DATA_DEFAULT_SIZE;
bool SSLConfigParams::server_allow_early_data_params = false;

int SSLConfigParams::async_handshake_enabled = 0;
char *SSLConfigParams::engine_conf_file      = nullptr;

static std::unique_ptr<ConfigUpdateHandler<SSLCertificateConfig>> sslCertUpdate;
static std::unique_ptr<ConfigUpdateHandler<SSLTicketKeyConfig>> sslTicketKey;

SSLConfigParams::SSLConfigParams()
{
  ink_mutex_init(&ctxMapLock);
  reset();
}

SSLConfigParams::~SSLConfigParams()
{
  cleanup();
  ink_mutex_destroy(&ctxMapLock);
}

void
SSLConfigInit(IpMap *global)
{
  SSLConfigParams::proxy_protocol_ipmap = global;
}

void
SSLConfigParams::reset()
{
  serverCertPathOnly = serverCertChainFilename = configFilePath = serverCACertFilename = serverCACertPath = clientCertPath =
    clientKeyPath = clientCACertFilename = clientCACertPath = cipherSuite = client_cipherSuite = dhparamsFile = serverKeyPathOnly =
      clientKeyPathOnly = clientCertPathOnly = nullptr;
  ssl_ocsp_response_path_only                = nullptr;
  server_tls13_cipher_suites                 = nullptr;
  client_tls13_cipher_suites                 = nullptr;
  server_groups_list                         = nullptr;
  client_groups_list                         = nullptr;
  client_ctx                                 = nullptr;
  clientCertLevel = client_verify_depth = verify_depth = 0;
  verifyServerPolicy                                   = YamlSNIConfig::Policy::DISABLED;
  verifyServerProperties                               = YamlSNIConfig::Property::NONE;
  ssl_ctx_options                                      = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
  ssl_client_ctx_options                               = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
  ssl_session_cache                                    = SSL_SESSION_CACHE_MODE_SERVER_ATS_IMPL;
  ssl_session_cache_size                               = 1024 * 100;
  ssl_session_cache_num_buckets = 1024; // Sessions per bucket is ceil(ssl_session_cache_size / ssl_session_cache_num_buckets)
  ssl_session_cache_skip_on_contention = 0;
  ssl_session_cache_timeout            = 0;
  ssl_session_cache_auto_clear         = 1;
  configExitOnLoadError                = 1;
}

void
SSLConfigParams::cleanup()
{
  serverCertChainFilename = static_cast<char *>(ats_free_null(serverCertChainFilename));
  serverCACertFilename    = static_cast<char *>(ats_free_null(serverCACertFilename));
  serverCACertPath        = static_cast<char *>(ats_free_null(serverCACertPath));
  clientCertPath          = static_cast<char *>(ats_free_null(clientCertPath));
  clientCertPathOnly      = static_cast<char *>(ats_free_null(clientCertPathOnly));
  clientKeyPath           = static_cast<char *>(ats_free_null(clientKeyPath));
  clientKeyPathOnly       = static_cast<char *>(ats_free_null(clientKeyPathOnly));
  clientCACertFilename    = static_cast<char *>(ats_free_null(clientCACertFilename));
  clientCACertPath        = static_cast<char *>(ats_free_null(clientCACertPath));
  configFilePath          = static_cast<char *>(ats_free_null(configFilePath));
  serverCertPathOnly      = static_cast<char *>(ats_free_null(serverCertPathOnly));
  serverKeyPathOnly       = static_cast<char *>(ats_free_null(serverKeyPathOnly));
  cipherSuite             = static_cast<char *>(ats_free_null(cipherSuite));
  client_cipherSuite      = static_cast<char *>(ats_free_null(client_cipherSuite));
  dhparamsFile            = static_cast<char *>(ats_free_null(dhparamsFile));

  ssl_ocsp_response_path_only = static_cast<char *>(ats_free_null(ssl_ocsp_response_path_only));

  server_tls13_cipher_suites = static_cast<char *>(ats_free_null(server_tls13_cipher_suites));
  client_tls13_cipher_suites = static_cast<char *>(ats_free_null(client_tls13_cipher_suites));
  server_groups_list         = static_cast<char *>(ats_free_null(server_groups_list));
  client_groups_list         = static_cast<char *>(ats_free_null(client_groups_list));

  cleanupCTXTable();
  reset();
}

/**  set_paths_helper

 If path is *not* absolute, consider it relative to PREFIX
 if it's empty, just take SYSCONFDIR, otherwise we can take it as-is
 if final_path is nullptr, it will not be updated.

 XXX: Add handling for Windows?
 */
static void
set_paths_helper(const char *path, const char *filename, char **final_path, char **final_filename)
{
  if (final_path) {
    if (path && path[0] != '/') {
      *final_path = ats_stringdup(Layout::get()->relative_to(Layout::get()->prefix, path));
    } else if (!path || path[0] == '\0') {
      *final_path = ats_stringdup(RecConfigReadConfigDir());
    } else {
      *final_path = ats_strdup(path);
    }
  }

  if (final_filename && path) {
    *final_filename = filename ? ats_stringdup(Layout::get()->relative_to(path, filename)) : nullptr;
  }
}

void
SSLConfigParams::initialize()
{
  char *serverCertRelativePath          = nullptr;
  char *ssl_server_private_key_path     = nullptr;
  char *CACertRelativePath              = nullptr;
  char *ssl_client_cert_filename        = nullptr;
  char *ssl_client_cert_path            = nullptr;
  char *ssl_client_private_key_filename = nullptr;
  char *ssl_client_private_key_path     = nullptr;
  char *clientCACertRelativePath        = nullptr;
  char *ssl_server_ca_cert_filename     = nullptr;
  char *ssl_client_ca_cert_filename     = nullptr;
  char *ssl_ocsp_response_path          = nullptr;

  cleanup();

  //+++++++++++++++++++++++++ Server part +++++++++++++++++++++++++++++++++
  verify_depth = 7;

  REC_ReadConfigInt32(clientCertLevel, "proxy.config.ssl.client.certification_level");
  REC_ReadConfigStringAlloc(cipherSuite, "proxy.config.ssl.server.cipher_suite");
  REC_ReadConfigStringAlloc(client_cipherSuite, "proxy.config.ssl.client.cipher_suite");
  REC_ReadConfigStringAlloc(server_tls13_cipher_suites, "proxy.config.ssl.server.TLSv1_3.cipher_suites");
  REC_ReadConfigStringAlloc(client_tls13_cipher_suites, "proxy.config.ssl.client.TLSv1_3.cipher_suites");

  dhparamsFile = ats_stringdup(RecConfigReadConfigPath("proxy.config.ssl.server.dhparams_file"));

  int option = 0;

  REC_ReadConfigInteger(option, "proxy.config.ssl.TLSv1");
  if (!option) {
    ssl_ctx_options |= SSL_OP_NO_TLSv1;
  }

  REC_ReadConfigInteger(option, "proxy.config.ssl.client.TLSv1");
  if (!option) {
    ssl_client_ctx_options |= SSL_OP_NO_TLSv1;
  }

  REC_ReadConfigInteger(option, "proxy.config.ssl.TLSv1_1");
  if (!option) {
    ssl_ctx_options |= SSL_OP_NO_TLSv1_1;
  }

  REC_ReadConfigInteger(option, "proxy.config.ssl.client.TLSv1_1");
  if (!option) {
    ssl_client_ctx_options |= SSL_OP_NO_TLSv1_1;
  }

  REC_ReadConfigInteger(option, "proxy.config.ssl.TLSv1_2");
  if (!option) {
    ssl_ctx_options |= SSL_OP_NO_TLSv1_2;
  }

  REC_ReadConfigInteger(option, "proxy.config.ssl.client.TLSv1_2");
  if (!option) {
    ssl_client_ctx_options |= SSL_OP_NO_TLSv1_2;
  }

#ifdef SSL_OP_NO_TLSv1_3
  REC_ReadConfigInteger(option, "proxy.config.ssl.TLSv1_3");
  if (!option) {
    ssl_ctx_options |= SSL_OP_NO_TLSv1_3;
  }

  REC_ReadConfigInteger(option, "proxy.config.ssl.client.TLSv1_3");
  if (!option) {
    ssl_client_ctx_options |= SSL_OP_NO_TLSv1_3;
  }
#endif

#ifdef SSL_OP_CIPHER_SERVER_PREFERENCE
  REC_ReadConfigInteger(option, "proxy.config.ssl.server.honor_cipher_order");
  if (option) {
    ssl_ctx_options |= SSL_OP_CIPHER_SERVER_PREFERENCE;
  }
#endif

#ifdef SSL_OP_PRIORITIZE_CHACHA
  REC_ReadConfigInteger(option, "proxy.config.ssl.server.prioritize_chacha");
  if (option) {
    ssl_ctx_options |= SSL_OP_PRIORITIZE_CHACHA;
  }
#endif

#ifdef SSL_OP_NO_COMPRESSION
  ssl_ctx_options |= SSL_OP_NO_COMPRESSION;
  ssl_client_ctx_options |= SSL_OP_NO_COMPRESSION;
#else
  sk_SSL_COMP_zero(SSL_COMP_get_compression_methods());
#endif

// Enable ephemeral DH parameters for the case where we use a cipher with DH forward security.
#ifdef SSL_OP_SINGLE_DH_USE
  ssl_ctx_options |= SSL_OP_SINGLE_DH_USE;
  ssl_client_ctx_options |= SSL_OP_SINGLE_DH_USE;
#endif

#ifdef SSL_OP_SINGLE_ECDH_USE
  ssl_ctx_options |= SSL_OP_SINGLE_ECDH_USE;
  ssl_client_ctx_options |= SSL_OP_SINGLE_ECDH_USE;
#endif

  // Enable all SSL compatibility workarounds.
  ssl_ctx_options |= SSL_OP_ALL;
  ssl_client_ctx_options |= SSL_OP_ALL;

// According to OpenSSL source, applications must enable this if they support the Server Name extension. Since
// we do, then we ought to enable this. Httpd also enables this unconditionally.
#ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
  ssl_ctx_options |= SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION;
  ssl_client_ctx_options |= SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION;
#endif

  REC_ReadConfigInteger(server_max_early_data, "proxy.config.ssl.server.max_early_data");
  REC_ReadConfigInt32(server_allow_early_data_params, "proxy.config.ssl.server.allow_early_data_params");

  // According to OpenSSL the default value is 16384,
  // we keep it unless "server_max_early_data" is higher.
  server_recv_max_early_data = std::max(server_max_early_data, EARLY_DATA_DEFAULT_SIZE);

  REC_ReadConfigStringAlloc(serverCertChainFilename, "proxy.config.ssl.server.cert_chain.filename");
  REC_ReadConfigStringAlloc(serverCertRelativePath, "proxy.config.ssl.server.cert.path");
  set_paths_helper(serverCertRelativePath, nullptr, &serverCertPathOnly, nullptr);
  ats_free(serverCertRelativePath);

  configFilePath = ats_stringdup(RecConfigReadConfigPath("proxy.config.ssl.server.multicert.filename"));
  REC_ReadConfigInteger(configExitOnLoadError, "proxy.config.ssl.server.multicert.exit_on_load_fail");

  REC_ReadConfigStringAlloc(ssl_server_private_key_path, "proxy.config.ssl.server.private_key.path");
  set_paths_helper(ssl_server_private_key_path, nullptr, &serverKeyPathOnly, nullptr);
  ats_free(ssl_server_private_key_path);

  REC_ReadConfigStringAlloc(ssl_server_ca_cert_filename, "proxy.config.ssl.CA.cert.filename");
  REC_ReadConfigStringAlloc(CACertRelativePath, "proxy.config.ssl.CA.cert.path");
  set_paths_helper(CACertRelativePath, ssl_server_ca_cert_filename, &serverCACertPath, &serverCACertFilename);
  ats_free(ssl_server_ca_cert_filename);
  ats_free(CACertRelativePath);

  // SSL session cache configurations
  REC_ReadConfigInteger(ssl_session_cache, "proxy.config.ssl.session_cache");
  REC_ReadConfigInteger(ssl_session_cache_size, "proxy.config.ssl.session_cache.size");
  REC_ReadConfigInteger(ssl_session_cache_num_buckets, "proxy.config.ssl.session_cache.num_buckets");
  REC_ReadConfigInteger(ssl_session_cache_skip_on_contention, "proxy.config.ssl.session_cache.skip_cache_on_bucket_contention");
  REC_ReadConfigInteger(ssl_session_cache_timeout, "proxy.config.ssl.session_cache.timeout");
  REC_ReadConfigInteger(ssl_session_cache_auto_clear, "proxy.config.ssl.session_cache.auto_clear");

  SSLConfigParams::session_cache_max_bucket_size =
    static_cast<size_t>(ceil(static_cast<double>(ssl_session_cache_size) / ssl_session_cache_num_buckets));
  SSLConfigParams::session_cache_skip_on_lock_contention = ssl_session_cache_skip_on_contention;
  SSLConfigParams::session_cache_number_buckets          = ssl_session_cache_num_buckets;

  if (ssl_session_cache == SSL_SESSION_CACHE_MODE_SERVER_ATS_IMPL) {
    session_cache = new SSLSessionCache();
  }

  // SSL record size
  REC_EstablishStaticConfigInt32(ssl_maxrecord, "proxy.config.ssl.max_record_size");

  // SSL OCSP Stapling configurations
  REC_ReadConfigInt32(ssl_ocsp_enabled, "proxy.config.ssl.ocsp.enabled");
  REC_EstablishStaticConfigInt32(ssl_ocsp_cache_timeout, "proxy.config.ssl.ocsp.cache_timeout");
  REC_EstablishStaticConfigInt32(ssl_ocsp_request_timeout, "proxy.config.ssl.ocsp.request_timeout");
  REC_EstablishStaticConfigInt32(ssl_ocsp_update_period, "proxy.config.ssl.ocsp.update_period");
  REC_ReadConfigStringAlloc(ssl_ocsp_response_path, "proxy.config.ssl.ocsp.response.path");
  set_paths_helper(ssl_ocsp_response_path, nullptr, &ssl_ocsp_response_path_only, nullptr);
  ats_free(ssl_ocsp_response_path);

  REC_ReadConfigInt32(ssl_handshake_timeout_in, "proxy.config.ssl.handshake_timeout_in");

  REC_ReadConfigInt32(async_handshake_enabled, "proxy.config.ssl.async.handshake.enabled");
  REC_ReadConfigStringAlloc(engine_conf_file, "proxy.config.ssl.engine.conf_file");

  REC_ReadConfigStringAlloc(server_groups_list, "proxy.config.ssl.server.groups_list");

  // ++++++++++++++++++++++++ Client part ++++++++++++++++++++
  client_verify_depth = 7;

  char *verify_server = nullptr;
  REC_ReadConfigStringAlloc(verify_server, "proxy.config.ssl.client.verify.server.policy");
  if (strcmp(verify_server, "DISABLED") == 0) {
    verifyServerPolicy = YamlSNIConfig::Policy::DISABLED;
  } else if (strcmp(verify_server, "PERMISSIVE") == 0) {
    verifyServerPolicy = YamlSNIConfig::Policy::PERMISSIVE;
  } else if (strcmp(verify_server, "ENFORCED") == 0) {
    verifyServerPolicy = YamlSNIConfig::Policy::ENFORCED;
  } else {
    Warning("%s is invalid for proxy.config.ssl.client.verify.server.policy.  Should be one of DISABLED, PERMISSIVE, or ENFORCED",
            verify_server);
    verifyServerPolicy = YamlSNIConfig::Policy::DISABLED;
  }
  ats_free(verify_server);

  REC_ReadConfigStringAlloc(verify_server, "proxy.config.ssl.client.verify.server.properties");
  if (strcmp(verify_server, "SIGNATURE") == 0) {
    verifyServerProperties = YamlSNIConfig::Property::SIGNATURE_MASK;
  } else if (strcmp(verify_server, "NAME") == 0) {
    verifyServerProperties = YamlSNIConfig::Property::NAME_MASK;
  } else if (strcmp(verify_server, "ALL") == 0) {
    verifyServerProperties = YamlSNIConfig::Property::ALL_MASK;
  } else if (strcmp(verify_server, "NONE") == 0) {
    verifyServerProperties = YamlSNIConfig::Property::NONE;
  } else {
    Warning("%s is invalid for proxy.config.ssl.client.verify.server.properties.  Should be one of SIGNATURE, NAME, or ALL",
            verify_server);
    verifyServerProperties = YamlSNIConfig::Property::NONE;
  }
  ats_free(verify_server);

  ssl_client_cert_filename = nullptr;
  ssl_client_cert_path     = nullptr;
  REC_ReadConfigStringAlloc(ssl_client_cert_filename, "proxy.config.ssl.client.cert.filename");
  REC_ReadConfigStringAlloc(ssl_client_cert_path, "proxy.config.ssl.client.cert.path");
  set_paths_helper(ssl_client_cert_path, ssl_client_cert_filename, &clientCertPathOnly, &clientCertPath);
  ats_free_null(ssl_client_cert_filename);
  ats_free_null(ssl_client_cert_path);

  REC_ReadConfigStringAlloc(ssl_client_private_key_filename, "proxy.config.ssl.client.private_key.filename");
  REC_ReadConfigStringAlloc(ssl_client_private_key_path, "proxy.config.ssl.client.private_key.path");
  set_paths_helper(ssl_client_private_key_path, ssl_client_private_key_filename, &clientKeyPathOnly, &clientKeyPath);
  ats_free_null(ssl_client_private_key_filename);
  ats_free_null(ssl_client_private_key_path);

  REC_ReadConfigStringAlloc(ssl_client_ca_cert_filename, "proxy.config.ssl.client.CA.cert.filename");
  REC_ReadConfigStringAlloc(clientCACertRelativePath, "proxy.config.ssl.client.CA.cert.path");
  set_paths_helper(clientCACertRelativePath, ssl_client_ca_cert_filename, &clientCACertPath, &clientCACertFilename);
  ats_free(clientCACertRelativePath);
  ats_free(ssl_client_ca_cert_filename);

  REC_ReadConfigStringAlloc(client_groups_list, "proxy.config.ssl.client.groups_list");

  REC_ReadConfigInt32(ssl_allow_client_renegotiation, "proxy.config.ssl.allow_client_renegotiation");

  REC_ReadConfigInt32(ssl_misc_max_iobuffer_size_index, "proxy.config.ssl.misc.io.max_buffer_index");

  // Enable client regardless of config file settings as remap file
  // can cause HTTP layer to connect using SSL. But only if SSL
  // initialization hasn't failed already.
  client_ctx = this->getCTX(this->clientCertPath, this->clientKeyPath, this->clientCACertFilename, this->clientCACertPath);
  if (!client_ctx) {
    SSLError("Can't initialize the SSL client, HTTPS in remap rules will not function");
  }
}

shared_SSL_CTX
SSLConfigParams::getClientSSL_CTX() const
{
  return client_ctx;
}

void
SSLConfig::startup()
{
  reconfigure();
}

void
SSLConfig::reconfigure()
{
  Debug("ssl", "Reload SSLConfig");
  SSLConfigParams *params;
  params = new SSLConfigParams;
  params->initialize(); // re-read configuration
  configid = configProcessor.set(configid, params);
}

SSLConfigParams *
SSLConfig::acquire()
{
  return static_cast<SSLConfigParams *>(configProcessor.get(configid));
}

void
SSLConfig::release(SSLConfigParams *params)
{
  configProcessor.release(configid, params);
}

bool
SSLCertificateConfig::startup()
{
  sslCertUpdate.reset(new ConfigUpdateHandler<SSLCertificateConfig>());
  sslCertUpdate->attach("proxy.config.ssl.server.multicert.filename");
  sslCertUpdate->attach("proxy.config.ssl.server.cert.path");
  sslCertUpdate->attach("proxy.config.ssl.server.private_key.path");
  sslCertUpdate->attach("proxy.config.ssl.server.cert_chain.filename");
  sslCertUpdate->attach("proxy.config.ssl.server.session_ticket.enable");
  // Exit if there are problems on the certificate loading and the
  // proxy.config.ssl.server.multicert.exit_on_load_fail is true
  SSLConfig::scoped_config params;
  if (!reconfigure() && params->configExitOnLoadError) {
    Fatal("failed to load SSL certificate file, %s", params->configFilePath);
  }

  return true;
}

bool
SSLCertificateConfig::reconfigure()
{
  bool retStatus = true;
  SSLConfig::scoped_config params;
  SSLCertLookup *lookup = new SSLCertLookup();

  // Test SSL certificate loading startup. With large numbers of certificates, reloading can take time, so delay
  // twice the healthcheck period to simulate a loading a large certificate set.
  if (is_action_tag_set("test.multicert.delay")) {
    const int secs = 60;
    Debug("ssl", "delaying certificate reload by %d secs", secs);
    ink_hrtime_sleep(HRTIME_SECONDS(secs));
  }

  SSLMultiCertConfigLoader loader(params);
  loader.load(lookup);

  if (!lookup->is_valid) {
    retStatus = false;
  }
  // If there are errors in the certificate configs and we had wanted to exit on error
  // we won't want to reset the config
  if (retStatus || !params->configExitOnLoadError) {
    configid = configProcessor.set(configid, lookup);
  } else {
    delete lookup;
  }

  if (retStatus) {
    Note("%s finished loading", params->configFilePath);
  } else {
    Error("%s failed to load", params->configFilePath);
  }

  return retStatus;
}

SSLCertLookup *
SSLCertificateConfig::acquire()
{
  return static_cast<SSLCertLookup *>(configProcessor.get(configid));
}

void
SSLCertificateConfig::release(SSLCertLookup *lookup)
{
  configProcessor.release(configid, lookup);
}

bool
SSLTicketParams::LoadTicket(bool &nochange)
{
  cleanup();
  nochange = true;

#if TS_HAS_TLS_SESSION_TICKET
  ssl_ticket_key_block *keyblock = nullptr;

  SSLConfig::scoped_config params;
  time_t last_load_time    = 0;
  bool no_default_keyblock = true;

  SSLTicketKeyConfig::scoped_config ticket_params;
  if (ticket_params) {
    last_load_time      = ticket_params->load_time;
    no_default_keyblock = ticket_params->default_global_keyblock == nullptr;
  }

  // elevate/allow file access to root read only files/certs
  uint32_t elevate_setting = 0;
  REC_ReadConfigInteger(elevate_setting, "proxy.config.ssl.cert.load_elevated");
  ElevateAccess elevate_access(elevate_setting ? ElevateAccess::FILE_PRIVILEGE : 0); // destructor will demote for us

  if (REC_ReadConfigStringAlloc(ticket_key_filename, "proxy.config.ssl.server.ticket_key.filename") == REC_ERR_OKAY &&
      ticket_key_filename != nullptr) {
    ats_scoped_str ticket_key_path(Layout::relative_to(params->serverCertPathOnly, ticket_key_filename));
    // See if the file changed since we last loaded
    struct stat sdata;
    if (last_load_time && (stat(ticket_key_filename, &sdata) >= 0)) {
      if (sdata.st_mtime && sdata.st_mtime <= last_load_time) {
        Debug("ssl", "ticket key %s has not changed", ticket_key_filename);
        // No updates since last load
        return true;
      }
    }
    nochange = false;
    keyblock = ssl_create_ticket_keyblock(ticket_key_path);
    // Initialize if we don't have one yet
  } else if (no_default_keyblock) {
    nochange = false;
    keyblock = ssl_create_ticket_keyblock(nullptr);
  } else {
    // No need to update.  Keep the previous ticket param
    return true;
  }
  if (!keyblock) {
    Error("Could not load ticket key from %s", ticket_key_filename);
    return false;
  }
  default_global_keyblock = keyblock;
  load_time               = time(nullptr);

  Debug("ssl", "ticket key reloaded from %s", ticket_key_filename);
#endif
  return true;
}

void
SSLTicketParams::LoadTicketData(char *ticket_data, int ticket_data_len)
{
  cleanup();
#if TS_HAS_TLS_SESSION_TICKET
  if (ticket_data != nullptr && ticket_data_len > 0) {
    default_global_keyblock = ticket_block_create(ticket_data, ticket_data_len);
  } else {
    default_global_keyblock = ssl_create_ticket_keyblock(nullptr);
  }
  load_time = time(nullptr);
#endif
}

void
SSLTicketKeyConfig::startup()
{
  sslTicketKey.reset(new ConfigUpdateHandler<SSLTicketKeyConfig>());

  sslTicketKey->attach("proxy.config.ssl.server.ticket_key.filename");
  SSLConfig::scoped_config params;
  if (!reconfigure() && params->configExitOnLoadError) {
    Fatal("Failed to load SSL ticket key file");
  }
}

bool
SSLTicketKeyConfig::reconfigure()
{
  SSLTicketParams *ticketKey = new SSLTicketParams();

  if (ticketKey) {
    bool nochange = false;
    if (!ticketKey->LoadTicket(nochange)) {
      delete ticketKey;
      return false;
    }
    // Nothing updated, leave the original configuration
    if (nochange) {
      delete ticketKey;
      return true;
    }
  }
  configid = configProcessor.set(configid, ticketKey);
  return true;
}

bool
SSLTicketKeyConfig::reconfigure_data(char *ticket_data, int ticket_data_len)
{
  SSLTicketParams *ticketKey = new SSLTicketParams();
  if (ticketKey) {
    ticketKey->LoadTicketData(ticket_data, ticket_data_len);
  }
  configid = configProcessor.set(configid, ticketKey);
  return true;
}

void
SSLTicketParams::cleanup()
{
  ticket_block_free(default_global_keyblock);
  ticket_key_filename = static_cast<char *>(ats_free_null(ticket_key_filename));
}

shared_SSL_CTX
SSLConfigParams::getCTX(const char *client_cert, const char *key_file, const char *ca_bundle_file, const char *ca_bundle_path) const
{
  shared_SSL_CTX client_ctx = nullptr;
  std::string top_level_key, ctx_key;
  ts::bwprint(top_level_key, "{}:{}", ca_bundle_file, ca_bundle_path);
  ts::bwprint(ctx_key, "{}:{}", client_cert, key_file);

  Debug("ssl", "Load client cert %s %s", client_cert, key_file);

  auto ctx_map_iter = top_level_ctx_map.find(top_level_key);
  if (ctx_map_iter != top_level_ctx_map.end()) {
    auto ctx_iter = ctx_map_iter->second.find(ctx_key);
    if (ctx_iter != ctx_map_iter->second.end()) {
      client_ctx = ctx_iter->second;
    }
  }

  // Create context if doesn't exists
  if (!client_ctx) {
    client_ctx = shared_SSL_CTX(SSLInitClientContext(this), SSLReleaseContext);

    if (client_cert) {
      // Set public and private keys
      if (!SSL_CTX_use_certificate_chain_file(client_ctx.get(), client_cert)) {
        SSLError("failed to load client certificate from %s", client_cert);
        goto fail;
      }
      if (!key_file || key_file[0] == '\0') {
        key_file = client_cert;
      }
      if (!SSL_CTX_use_PrivateKey_file(client_ctx.get(), key_file, SSL_FILETYPE_PEM)) {
        SSLError("failed to load client private key file from %s", key_file);
        goto fail;
      }

      if (!SSL_CTX_check_private_key(client_ctx.get())) {
        SSLError("client private key (%s) does not match the certificate public key (%s)", key_file, client_cert);
        goto fail;
      }
    }

    // Set CA information for verifying peer cert
    if (ca_bundle_file != nullptr || ca_bundle_path != nullptr) {
      if (!SSL_CTX_load_verify_locations(client_ctx.get(), ca_bundle_file, ca_bundle_path)) {
        SSLError("invalid client CA Certificate file (%s) or CA Certificate path (%s)", ca_bundle_file, ca_bundle_path);
        goto fail;
      }
    } else if (!SSL_CTX_set_default_verify_paths(client_ctx.get())) {
      SSLError("failed to set the default verify paths");
      goto fail;
    }

    // Try to update the context in mapping with lock acquired. If a valid context exists, return it without changing the structure.
    ink_mutex_acquire(&ctxMapLock);
    auto ctx_iter = top_level_ctx_map[top_level_key].find(ctx_key);
    if (ctx_iter == top_level_ctx_map[top_level_key].end() || ctx_iter->second == nullptr) {
      top_level_ctx_map[top_level_key][ctx_key] = client_ctx;
    } else {
      client_ctx = ctx_iter->second;
    }
    ink_mutex_release(&ctxMapLock);
  }
  return client_ctx;

fail:
  return nullptr;
}

void
SSLConfigParams::cleanupCTXTable()
{
  ink_mutex_acquire(&ctxMapLock);
  top_level_ctx_map.clear();
  ink_mutex_release(&ctxMapLock);
}
