TEST_P(SslSocketTest, TestConnectionFailsWhenRejectOnExpiredAndResponseExpired) {
  const std::string server_ctx_yaml = R"EOF(
  common_tls_context:
    tls_certificates:
    - certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/ocsp/test_data/good_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/ocsp/test_data/good_key.pem"
      ocsp_staple:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/ocsp/test_data/unknown_ocsp_resp.der"
  ocsp_staple_policy: strict_stapling
  )EOF";

  const std::string client_ctx_yaml = R"EOF(
  common_tls_context:
    tls_params:
      cipher_suites:
      - TLS_RSA_WITH_AES_128_GCM_SHA256
)EOF";

  TestUtilOptions test_options(client_ctx_yaml, server_ctx_yaml, false, GetParam());
  testUtil(test_options.setExpectedServerStats("ssl.ocsp_staple_failed").enableOcspStapling());
}


  TestUtilOptionsV2& setClientSession(const std::string& client_session) {
    client_session_ = client_session;
    return *this;
  }

CVE-2022-21654

tls allows re-use when some cert validation settings have changed

Signed-off-by: Yan Avlasov <yavlasov@google.com>


const std::string& expectedPeerSubject() const { return expected_peer_subject_; }


  TestUtilOptionsV2& setExpectedClientStats(const std::string& expected_client_stats) {
    expected_client_stats_ = expected_client_stats;
    return *this;
  }


  TestUtilOptions& setExpectedPeerSubject(const std::string& expected_peer_subject) {
    expected_peer_subject_ = expected_peer_subject;
    return *this;
  }


bool expectedPrivateKeyMethod() const { return expect_private_key_method_; }


const std::string& expectedClientStats() const { return expected_client_stats_; }


TEST_P(SslSocketTest, RsaPrivateKeyProviderAsyncDecryptCompleteFailure) {
  const std::string server_ctx_yaml = R"EOF(
  common_tls_context:
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_cert.pem"
      private_key_provider:
        provider_name: test
        typed_config:
          "@type": type.googleapis.com/google.protobuf.Struct
          value:
            private_key_file: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_key.pem"
            expected_operation: decrypt
            async_method_error: true
            mode: rsa
    validation_context:
      trusted_ca:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem"
      crl:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.crl"
)EOF";
  const std::string failing_client_ctx_yaml = R"EOF(
  common_tls_context:
    tls_params:
      cipher_suites:
      - TLS_RSA_WITH_AES_128_GCM_SHA256
)EOF";

  TestUtilOptions failing_test_options(failing_client_ctx_yaml, server_ctx_yaml, false, GetParam());
  testUtil(failing_test_options.setPrivateKeyMethodExpected(true)
               .setExpectedServerCloseEvent(Network::ConnectionEvent::LocalClose)
               .setExpectedServerStats("ssl.connection_error")
               .setExpectedTransportFailureReasonContains("system library")
               .setNotExpectedClientStats("ssl.connection_error"));
}


TEST_P(SslSocketTest, RevokedLeafCertificateOnlyLeafCRLValidation) {
  // The test checks that revoked certificate will makes the validation fails even if we set
  // only_verify_leaf_cert_crl to true.
  //
  // Trust chain contains:
  //  - Root authority certificate (i.e., ca_cert.pem)
  //  - Intermediate authority certificate (i.e., intermediate_ca_cert.pem)
  //  - Intermediate authority certificate revocation list (i.e., intermediate_ca_cert.crl)
  //
  // Trust chain omits (But this test will succeed):
  //  - Root authority certificate revocation list (i.e., ca_cert.crl)
  const std::string incomplete_server_ctx_yaml = R"EOF(
  common_tls_context:
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_key.pem"
    validation_context:
      trusted_ca:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/intermediate_ca_cert_chain_with_crl.pem"
      only_verify_leaf_cert_crl: true
)EOF";

  // This should fail, since the certificate has been revoked.
  const std::string revoked_client_ctx_yaml = R"EOF(
  common_tls_context:
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns3_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns3_key.pem"
)EOF";

  TestUtilOptions complete_revoked_test_options(revoked_client_ctx_yaml, incomplete_server_ctx_yaml,
                                                false, GetParam());
  testUtil(complete_revoked_test_options.setExpectedServerStats("ssl.fail_verify_error")
               .setExpectedVerifyErrorCode(X509_V_ERR_CERT_REVOKED));
}


const std::string& clientSession() const { return client_session_; }


TEST_P(SslSocketTest, RsaPrivateKeyProviderDecryptFailure) {
  const std::string server_ctx_yaml = R"EOF(
  common_tls_context:
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_cert.pem"
      private_key_provider:
        provider_name: test
        typed_config:
          "@type": type.googleapis.com/google.protobuf.Struct
          value:
            private_key_file: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_key.pem"
            expected_operation: decrypt
            method_error: true
            mode: rsa
    validation_context:
      trusted_ca:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem"
      crl:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.crl"
)EOF";
  const std::string failing_client_ctx_yaml = R"EOF(
  common_tls_context:
    tls_params:
      cipher_suites:
      - TLS_RSA_WITH_AES_128_GCM_SHA256
)EOF";

  TestUtilOptions failing_test_options(failing_client_ctx_yaml, server_ctx_yaml, false, GetParam());
  testUtil(failing_test_options.setPrivateKeyMethodExpected(true).setExpectedServerStats(
      "ssl.connection_error"));
}


const std::string& expectedPeerIssuer() const { return expected_peer_issuer_; }


  TestUtilOptionsBase(bool expect_success, Network::Address::IpVersion version)
      : expect_success_(expect_success), version_(version) {}


TEST_P(SslSocketTest, ClientCertificateHashAndSpkiVerificationNoCA) {
  envoy::config::listener::v3::Listener listener;
  envoy::config::listener::v3::FilterChain* filter_chain = listener.add_filter_chains();
  envoy::extensions::transport_sockets::tls::v3::DownstreamTlsContext tls_context;
  envoy::extensions::transport_sockets::tls::v3::TlsCertificate* server_cert =
      tls_context.mutable_common_tls_context()->add_tls_certificates();
  server_cert->mutable_certificate_chain()->set_filename(TestEnvironment::substitute(
      "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem"));
  server_cert->mutable_private_key()->set_filename(TestEnvironment::substitute(
      "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_key.pem"));
  envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext*
      server_validation_ctx =
          tls_context.mutable_common_tls_context()->mutable_validation_context();
  server_validation_ctx->add_verify_certificate_hash(
      "0000000000000000000000000000000000000000000000000000000000000000");
  server_validation_ctx->add_verify_certificate_spki(TEST_SAN_DNS_CERT_SPKI);
  server_validation_ctx->add_verify_certificate_spki(TEST_SAN_URI_CERT_SPKI);
  updateFilterChain(tls_context, *filter_chain);

  envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext client;
  envoy::extensions::transport_sockets::tls::v3::TlsCertificate* client_cert =
      client.mutable_common_tls_context()->add_tls_certificates();
  client_cert->mutable_certificate_chain()->set_filename(TestEnvironment::substitute(
      "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_cert.pem"));
  client_cert->mutable_private_key()->set_filename(TestEnvironment::substitute(
      "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_key.pem"));

  TestUtilOptionsV2 test_options(listener, client, true, GetParam());
  testUtilV2(test_options.setExpectedClientCertUri("spiffe://lyft.com/test-team")
                 .setExpectedServerCertDigest(TEST_SAN_DNS_CERT_256_HASH));

  // Works even with client renegotiation.
  client.set_allow_renegotiation(true);
  testUtilV2(test_options);
}


TEST_P(SslSocketTest, GetIssueExpireTimesPeerCert) {
  const std::string client_ctx_yaml = R"EOF(
  common_tls_context:
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/no_san_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/no_san_key.pem"
)EOF";

  const std::string server_ctx_yaml = R"EOF(
  common_tls_context:
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_key.pem"
    validation_context:
      trusted_ca:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem"
  require_client_certificate: true
)EOF";
  TestUtilOptions test_options(client_ctx_yaml, server_ctx_yaml, true, GetParam());
  testUtil(test_options.setExpectedSerialNumber(TEST_NO_SAN_CERT_SERIAL)
               .setExpectedValidFromTimePeerCert(TEST_NO_SAN_CERT_NOT_BEFORE)
               .setExpectedExpirationTimePeerCert(TEST_NO_SAN_CERT_NOT_AFTER));
}


  TestUtilOptionsV2& setExpectedTransportFailureReasonContains(
      const std::string& expected_transport_failure_reason_contains) {
    expected_transport_failure_reason_contains_ = expected_transport_failure_reason_contains;
    return *this;
  }


TEST_P(SslReadBufferLimitTest, SmallReadsIntoSameSlice) {
  // write_size * num_writes must be large enough to cause buffer reserving fragmentation,
  // but smaller than one reservation so the expected slice to be 1.
  const uint32_t write_size = 1;
  const uint32_t num_writes = 12 * 1024;
  const uint32_t read_buffer_limit = write_size * num_writes;
  const uint32_t expected_chunk_size = write_size * num_writes;

  initialize();

  EXPECT_CALL(listener_callbacks_, onAccept_(_))
      .WillOnce(Invoke([&](Network::ConnectionSocketPtr& socket) -> void {
        server_connection_ = dispatcher_->createServerConnection(
            std::move(socket), server_ssl_socket_factory_->createTransportSocket(nullptr),
            stream_info_);
        server_connection_->setBufferLimits(read_buffer_limit);
        server_connection_->addConnectionCallbacks(server_callbacks_);
        server_connection_->addReadFilter(read_filter_);
        EXPECT_EQ("", server_connection_->nextProtocol());
        EXPECT_EQ(read_buffer_limit, server_connection_->bufferLimit());
      }));

  EXPECT_CALL(client_callbacks_, onEvent(Network::ConnectionEvent::Connected))
      .WillOnce(Invoke([&](Network::ConnectionEvent) -> void { dispatcher_->exit(); }));
  dispatcher_->run(Event::Dispatcher::RunType::Block);

  uint32_t filter_seen = 0;

  EXPECT_CALL(*read_filter_, onNewConnection());
  EXPECT_CALL(*read_filter_, onData(_, _))
      .WillRepeatedly(Invoke([&](Buffer::Instance& data, bool) -> Network::FilterStatus {
        EXPECT_GE(expected_chunk_size, data.length());
        EXPECT_EQ(1, data.getRawSlices().size());
        filter_seen += data.length();
        data.drain(data.length());
        if (filter_seen == (write_size * num_writes)) {
          server_connection_->close(Network::ConnectionCloseType::FlushWrite);
        }
        return Network::FilterStatus::StopIteration;
      }));

  EXPECT_CALL(client_callbacks_, onEvent(Network::ConnectionEvent::RemoteClose))
      .WillOnce(Invoke([&](Network::ConnectionEvent) -> void {
        EXPECT_EQ((write_size * num_writes), filter_seen);
        dispatcher_->exit();
      }));

  for (uint32_t i = 0; i < num_writes; i++) {
    Buffer::OwnedImpl data(std::string(write_size, 'a'));
    client_connection_->write(data, false);
  }

  dispatcher_->run(Event::Dispatcher::RunType::Block);
}


TEST_P(SslSocketTest, HalfClose) {
  const std::string server_ctx_yaml = R"EOF(
  common_tls_context:
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_key.pem"
    validation_context:
      trusted_ca:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_certificates.pem"
)EOF";

  envoy::extensions::transport_sockets::tls::v3::DownstreamTlsContext server_tls_context;
  TestUtility::loadFromYaml(TestEnvironment::substitute(server_ctx_yaml), server_tls_context);
  auto server_cfg = std::make_unique<ServerContextConfigImpl>(server_tls_context, factory_context_);
  ContextManagerImpl manager(time_system_);
  Stats::TestUtil::TestStore server_stats_store;
  ServerSslSocketFactory server_ssl_socket_factory(std::move(server_cfg), manager,
                                                   server_stats_store, std::vector<std::string>{});

  auto socket = std::make_shared<Network::Test::TcpListenSocketImmediateListen>(
      Network::Test::getCanonicalLoopbackAddress(GetParam()));
  Network::MockTcpListenerCallbacks listener_callbacks;
  Network::ListenerPtr listener =
      dispatcher_->createListener(socket, listener_callbacks, runtime_, true, false);
  std::shared_ptr<Network::MockReadFilter> server_read_filter(new Network::MockReadFilter());
  std::shared_ptr<Network::MockReadFilter> client_read_filter(new Network::MockReadFilter());

  const std::string client_ctx_yaml = R"EOF(
    common_tls_context:
  )EOF";

  envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext tls_context;
  TestUtility::loadFromYaml(TestEnvironment::substitute(client_ctx_yaml), tls_context);
  auto client_cfg = std::make_unique<ClientContextConfigImpl>(tls_context, factory_context_);
  Stats::TestUtil::TestStore client_stats_store;
  ClientSslSocketFactory client_ssl_socket_factory(std::move(client_cfg), manager,
                                                   client_stats_store);
  Network::ClientConnectionPtr client_connection = dispatcher_->createClientConnection(
      socket->connectionInfoProvider().localAddress(), Network::Address::InstanceConstSharedPtr(),
      client_ssl_socket_factory.createTransportSocket(nullptr), nullptr);
  client_connection->enableHalfClose(true);
  client_connection->addReadFilter(client_read_filter);
  client_connection->connect();
  Network::MockConnectionCallbacks client_connection_callbacks;
  client_connection->addConnectionCallbacks(client_connection_callbacks);

  Network::ConnectionPtr server_connection;
  Network::MockConnectionCallbacks server_connection_callbacks;
  EXPECT_CALL(listener_callbacks, onAccept_(_))
      .WillOnce(Invoke([&](Network::ConnectionSocketPtr& socket) -> void {
        server_connection = dispatcher_->createServerConnection(
            std::move(socket), server_ssl_socket_factory.createTransportSocket(nullptr),
            stream_info_);
        server_connection->enableHalfClose(true);
        server_connection->addReadFilter(server_read_filter);
        server_connection->addConnectionCallbacks(server_connection_callbacks);
        Buffer::OwnedImpl data("hello");
        server_connection->write(data, true);
      }));

  EXPECT_CALL(*server_read_filter, onNewConnection())
      .WillOnce(Return(Network::FilterStatus::Continue));
  EXPECT_CALL(*client_read_filter, onNewConnection())
      .WillOnce(Return(Network::FilterStatus::Continue));
  EXPECT_CALL(server_connection_callbacks, onEvent(Network::ConnectionEvent::Connected));
  EXPECT_CALL(client_connection_callbacks, onEvent(Network::ConnectionEvent::Connected));
  EXPECT_CALL(*client_read_filter, onData(BufferStringEqual("hello"), true))
      .WillOnce(Invoke([&](Buffer::Instance&, bool) -> Network::FilterStatus {
        Buffer::OwnedImpl buffer("world");
        client_connection->write(buffer, true);
        return Network::FilterStatus::Continue;
      }));
  EXPECT_CALL(client_connection_callbacks, onEvent(Network::ConnectionEvent::LocalClose));
  EXPECT_CALL(*server_read_filter, onData(BufferStringEqual("world"), true));
  EXPECT_CALL(server_connection_callbacks, onEvent(Network::ConnectionEvent::RemoteClose))
      .WillOnce(Invoke([&](Network::ConnectionEvent) -> void { dispatcher_->exit(); }));

  dispatcher_->run(Event::Dispatcher::RunType::Block);
}


size_t DefaultCertValidator::daysUntilFirstCertExpires() const {
  return Utility::getDaysUntilExpiration(ca_cert_.get(), time_source_);
}


const std::string& expectedPeerCert() const { return expected_peer_cert_; }


TEST_P(SslSocketTest, ClientSessionResumptionEnabledTls13) {
  const std::string server_ctx_yaml = R"EOF(
  common_tls_context:
    tls_params:
      tls_minimum_protocol_version: TLSv1_3
      tls_maximum_protocol_version: TLSv1_3
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_key.pem"
)EOF";

  const std::string client_ctx_yaml = R"EOF(
  common_tls_context:
    tls_params:
      tls_minimum_protocol_version: TLSv1_3
      tls_maximum_protocol_version: TLSv1_3
  max_session_keys: 2
)EOF";

  testClientSessionResumption(server_ctx_yaml, client_ctx_yaml, true, GetParam());
}


  TestUtilOptions& setExpectedClientCertUri(const std::string& expected_client_cert_uri) {
    TestUtilOptionsBase::setExpectedClientCertUri(expected_client_cert_uri);
    return *this;
  }


  TestUtilOptions& setExpectedOcspResponse(const std::string& expected_ocsp_response) {
    expected_ocsp_response_ = expected_ocsp_response;
    return *this;
  }


TEST_P(SslSocketTest, FailedClientCertificateDefaultExpirationVerification) {
  envoy::config::listener::v3::Listener listener;
  envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext client;

  configureServerAndExpiredClientCertificate(listener, client, /*server_config=*/{});

  TestUtilOptionsV2 test_options(listener, client, false, GetParam());
  testUtilV2(test_options.setExpectedClientCertUri("spiffe://lyft.com/test-team")
                 .setExpectedTransportFailureReasonContains("SSLV3_ALERT_CERTIFICATE_EXPIRED"));
}


TEST_P(SslSocketTest, ClientCertificateHashVerificationNoCA) {
  const std::string client_ctx_yaml = R"EOF(
  common_tls_context:
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_key.pem"
)EOF";

  const std::string server_ctx_yaml = absl::StrCat(R"EOF(
  common_tls_context:
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_key.pem"
    validation_context:
      verify_certificate_hash: ")EOF",
                                                   TEST_SAN_URI_CERT_256_HASH, "\"");

  TestUtilOptions test_options(client_ctx_yaml, server_ctx_yaml, true, GetParam());
  testUtil(test_options.setExpectedClientCertUri("spiffe://lyft.com/test-team")
               .setExpectedSerialNumber(TEST_SAN_URI_CERT_SERIAL));
}


bool expectNoCert() const { return expect_no_cert_; }


const std::string& expectedSerialNumber() const { return expected_serial_number_; }


const std::vector<std::string>& expectedLocalUri() const { return expected_local_uri_; }


TEST_P(SslSocketTest, TicketSessionResumptionWrongKey) {
  const std::string server_ctx_yaml1 = R"EOF(
  common_tls_context:
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_key.pem"
  session_ticket_keys:
    keys:
      filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ticket_key_a"
)EOF";

  const std::string server_ctx_yaml2 = R"EOF(
  common_tls_context:
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_key.pem"
  session_ticket_keys:
    keys:
      filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ticket_key_b"
)EOF";

  const std::string client_ctx_yaml = R"EOF(
    common_tls_context:
  )EOF";

  testTicketSessionResumption(server_ctx_yaml1, {}, server_ctx_yaml2, {}, client_ctx_yaml, false,
                              GetParam());
}


TEST_P(SslSocketTest, TicketSessionResumptionDifferentVerifyCertHash) {
  const std::string server_ctx_yaml1 = absl::StrCat(R"EOF(
  session_ticket_keys:
    keys:
      filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ticket_key_a"
  common_tls_context:
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_key.pem"
    validation_context:
      trusted_ca:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem"
      verify_certificate_hash:
        - ")EOF",
                                                    TEST_SAN_URI_CERT_256_HASH, "\"");

  const std::string server_ctx_yaml2 = absl::StrCat(R"EOF(
  session_ticket_keys:
    keys:
      filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ticket_key_a"
  common_tls_context:
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_key.pem"
    validation_context:
      trusted_ca:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem"
      verify_certificate_hash:
        - "0000000000000000000000000000000000000000000000000000000000000000"
        - ")EOF",
                                                    TEST_SAN_URI_CERT_256_HASH, "\"");

  const std::string client_ctx_yaml = R"EOF(
  common_tls_context:
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_key.pem"
)EOF";

  testTicketSessionResumption(server_ctx_yaml1, {}, server_ctx_yaml1, {}, client_ctx_yaml, true,
                              GetParam());
  testTicketSessionResumption(server_ctx_yaml1, {}, server_ctx_yaml2, {}, client_ctx_yaml, false,
                              GetParam());
}


TEST_P(SslSocketTest, TestConnectionFailsOnStapleRequiredAndOcspExpired) {
  const std::string server_ctx_yaml = R"EOF(
  common_tls_context:
    tls_certificates:
    - certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/ocsp/test_data/good_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/ocsp/test_data/good_key.pem"
      ocsp_staple:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/ocsp/test_data/unknown_ocsp_resp.der"
  ocsp_staple_policy: must_staple
  )EOF";

  const std::string client_ctx_yaml = R"EOF(
  common_tls_context:
    tls_params:
      cipher_suites:
      - TLS_RSA_WITH_AES_128_GCM_SHA256
)EOF";
  TestUtilOptions test_options(client_ctx_yaml, server_ctx_yaml, false, GetParam());
  testUtil(test_options.setExpectedServerStats("ssl.ocsp_staple_failed").enableOcspStapling());
}


const std::string& expectedLocalSubject() const { return expected_local_subject_; }


TEST_P(SslSocketTest, StatelessSessionResumptionDisabled) {
  const std::string server_ctx_yaml = R"EOF(
  common_tls_context:
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_key.pem"
  disable_stateless_session_resumption: true
)EOF";

  const std::string client_ctx_yaml = R"EOF(
    common_tls_context:
  )EOF";

  testSupportForStatelessSessionResumption(server_ctx_yaml, client_ctx_yaml, false, GetParam());
}


TEST_P(SslSocketTest, DownstreamNotReadySslSocket) {
  Stats::TestUtil::TestStore stats_store;
  NiceMock<LocalInfo::MockLocalInfo> local_info;
  testing::NiceMock<Server::Configuration::MockTransportSocketFactoryContext> factory_context;
  NiceMock<Init::MockManager> init_manager;
  NiceMock<Event::MockDispatcher> dispatcher;
  EXPECT_CALL(factory_context, mainThreadDispatcher()).WillRepeatedly(ReturnRef(dispatcher));
  EXPECT_CALL(factory_context, localInfo()).WillOnce(ReturnRef(local_info));
  EXPECT_CALL(factory_context, stats()).WillOnce(ReturnRef(stats_store));
  EXPECT_CALL(factory_context, initManager()).WillRepeatedly(ReturnRef(init_manager));

  envoy::extensions::transport_sockets::tls::v3::DownstreamTlsContext tls_context;
  auto sds_secret_configs =
      tls_context.mutable_common_tls_context()->mutable_tls_certificate_sds_secret_configs()->Add();
  sds_secret_configs->set_name("abc.com");
  sds_secret_configs->mutable_sds_config();
  auto server_cfg = std::make_unique<ServerContextConfigImpl>(tls_context, factory_context);
  EXPECT_TRUE(server_cfg->tlsCertificates().empty());
  EXPECT_FALSE(server_cfg->isReady());

  ContextManagerImpl manager(time_system_);
  ServerSslSocketFactory server_ssl_socket_factory(std::move(server_cfg), manager, stats_store,
                                                   std::vector<std::string>{});
  auto transport_socket = server_ssl_socket_factory.createTransportSocket(nullptr);
  EXPECT_FALSE(transport_socket->startSecureTransport());                                  // Noop
  transport_socket->configureInitialCongestionWindow(200, std::chrono::microseconds(223)); // Noop
  EXPECT_EQ(EMPTY_STRING, transport_socket->protocol());
  EXPECT_EQ(nullptr, transport_socket->ssl());
  EXPECT_EQ(true, transport_socket->canFlushClose());
  Buffer::OwnedImpl buffer;
  Network::IoResult result = transport_socket->doRead(buffer);
  EXPECT_EQ(Network::PostIoAction::Close, result.action_);
  result = transport_socket->doWrite(buffer, true);
  EXPECT_EQ(Network::PostIoAction::Close, result.action_);
  EXPECT_EQ("TLS error: Secret is not supplied by SDS", transport_socket->failureReason());
}


TEST_P(SslSocketTest, GetPeerCertAcceptUntrusted) {
  const std::string client_ctx_yaml = R"EOF(
  common_tls_context:
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/no_san_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/no_san_key.pem"
)EOF";

  const std::string server_ctx_yaml = R"EOF(
  common_tls_context:
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_key.pem"
    validation_context:
      trusted_ca:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/fake_ca_cert.pem"
      trust_chain_verification: ACCEPT_UNTRUSTED
  require_client_certificate: true
)EOF";

  TestUtilOptions test_options(client_ctx_yaml, server_ctx_yaml, true, GetParam());
  std::string expected_peer_cert =
      TestEnvironment::readFileToStringForTest(TestEnvironment::substitute(
          "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/no_san_cert.pem"));
  testUtil(test_options.setExpectedSerialNumber(TEST_NO_SAN_CERT_SERIAL)
               .setExpectedPeerIssuer(
                   "CN=Test CA,OU=Lyft Engineering,O=Lyft,L=San Francisco,ST=California,C=US")
               .setExpectedPeerSubject(
                   "CN=Test Server,OU=Lyft Engineering,O=Lyft,L=San Francisco,ST=California,C=US")
               .setExpectedLocalSubject(
                   "CN=Test Server,OU=Lyft Engineering,O=Lyft,L=San Francisco,ST=California,C=US")
               .setExpectedPeerCert(expected_peer_cert));
}


TEST_P(SslSocketTest, Ipv4San) {
  const std::string client_ctx_yaml = R"EOF(
  common_tls_context:
    validation_context:
      trusted_ca:
        filename: "{{ test_rundir }}/test/config/integration/certs/upstreamcacert.pem"
      match_typed_subject_alt_names:
      - san_type: IP_ADDRESS
        matcher:
          exact: "127.0.0.1"
)EOF";

  const std::string server_ctx_yaml = R"EOF(
  common_tls_context:
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/config/integration/certs/upstreamlocalhostcert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/config/integration/certs/upstreamlocalhostkey.pem"
)EOF";

  TestUtilOptions test_options(client_ctx_yaml, server_ctx_yaml, true, GetParam());
  testUtil(test_options);
}


  TestUtilOptionsV2& setExpectedServerCertDigest(const std::string& expected_server_cert_digest) {
    expected_server_cert_digest_ = expected_server_cert_digest;
    return *this;
  }


TEST_P(SslSocketTest, GetCertDigestServerCertWithIntermediateCA) {
  const std::string client_ctx_yaml = R"EOF(
  common_tls_context:
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/no_san_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/no_san_key.pem"
)EOF";

  const std::string server_ctx_yaml = R"EOF(
  common_tls_context:
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns3_chain.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns3_key.pem"
    validation_context:
      trusted_ca:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem"
)EOF";

  TestUtilOptions test_options(client_ctx_yaml, server_ctx_yaml, true, GetParam());
  testUtil(test_options.setExpectedSha256Digest(TEST_NO_SAN_CERT_256_HASH)
               .setExpectedSha1Digest(TEST_NO_SAN_CERT_1_HASH)
               .setExpectedSerialNumber(TEST_NO_SAN_CERT_SERIAL));
}


TEST_P(SslSocketTest, EcdhCurves) {
  envoy::config::listener::v3::Listener listener;
  envoy::config::listener::v3::FilterChain* filter_chain = listener.add_filter_chains();
  envoy::extensions::transport_sockets::tls::v3::DownstreamTlsContext tls_context;
  envoy::extensions::transport_sockets::tls::v3::TlsCertificate* server_cert =
      tls_context.mutable_common_tls_context()->add_tls_certificates();
  server_cert->mutable_certificate_chain()->set_filename(TestEnvironment::substitute(
      "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem"));
  server_cert->mutable_private_key()->set_filename(TestEnvironment::substitute(
      "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_key.pem"));
  envoy::extensions::transport_sockets::tls::v3::TlsParameters* server_params =
      tls_context.mutable_common_tls_context()->mutable_tls_params();
  updateFilterChain(tls_context, *filter_chain);

  envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext client;
  envoy::extensions::transport_sockets::tls::v3::TlsParameters* client_params =
      client.mutable_common_tls_context()->mutable_tls_params();

  // Connection using defaults (client & server) succeeds.
  TestUtilOptionsV2 test_options(listener, client, true, GetParam());
  testUtilV2(test_options);

  // Connection using defaults (client & server) succeeds, even with client renegotiation.
  client.set_allow_renegotiation(true);
  testUtilV2(test_options);
  client.set_allow_renegotiation(false);

  // Client connects with one of the supported ECDH curves, connection succeeds.
  client_params->add_ecdh_curves("X25519");
  server_params->add_ecdh_curves("X25519");
  server_params->add_ecdh_curves("P-256");
  server_params->add_cipher_suites("ECDHE-RSA-AES128-GCM-SHA256");
  updateFilterChain(tls_context, *filter_chain);
  TestUtilOptionsV2 ecdh_curves_test_options(listener, client, true, GetParam());
  std::string stats = "ssl.curves.X25519";
  ecdh_curves_test_options.setExpectedServerStats(stats).setExpectedClientStats(stats);
  testUtilV2(ecdh_curves_test_options);
  client_params->clear_ecdh_curves();
  server_params->clear_ecdh_curves();
  server_params->clear_cipher_suites();

  // Client connects with unsupported ECDH curve, connection fails.
  client_params->add_ecdh_curves("X25519");
  server_params->add_ecdh_curves("P-256");
  server_params->add_cipher_suites("ECDHE-RSA-AES128-GCM-SHA256");
  updateFilterChain(tls_context, *filter_chain);
  TestUtilOptionsV2 error_test_options(listener, client, false, GetParam());
  error_test_options.setExpectedServerStats("ssl.connection_error");
  testUtilV2(error_test_options);

  client_params->clear_ecdh_curves();
  server_params->clear_ecdh_curves();
  server_params->clear_cipher_suites();

  // Verify that X25519 is not offered by default in FIPS builds.
  client_params->add_ecdh_curves("X25519");
  server_params->add_cipher_suites("ECDHE-RSA-AES128-GCM-SHA256");
  updateFilterChain(tls_context, *filter_chain);
#ifdef BORINGSSL_FIPS
  testUtilV2(error_test_options);
#else
  testUtilV2(ecdh_curves_test_options);
#endif
  client_params->clear_ecdh_curves();
  server_params->clear_cipher_suites();
}


const std::string& expectedCiphersuite() const { return expected_cipher_suite_; }


TEST_P(SslSocketTest, TestOcspStapleOmittedOnSkipStaplingAndResponseExpired) {
  const std::string server_ctx_yaml = R"EOF(
  common_tls_context:
    tls_certificates:
    - certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/ocsp/test_data/good_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/ocsp/test_data/good_key.pem"
      ocsp_staple:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/ocsp/test_data/unknown_ocsp_resp.der"
  ocsp_staple_policy: lenient_stapling
  )EOF";

  const std::string client_ctx_yaml = R"EOF(
  common_tls_context:
    tls_params:
      cipher_suites:
      - TLS_RSA_WITH_AES_128_GCM_SHA256
)EOF";
  TestUtilOptions test_options(client_ctx_yaml, server_ctx_yaml, true, GetParam());
  testUtil(test_options.setExpectedServerStats("ssl.ocsp_staple_omitted").enableOcspStapling());
}


TEST_P(SslSocketTest, ClientSessionResumptionEnabledTls12) {
  const std::string server_ctx_yaml = R"EOF(
  common_tls_context:
    tls_params:
      tls_minimum_protocol_version: TLSv1_0
      tls_maximum_protocol_version: TLSv1_2
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_key.pem"
)EOF";

  const std::string client_ctx_yaml = R"EOF(
  common_tls_context:
    tls_params:
      tls_minimum_protocol_version: TLSv1_0
      tls_maximum_protocol_version: TLSv1_2
  max_session_keys: 2
)EOF";

  testClientSessionResumption(server_ctx_yaml, client_ctx_yaml, true, GetParam());
}


bool ocspStaplingEnabled() const { return ocsp_stapling_enabled_; }


TEST_P(SslSocketTest, OverrideApplicationProtocols) {
  envoy::config::listener::v3::Listener listener;
  envoy::config::listener::v3::FilterChain* filter_chain = listener.add_filter_chains();
  envoy::extensions::transport_sockets::tls::v3::DownstreamTlsContext tls_context;
  envoy::extensions::transport_sockets::tls::v3::TlsCertificate* server_cert =
      tls_context.mutable_common_tls_context()->add_tls_certificates();

  server_cert->mutable_certificate_chain()->set_filename(TestEnvironment::substitute(
      "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem"));
  server_cert->mutable_private_key()->set_filename(TestEnvironment::substitute(
      "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_key.pem"));

  envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext client;
  TestUtilOptionsV2 test_options(listener, client, true, GetParam());

  // Client connects without ALPN to a server with "test" ALPN, no ALPN is negotiated.
  envoy::extensions::transport_sockets::tls::v3::CommonTlsContext* server_ctx =
      tls_context.mutable_common_tls_context();
  server_ctx->add_alpn_protocols("test");
  updateFilterChain(tls_context, *filter_chain);
  testUtilV2(test_options);
  server_ctx->clear_alpn_protocols();
  // Override client side ALPN, "test" ALPN is used.
  server_ctx->add_alpn_protocols("test");
  updateFilterChain(tls_context, *filter_chain);
  auto transport_socket_options = std::make_shared<Network::TransportSocketOptionsImpl>(
      "", std::vector<std::string>{}, std::vector<std::string>{"foo", "test", "bar"});

  testUtilV2(test_options.setExpectedALPNProtocol("test").setTransportSocketOptions(
      transport_socket_options));

  // Set fallback ALPN on the client side ALPN, "test" ALPN is used since no ALPN is specified
  // in the config.
  server_ctx->add_alpn_protocols("test");
  transport_socket_options = std::make_shared<Network::TransportSocketOptionsImpl>(
      "", std::vector<std::string>{}, std::vector<std::string>{}, std::vector<std::string>{"test"});
  testUtilV2(test_options.setExpectedALPNProtocol("test").setTransportSocketOptions(
      transport_socket_options));

  // With multiple fallbacks specified, a single match will match.
  transport_socket_options = std::make_shared<Network::TransportSocketOptionsImpl>(
      "", std::vector<std::string>{}, std::vector<std::string>{},
      std::vector<std::string>{"foo", "test"});
  testUtilV2(test_options.setExpectedALPNProtocol("test").setTransportSocketOptions(
      transport_socket_options));

  // With multiple matching fallbacks specified, a single match will match.
  server_ctx->add_alpn_protocols("foo");
  transport_socket_options = std::make_shared<Network::TransportSocketOptionsImpl>(
      "", std::vector<std::string>{}, std::vector<std::string>{},
      std::vector<std::string>{"foo", "test"});
  testUtilV2(test_options.setExpectedALPNProtocol("test").setTransportSocketOptions(
      transport_socket_options));

  // Update the client TLS config to specify ALPN. The fallback value should no longer be used.
  // Note that the server prefers "test" over "bar", but since the client only configures "bar",
  // the resulting ALPN will be "bar" even though "test" is included in the fallback.
  server_ctx->add_alpn_protocols("bar");
  updateFilterChain(tls_context, *filter_chain);
  client.mutable_common_tls_context()->add_alpn_protocols("bar");
  testUtilV2(test_options.setExpectedALPNProtocol("bar").setTransportSocketOptions(
      transport_socket_options));
}


const std::string& expectedPeerCertChain() const { return expected_peer_cert_chain_; }


  TestUtilOptionsV2& setExpectedServerStats(const std::string& expected_server_stats) {
    TestUtilOptionsBase::setExpectedServerStats(expected_server_stats);
    return *this;
  }


TEST_P(SslSocketTest, TestTransportSocketCallback) {
  // Make MockTransportSocketCallbacks.
  Network::MockIoHandle io_handle;
  NiceMock<Network::MockTransportSocketCallbacks> callbacks;
  ON_CALL(callbacks, ioHandle()).WillByDefault(ReturnRef(io_handle));

  // Make SslSocket.
  testing::NiceMock<Server::Configuration::MockTransportSocketFactoryContext> factory_context;
  Stats::TestUtil::TestStore stats_store;
  ON_CALL(factory_context, stats()).WillByDefault(ReturnRef(stats_store));
  NiceMock<LocalInfo::MockLocalInfo> local_info;
  ON_CALL(factory_context, localInfo()).WillByDefault(ReturnRef(local_info));

  envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext tls_context;
  auto client_cfg = std::make_unique<ClientContextConfigImpl>(tls_context, factory_context);

  ContextManagerImpl manager(time_system_);
  ClientSslSocketFactory client_ssl_socket_factory(std::move(client_cfg), manager, stats_store);

  Network::TransportSocketPtr transport_socket =
      client_ssl_socket_factory.createTransportSocket(nullptr);

  SslSocket* ssl_socket = dynamic_cast<SslSocket*>(transport_socket.get());

  // If no transport socket callbacks have been set, this method should return nullptr.
  EXPECT_EQ(ssl_socket->transportSocketCallbacks(), nullptr);

  // Otherwise, it should return a pointer to the set callbacks object.
  ssl_socket->setTransportSocketCallbacks(callbacks);
  EXPECT_EQ(ssl_socket->transportSocketCallbacks(), &callbacks);
}


  const std::string& expectedExpirationTimePeerCert() const {
    return expected_expiration_peer_cert_;
  }


TEST_P(SslSocketTest, ShutdownWithoutCloseNotify) {
  const std::string server_ctx_yaml = R"EOF(
  common_tls_context:
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_key.pem"
    validation_context:
      trusted_ca:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_certificates.pem"
)EOF";

  envoy::extensions::transport_sockets::tls::v3::DownstreamTlsContext server_tls_context;
  TestUtility::loadFromYaml(TestEnvironment::substitute(server_ctx_yaml), server_tls_context);
  auto server_cfg = std::make_unique<ServerContextConfigImpl>(server_tls_context, factory_context_);
  ContextManagerImpl manager(time_system_);
  Stats::TestUtil::TestStore server_stats_store;
  ServerSslSocketFactory server_ssl_socket_factory(std::move(server_cfg), manager,
                                                   server_stats_store, std::vector<std::string>{});

  auto socket = std::make_shared<Network::Test::TcpListenSocketImmediateListen>(
      Network::Test::getCanonicalLoopbackAddress(GetParam()));
  Network::MockTcpListenerCallbacks listener_callbacks;
  Network::ListenerPtr listener =
      dispatcher_->createListener(socket, listener_callbacks, runtime_, true, false);
  std::shared_ptr<Network::MockReadFilter> server_read_filter(new Network::MockReadFilter());
  std::shared_ptr<Network::MockReadFilter> client_read_filter(new Network::MockReadFilter());

  const std::string client_ctx_yaml = R"EOF(
    common_tls_context:
  )EOF";

  envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext tls_context;
  TestUtility::loadFromYaml(TestEnvironment::substitute(client_ctx_yaml), tls_context);
  auto client_cfg = std::make_unique<ClientContextConfigImpl>(tls_context, factory_context_);
  Stats::TestUtil::TestStore client_stats_store;
  ClientSslSocketFactory client_ssl_socket_factory(std::move(client_cfg), manager,
                                                   client_stats_store);
  Network::ClientConnectionPtr client_connection = dispatcher_->createClientConnection(
      socket->connectionInfoProvider().localAddress(), Network::Address::InstanceConstSharedPtr(),
      client_ssl_socket_factory.createTransportSocket(nullptr), nullptr);
  Network::MockConnectionCallbacks client_connection_callbacks;
  client_connection->enableHalfClose(true);
  client_connection->addReadFilter(client_read_filter);
  client_connection->addConnectionCallbacks(client_connection_callbacks);
  client_connection->connect();

  Network::ConnectionPtr server_connection;
  Network::MockConnectionCallbacks server_connection_callbacks;
  EXPECT_CALL(listener_callbacks, onAccept_(_))
      .WillOnce(Invoke([&](Network::ConnectionSocketPtr& socket) -> void {
        server_connection = dispatcher_->createServerConnection(
            std::move(socket), server_ssl_socket_factory.createTransportSocket(nullptr),
            stream_info_);
        server_connection->enableHalfClose(true);
        server_connection->addReadFilter(server_read_filter);
        server_connection->addConnectionCallbacks(server_connection_callbacks);
      }));
  EXPECT_CALL(server_connection_callbacks, onEvent(Network::ConnectionEvent::Connected))
      .WillOnce(Invoke([&](Network::ConnectionEvent) -> void {
        Buffer::OwnedImpl data("hello");
        server_connection->write(data, false);
        EXPECT_EQ(data.length(), 0);
        // Calling close(FlushWrite) in onEvent() callback results in PostIoAction::Close,
        // after which the connection is closed without write ready event being delivered,
        // and with all outstanding data (here, "hello") being lost.
      }));

  EXPECT_CALL(*client_read_filter, onNewConnection())
      .WillOnce(Return(Network::FilterStatus::Continue));
  EXPECT_CALL(client_connection_callbacks, onEvent(Network::ConnectionEvent::Connected));
  EXPECT_CALL(*client_read_filter, onData(BufferStringEqual("hello"), false))
      .WillOnce(Invoke([&](Buffer::Instance& read_buffer, bool) -> Network::FilterStatus {
        read_buffer.drain(read_buffer.length());
        // Close without sending close_notify alert.
        const SslHandshakerImpl* ssl_socket =
            dynamic_cast<const SslHandshakerImpl*>(client_connection->ssl().get());
        EXPECT_EQ(ssl_socket->state(), Ssl::SocketState::HandshakeComplete);
        SSL_set_quiet_shutdown(ssl_socket->ssl(), 1);
        client_connection->close(Network::ConnectionCloseType::FlushWrite);
        return Network::FilterStatus::StopIteration;
      }));

  EXPECT_CALL(*server_read_filter, onNewConnection())
      .WillOnce(Return(Network::FilterStatus::Continue));
  EXPECT_CALL(*server_read_filter, onData(BufferStringEqual(""), true))
      .WillOnce(Invoke([&](Buffer::Instance&, bool) -> Network::FilterStatus {
        // Close without sending close_notify alert.
        const SslHandshakerImpl* ssl_socket =
            dynamic_cast<const SslHandshakerImpl*>(server_connection->ssl().get());
        EXPECT_EQ(ssl_socket->state(), Ssl::SocketState::HandshakeComplete);
        SSL_set_quiet_shutdown(ssl_socket->ssl(), 1);
        server_connection->close(Network::ConnectionCloseType::NoFlush);
        return Network::FilterStatus::StopIteration;
      }));

  EXPECT_CALL(client_connection_callbacks, onEvent(Network::ConnectionEvent::LocalClose));
  EXPECT_CALL(server_connection_callbacks, onEvent(Network::ConnectionEvent::LocalClose))
      .WillOnce(Invoke([&](Network::ConnectionEvent) -> void { dispatcher_->exit(); }));

  dispatcher_->run(Event::Dispatcher::RunType::Block);
}


TEST_P(SslReadBufferLimitTest, SomeLimit) {
  readBufferLimitTest(32 * 1024, 32 * 1024, 256 * 1024, 1, false);
}


bool expectSuccess() const { return expect_success_; }


  setExpectedRequestedServerName(const std::string& expected_requested_server_name) {
    expected_requested_server_name_ = expected_requested_server_name;
    return *this;
  }


TEST_P(SslSocketTest, RsaPrivateKeyProviderMultiCertSuccess) {
  const std::string client_ctx_yaml = absl::StrCat(R"EOF(
    common_tls_context:
      tls_params:
        tls_minimum_protocol_version: TLSv1_2
        tls_maximum_protocol_version: TLSv1_2
        cipher_suites:
        - ECDHE-ECDSA-AES128-GCM-SHA256
        - ECDHE-RSA-AES128-GCM-SHA256
      validation_context:
        verify_certificate_hash: )EOF",
                                                   TEST_SELFSIGNED_ECDSA_P256_CERT_256_HASH);

  const std::string server_ctx_yaml = R"EOF(
  common_tls_context:
    tls_certificates:
    - certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_cert.pem"
      private_key_provider:
        provider_name: test
        typed_config:
          "@type": type.googleapis.com/google.protobuf.Struct
          value:
            private_key_file: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_key.pem"
            expected_operation: sign
            sync_mode: false
            mode: rsa
    - certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_ecdsa_p256_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_ecdsa_p256_key.pem"
)EOF";

  TestUtilOptions test_options(client_ctx_yaml, server_ctx_yaml, true, GetParam());
  testUtil(test_options.setPrivateKeyMethodExpected(true));
}


TEST_P(SslSocketTest, FailedClientCertAllowExpiredBadHashVerification) {
  envoy::config::listener::v3::Listener listener;
  envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext client;

  OptionalServerConfig server_config;
  server_config.allow_expired_cert = true;
  server_config.cert_hash = "0000000000000000000000000000000000000000000000000000000000000000";
  configureServerAndExpiredClientCertificate(listener, client, server_config);

  TestUtilOptionsV2 test_options(listener, client, false, GetParam());
  testUtilV2(test_options.setExpectedServerStats("ssl.fail_verify_cert_hash")
                 .setExpectedClientCertUri("spiffe://lyft.com/test-team")
                 .setExpectedTransportFailureReasonContains("SSLV3_ALERT_CERTIFICATE_EXPIRED"));
}


TEST_P(SslSocketTest, FailedClientAuthSanVerificationNoClientCert) {
  const std::string client_ctx_yaml = R"EOF(
    common_tls_context:
  )EOF";

  const std::string server_ctx_yaml = R"EOF(
  common_tls_context:
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_key.pem"
    validation_context:
      trusted_ca:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem"
      match_typed_subject_alt_names:
      - san_type: DNS
        matcher:
          exact: "example.com"
)EOF";

  TestUtilOptions test_options(client_ctx_yaml, server_ctx_yaml, false, GetParam());
  testUtil(test_options.setExpectedServerStats("ssl.fail_verify_no_cert"));
}


TEST_P(SslSocketTest, RevokedIntermediateCertificateCRLInTrustedCA) {

  // This should succeed, since the crl chain is complete.
  //
  // Trust chain contains:
  //  - Root authority certificate (i.e., ca_cert.pem)
  //  - Root authority certificate revocation list (i.e., ca_cert.crl)
  //  - Intermediate authority certificate (i.e., intermediate_ca_cert.pem)
  //  - Intermediate authority certificate revocation list (i.e., intermediate_ca_cert.crl)
  const std::string complete_server_ctx_yaml = R"EOF(
  common_tls_context:
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_key.pem"
    validation_context:
      trusted_ca:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/intermediate_ca_cert_chain_with_crl_chain.pem"
)EOF";

  // This should fail, since the crl chain is incomplete.
  //
  // Trust chain contains:
  //  - Root authority certificate (i.e., ca_cert.pem)
  //  - Intermediate authority certificate (i.e., intermediate_ca_cert.pem)
  //  - Intermediate authority certificate revocation list (i.e., intermediate_ca_cert.crl)
  //
  // Trust chain omits:
  //  - Root authority certificate revocation list (i.e., ca_cert.crl)
  const std::string incomplete_server_ctx_yaml = R"EOF(
  common_tls_context:
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_key.pem"
    validation_context:
      trusted_ca:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/intermediate_ca_cert_chain_with_crl.pem"
)EOF";

  // This should fail, since the certificate has been revoked.
  const std::string revoked_client_ctx_yaml = R"EOF(
  common_tls_context:
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns3_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns3_key.pem"
)EOF";

  // This should succeed, since the certificate has not been revoked.
  const std::string unrevoked_client_ctx_yaml = R"EOF(
  common_tls_context:
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns4_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns4_key.pem"
)EOF";

  // Ensure that incomplete crl chains fail with revoked certificates.
  TestUtilOptions incomplete_revoked_test_options(revoked_client_ctx_yaml,
                                                  incomplete_server_ctx_yaml, false, GetParam());
  testUtil(incomplete_revoked_test_options.setExpectedServerStats("ssl.fail_verify_error")
               .setExpectedVerifyErrorCode(X509_V_ERR_CERT_REVOKED));

  // Ensure that incomplete crl chains fail with unrevoked certificates.
  TestUtilOptions incomplete_unrevoked_test_options(unrevoked_client_ctx_yaml,
                                                    incomplete_server_ctx_yaml, false, GetParam());
  testUtil(incomplete_unrevoked_test_options.setExpectedServerStats("ssl.fail_verify_error")
               .setExpectedVerifyErrorCode(X509_V_ERR_UNABLE_TO_GET_CRL));

  // Ensure that complete crl chains fail with revoked certificates.
  TestUtilOptions complete_revoked_test_options(revoked_client_ctx_yaml, complete_server_ctx_yaml,
                                                false, GetParam());
  testUtil(complete_revoked_test_options.setExpectedServerStats("ssl.fail_verify_error")
               .setExpectedVerifyErrorCode(X509_V_ERR_CERT_REVOKED));

  // Ensure that complete crl chains succeed with unrevoked certificates.
  TestUtilOptions complete_unrevoked_test_options(unrevoked_client_ctx_yaml,
                                                  complete_server_ctx_yaml, true, GetParam());
  testUtil(complete_unrevoked_test_options.setExpectedSerialNumber(TEST_SAN_DNS4_CERT_SERIAL));
}


  TestUtilOptions& setExpectedSha1Digest(const std::string& expected_sha1_digest) {
    expected_sha1_digest_ = expected_sha1_digest;
    return *this;
  }


  const std::string& expectedValidFromTimePeerCert() const {
    return expected_valid_from_peer_cert_;
  }


TEST_P(SslSocketTest, TicketSessionResumptionDifferentVerifyCertSpki) {
  const std::string server_ctx_yaml1 = absl::StrCat(R"EOF(
  session_ticket_keys:
    keys:
      filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ticket_key_a"
  common_tls_context:
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_key.pem"
    validation_context:
      trusted_ca:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem"
      verify_certificate_spki:
        - ")EOF",
                                                    TEST_SAN_URI_CERT_SPKI, "\"");

  const std::string server_ctx_yaml2 = absl::StrCat(R"EOF(
  session_ticket_keys:
    keys:
      filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ticket_key_a"
  common_tls_context:
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_key.pem"
    validation_context:
      trusted_ca:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem"
      verify_certificate_spki:
        - "NvqYIYSbgK2vCJpQhObf77vv+bQWtc5ek5RIOwPiC9A="
        - ")EOF",
                                                    TEST_SAN_URI_CERT_SPKI, "\"");

  const std::string client_ctx_yaml = R"EOF(
  common_tls_context:
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_key.pem"
)EOF";

  testTicketSessionResumption(server_ctx_yaml1, {}, server_ctx_yaml1, {}, client_ctx_yaml, true,
                              GetParam());
  testTicketSessionResumption(server_ctx_yaml1, {}, server_ctx_yaml2, {}, client_ctx_yaml, false,
                              GetParam());
}


TEST_P(SslSocketTest, RevokedCertificateCRLInTrustedCA) {

  const std::string server_ctx_yaml = R"EOF(
  common_tls_context:
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_key.pem"
    validation_context:
      trusted_ca:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert_with_crl.pem"
)EOF";

  // This should fail, since the certificate has been revoked.
  const std::string revoked_client_ctx_yaml = R"EOF(
  common_tls_context:
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_key.pem"
)EOF";

  TestUtilOptions revoked_test_options(revoked_client_ctx_yaml, server_ctx_yaml, false, GetParam());
  testUtil(revoked_test_options.setExpectedServerStats("ssl.fail_verify_error")
               .setExpectedVerifyErrorCode(X509_V_ERR_CERT_REVOKED));

  // This should succeed, since the cert isn't revoked.
  const std::string successful_client_ctx_yaml = R"EOF(
  common_tls_context:
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns2_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns2_key.pem"
)EOF";
  TestUtilOptions successful_test_options(successful_client_ctx_yaml, server_ctx_yaml, true,
                                          GetParam());
  testUtil(successful_test_options.setExpectedSerialNumber(TEST_SAN_DNS2_CERT_SERIAL));
}


TEST_P(SslSocketTest, TicketSessionResumptionDifferentServerNames) {
  const std::string server_ctx_yaml1 = R"EOF(
  common_tls_context:
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_key.pem"
  session_ticket_keys:
    keys:
      filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ticket_key_a"
)EOF";

  const std::string server_ctx_yaml2 = R"EOF(
  common_tls_context:
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_key.pem"
  session_ticket_keys:
    keys:
      filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ticket_key_a"
)EOF";

  std::vector<std::string> server_names1 = {"server1.example.com"};

  const std::string client_ctx_yaml = R"EOF(
    common_tls_context:
  )EOF";

  testTicketSessionResumption(server_ctx_yaml1, server_names1, server_ctx_yaml2, {},
                              client_ctx_yaml, false, GetParam());
}


TEST_P(SslSocketTest, ClientSessionResumptionDisabledTls13) {
  const std::string server_ctx_yaml = R"EOF(
  common_tls_context:
    tls_params:
      tls_minimum_protocol_version: TLSv1_3
      tls_maximum_protocol_version: TLSv1_3
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_key.pem"
)EOF";

  const std::string client_ctx_yaml = R"EOF(
  common_tls_context:
    tls_params:
      tls_minimum_protocol_version: TLSv1_3
      tls_maximum_protocol_version: TLSv1_3
  max_session_keys: 0
)EOF";

  testClientSessionResumption(server_ctx_yaml, client_ctx_yaml, false, GetParam());
}


absl::string_view name() override { return "envoy.tls.cert_validator.default"; }


TEST_P(SslSocketTest, TicketSessionResumptionWithClientCA) {
  const std::string server_ctx_yaml = R"EOF(
  common_tls_context:
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_key.pem"
    validation_context:
      trusted_ca:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem"
  session_ticket_keys:
    keys:
      filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ticket_key_a"
)EOF";

  const std::string client_ctx_yaml = R"EOF(
  common_tls_context:
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/no_san_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/no_san_key.pem"
)EOF";

  testTicketSessionResumption(server_ctx_yaml, {}, server_ctx_yaml, {}, client_ctx_yaml, true,
                              GetParam());
}


  TestUtilOptionsV2(
      const envoy::config::listener::v3::Listener& listener,
      const envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext& client_ctx_proto,
      bool expect_success, Network::Address::IpVersion version)
      : TestUtilOptionsBase(expect_success, version), listener_(listener),
        client_ctx_proto_(client_ctx_proto), transport_socket_options_(nullptr) {
    if (expect_success) {
      setExpectedServerStats("ssl.handshake").setExpectedClientStats("ssl.handshake");
    } else {
      setExpectedServerStats("ssl.fail_verify_error")
          .setExpectedClientStats("ssl.connection_error");
    }
  }


void updateFilterChain(
    const envoy::extensions::transport_sockets::tls::v3::DownstreamTlsContext& tls_context,
    envoy::config::listener::v3::FilterChain& filter_chain) {
  filter_chain.mutable_transport_socket()->mutable_typed_config()->PackFrom(tls_context);
}


TEST_P(SslSocketTest, NoCertUntrustedPermitted) {
  const std::string client_ctx_yaml = R"EOF(
    common_tls_context:
  )EOF";

  const std::string server_ctx_yaml = R"EOF(
  common_tls_context:
    tls_certificates:
      certificate_chain:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_cert.pem"
      private_key:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_key.pem"
    validation_context:
      trusted_ca:
        filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/fake_ca_cert.pem"
      trust_chain_verification: ACCEPT_UNTRUSTED
      verify_certificate_hash: "0000000000000000000000000000000000000000000000000000000000000000"
)EOF";

  TestUtilOptions test_options(client_ctx_yaml, server_ctx_yaml, true, GetParam());
  testUtil(test_options.setExpectedServerStats("ssl.no_certificate")
               .setExpectNoCert()
               .setExpectNoCertChain());
}


  void disconnect() {
    EXPECT_CALL(client_callbacks_, onEvent(Network::ConnectionEvent::LocalClose));
    EXPECT_CALL(server_callbacks_, onEvent(Network::ConnectionEvent::RemoteClose))
        .WillOnce(Invoke([&](Network::ConnectionEvent) -> void { dispatcher_->exit(); }));

    client_connection_->close(Network::ConnectionCloseType::NoFlush);
    dispatcher_->run(Event::Dispatcher::RunType::Block);
  }


void InvertibleRWFunction::AssignFrom(const NameValuePairs &source)
{
	AssignFromHelper<RWFunction>(this, source)
		CRYPTOPP_SET_FUNCTION_ENTRY(Prime1)
		CRYPTOPP_SET_FUNCTION_ENTRY(Prime2)
		CRYPTOPP_SET_FUNCTION_ENTRY(MultiplicativeInverseOfPrime2ModPrime1)
		;
}

Fix for CVE-2015-2141. Thanks to Evgeny Sidorov for reporting. Squaring to satisfy Jacobi requirements suggested by JPM.


bool RWFunction::Validate(RandomNumberGenerator &rng, unsigned int level) const
{
	bool pass = true;
	pass = pass && m_n > Integer::One() && m_n%8 == 5;
	return pass;
}


void RWFunction::DEREncode(BufferedTransformation &bt) const
{
	DERSequenceEncoder seq(bt);
	m_n.DEREncode(seq);
	seq.MessageEnd();
}


bool RWFunction::GetVoidValue(const char *name, const std::type_info &valueType, void *pValue) const
{
	return GetValueHelper(this, name, valueType, pValue).Assignable()
		CRYPTOPP_GET_FUNCTION_ENTRY(Modulus)
		;
}


Integer InvertibleRWFunction::CalculateInverse(RandomNumberGenerator &rng, const Integer &x) const
{
	DoQuickSanityCheck();
	ModularArithmetic modn(m_n);
	Integer r, rInv;

	// do this in a loop for people using small numbers for testing
	do {
		r.Randomize(rng, Integer::One(), m_n - Integer::One());
		// Fix for CVE-2015-2141. Thanks to Evgeny Sidorov for reporting.
		// Squaring to satisfy Jacobi requirements suggested by JPM.
		r = modn.Square(r);
		rInv = modn.MultiplicativeInverse(r);
	} while (rInv.IsZero());

	Integer re = modn.Square(r);
	re = modn.Multiply(re, x);			// blind

	Integer cp=re%m_p, cq=re%m_q;
	if (Jacobi(cp, m_p) * Jacobi(cq, m_q) != 1)
	{
		cp = cp.IsOdd() ? (cp+m_p) >> 1 : cp >> 1;
		cq = cq.IsOdd() ? (cq+m_q) >> 1 : cq >> 1;
	}

	#pragma omp parallel
		#pragma omp sections
		{
			#pragma omp section
				cp = ModularSquareRoot(cp, m_p);
			#pragma omp section
				cq = ModularSquareRoot(cq, m_q);
		}

	Integer y = CRT(cq, m_q, cp, m_p, m_u);
	y = modn.Multiply(y, rInv);				// unblind
	y = STDMIN(y, m_n-y);
	if (ApplyFunction(y) != x)				// check
		throw Exception(Exception::OTHER_ERROR, "InvertibleRWFunction: computational error during private key operation");
	return y;
}


Integer RWFunction::ApplyFunction(const Integer &in) const
{
	DoQuickSanityCheck();

	Integer out = in.Squared()%m_n;
	const word r = 12;
	// this code was written to handle both r = 6 and r = 12,
	// but now only r = 12 is used in P1363
	const word r2 = r/2;
	const word r3a = (16 + 5 - r) % 16;	// n%16 could be 5 or 13
	const word r3b = (16 + 13 - r) % 16;
	const word r4 = (8 + 5 - r/2) % 8;	// n%8 == 5
	switch (out % 16)
	{
	case r:
		break;
	case r2:
	case r2+8:
		out <<= 1;
		break;
	case r3a:
	case r3b:
		out.Negate();
		out += m_n;
		break;
	case r4:
	case r4+8:
		out.Negate();
		out += m_n;
		out <<= 1;
		break;
	default:
		out = Integer::Zero();
	}
	return out;
}


bool InvertibleRWFunction::GetVoidValue(const char *name, const std::type_info &valueType, void *pValue) const
{
	return GetValueHelper<RWFunction>(this, name, valueType, pValue).Assignable()
		CRYPTOPP_GET_FUNCTION_ENTRY(Prime1)
		CRYPTOPP_GET_FUNCTION_ENTRY(Prime2)
		CRYPTOPP_GET_FUNCTION_ENTRY(MultiplicativeInverseOfPrime2ModPrime1)
		;
}


void InvertibleRWFunction::GenerateRandom(RandomNumberGenerator &rng, const NameValuePairs &alg)
{
	int modulusSize = 2048;
	alg.GetIntValue("ModulusSize", modulusSize) || alg.GetIntValue("KeySize", modulusSize);

	if (modulusSize < 16)
		throw InvalidArgument("InvertibleRWFunction: specified modulus length is too small");

	AlgorithmParameters primeParam = MakeParametersForTwoPrimesOfEqualSize(modulusSize);
	m_p.GenerateRandom(rng, CombinedNameValuePairs(primeParam, MakeParameters("EquivalentTo", 3)("Mod", 8)));
	m_q.GenerateRandom(rng, CombinedNameValuePairs(primeParam, MakeParameters("EquivalentTo", 7)("Mod", 8)));

	m_n = m_p * m_q;
	m_u = m_q.InverseMod(m_p);
}


void RWFunction::BERDecode(BufferedTransformation &bt)
{
	BERSequenceDecoder seq(bt);
	m_n.BERDecode(seq);
	seq.MessageEnd();
}


bool InvertibleRWFunction::Validate(RandomNumberGenerator &rng, unsigned int level) const
{
	bool pass = RWFunction::Validate(rng, level);
	pass = pass && m_p > Integer::One() && m_p%8 == 3 && m_p < m_n;
	pass = pass && m_q > Integer::One() && m_q%8 == 7 && m_q < m_n;
	pass = pass && m_u.IsPositive() && m_u < m_p;
	if (level >= 1)
	{
		pass = pass && m_p * m_q == m_n;
		pass = pass && m_u * m_q % m_p == 1;
	}
	if (level >= 2)
		pass = pass && VerifyPrime(rng, m_p, level-2) && VerifyPrime(rng, m_q, level-2);
	return pass;
}


void InvertibleRWFunction::DEREncode(BufferedTransformation &bt) const
{
	DERSequenceEncoder seq(bt);
	m_n.DEREncode(seq);
	m_p.DEREncode(seq);
	m_q.DEREncode(seq);
	m_u.DEREncode(seq);
	seq.MessageEnd();
}


void InvertibleRWFunction::BERDecode(BufferedTransformation &bt)
{
	BERSequenceDecoder seq(bt);
	m_n.BERDecode(seq);
	m_p.BERDecode(seq);
	m_q.BERDecode(seq);
	m_u.BERDecode(seq);
	seq.MessageEnd();
}


void RWFunction::AssignFrom(const NameValuePairs &source)
{
	AssignFromHelper(this, source)
		CRYPTOPP_SET_FUNCTION_ENTRY(Modulus)
		;
}


void header_time(int stat, char *str, char *more)
{
  char buf[64];

  xprintf("HTTP/1.1 %d %s\r\nServer: toybox httpd/%s\r\nDate: %s\r\n%s"
    "Connection: close\r\n\r\n", stat, str, TOYBOX_VERSION,
    rfc1123(buf, time(0)), more ? : "");
}

Don't segfault if xabspath() returns null (spotted by Taolaw).


void httpd_main(void)
{
  if (toys.optc && chdir(*toys.optargs))
    return error_time(500, "Internal Error");
  // inetd only at the moment
  handle(0, 1);
}


void error_time(int stat, char *str)
{
  header_time(stat, str, 0);
  xprintf("<title>%d %s</title>"
    "
%d %s
", stat, str, stat, str);
}


static int isunder(char *file, char *dir)
{
  char *s1 = xabspath(dir, ABS_FILE), *s2 = xabspath(file, 0), *ss = s2;
  int rc = s1 && s2 && strstart(&ss, s1) && (!*ss || *ss=='/' || ss[-1]=='/');

  free(s2);
  free(s1);

  return rc;
}


void handle(int infd, int outfd)
{
  FILE *fp = fdopen(infd, "r");
  char *s = xgetline(fp), *ss, *esc, *path, *word[3];
  int i, fd;

  // Split line into method/path/protocol
  for (i = 0, ss = s;;) {
    word[i++] = ss;
    while (*ss && !strchr(" \r\n", *ss)) ss++;
    while (*ss && strchr(" \r\n", *ss)) *(ss++) = 0;
    if (i==3) break;
    if (!*ss) return header_time(400, "Bad Request", 0);
  }

  // Process additional http/1.1 lines
  while ((ss = xgetline(fp))) {
    i = *chomp(ss);
// TODO: any of
//User-Agent: Wget/1.20.1 (linux-gnu) - do we want to log anything?
//Accept: */* - 406 Too Snobbish
//Accept-Encoding: identity - we can gzip?
//Host: landley.net  - we could handle multiple domains?
//Connection: Keep-Alive - probably don't care

    free(ss);
    if (!i) break;
  }

  if (!strcasecmp(word[0], "get")) {
    struct stat st;
    if (*(ss = word[1])!='/') error_time(400, "Bad Request");
    while (*ss=='/') ss++;
    if (!*ss) ss = ".";
    else unescape_url(ss);

    // TODO domain.com:/path/to/blah domain2.com:/path/to/that
    if (!isunder(ss, ".") || stat(ss, &st)) error_time(404, "Not Found");
    else if (-1 == (fd = open(ss, O_RDONLY))) error_time(403, "Forbidden");
    else if (!S_ISDIR(st.st_mode)) {
      char buf[64];
file:
      header_time(200, "Ok", ss = xmprintf("Content-Type: %s\r\n"
        "Content-Length: %lld\r\nLast-Modified: %s\r\n",
        mime(ss), (long long)st.st_size, rfc1123(buf, st.st_mtime)));
      free(ss);
      xsendfile(fd, outfd);
    } else if (ss[strlen(ss)-1]!='/' && strcmp(ss, ".")) {
      header_time(302, "Found", path = xmprintf("Location: %s/\r\n", word[1]));
      free(path);
    } else {
      DIR *dd;
      struct dirent *dir;

      // Do we have an index.html?
      path = ss;
      ss = "index.html";
      path = xmprintf("%s%s", path, ss);
      if (!stat(path, &st) || !S_ISREG(st.st_mode)) i = -1;
      else if (-1 == (i = open(path, O_RDONLY))) error_time(403, "Forbidden");
      free(path);
      if (i != -1) {
        close(fd);
        fd = i;

        goto file;
      }

      // List directory contents
      header_time(200, "Ok", "Content-Type: text/html\r\n");
      dprintf(outfd, "<title>Index of %s</title>\n"
        "
Index of %s
\n", word[1], word[1]);
      for (dd = fdopendir(fd); (dir = readdir(dd));) {
        esc = escape_url(dir->d_name, "<>&\"");
        dprintf(outfd, "%s
\n", esc, esc);
        free(esc);
      }
      dprintf(outfd, "\n");
    }
  } else error_time(501, "Not Implemented");
  free(s);
}


char *mime(char *file)
{
  char *s = strrchr(file, '.'), *types[] = {
    "asc\0text/plain", "bin\0application/octet-stream", "bmp\0image/bmp",
    "cpio\0application/x-cpio", "css\0text/css", "doc\0application/msword",
    "dtd\0text/xml", "dvi\0application/x-dvi", "gif\0image/gif",
    "htm\0text/html", "html\0text/html", "jar\0applicat/x-java-archive",
    "jpeg\0image/jpeg", "jpg\0image/jpeg", "js\0application/x-javascript",
    "mp3\0audio/mpeg", "mp4\0video/mp4", "mpg\0video/mpeg",
    "ogg\0application/ogg", "pbm\0image/x-portable-bitmap",
    "pdf\0application/pdf", "png\0image/png",
    "ppt\0application/vnd.ms-powerpoint", "ps\0application/postscript",
    "rtf\0text/rtf", "sgml\0text/sgml", "svg\0image/svg+xml",
    "tar\0application/x-tar", "tex\0application/x-tex", "tiff\0image/tiff",
    "txt\0text/plain", "wav\0audio/x-wav", "xls\0application/vnd.ms-excel",
    "xml\0tet/xml", "zip\0application/zip"
  };
  int i;

  strcpy(toybuf, "text/plain");
  if (s++) for (i = 0; i<ARRAY_LEN(types); i++) {
    if (strcasecmp(s, types[i])) continue;
    strcpy(toybuf, types[i]+strlen(types[i])+1);
    break;
  }
  if (!strncmp(toybuf, "text/", 5)) strcat(toybuf, "; charset=UTF-8");

  return toybuf;
}


char *rfc1123(char *buf, time_t t)
{
  strftime(buf, 64, "%a, %d %b %Y %T GMT", gmtime(&t));

  return buf;
}


DLLIMPORT int cfg_setlist(cfg_t *cfg, const char *name, unsigned int nvalues, ...)
{
	va_list ap;
	cfg_opt_t *opt = cfg_getopt(cfg, name);

	if (!opt || !is_set(CFGF_LIST, opt->flags)) {
		errno = EINVAL;
		return CFG_FAIL;
	}

	cfg_free_value(opt);
	va_start(ap, nvalues);
	cfg_addlist_internal(opt, nvalues, ap);
	va_end(ap);

	return CFG_SUCCESS;
}

Fix #163: unterminated username used with getpwnam()

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>


DLLIMPORT int cfg_opt_print_indent(cfg_opt_t *opt, FILE *fp, int indent)
{
	return cfg_opt_print_pff_indent(opt, fp, NULL, indent);
}


static void cfg_init_defaults(cfg_t *cfg)
{
	int i;

	for (i = 0; cfg->opts && cfg->opts[i].name; i++) {
		int j;

		for (j = 0; j < i; ++j) {
			if (is_set(CFGF_NOCASE, cfg->opts[i].flags | cfg->opts[j].flags)) {
				if (strcasecmp(cfg->opts[i].name, cfg->opts[j].name))
					continue;
			} else {
				if (strcmp(cfg->opts[i].name, cfg->opts[j].name))
					continue;
			}
			/*
			 * There are two definitions of the same option name.
			 * What to do? It's a programming error and not an end
			 * user input error. Lets print a message and abort...
			 */
			cfg_error(cfg, _("duplicate option '%s' not allowed"),
				cfg->opts[i].name);
			break;
		}

		/* libConfuse doesn't handle default values for "simple" options */
		if (cfg->opts[i].simple_value.ptr || is_set(CFGF_NODEFAULT, cfg->opts[i].flags))
			continue;

		if (cfg->opts[i].type != CFGT_SEC) {
			cfg->opts[i].flags |= CFGF_DEFINIT;

			if (is_set(CFGF_LIST, cfg->opts[i].flags) || cfg->opts[i].def.parsed) {
				int xstate, ret = 0;
				char *buf;
				FILE *fp;

				/* If it's a list, but no default value was given,
				 * keep the option uninitialized.
				 */
				buf = cfg->opts[i].def.parsed;
				if (!buf || !buf[0])
					continue;

				/* setup scanning from the string specified for the
				 * "default" value, force the correct state and option
				 */

				if (is_set(CFGF_LIST, cfg->opts[i].flags))
					/* lists must be surrounded by {braces} */
					xstate = 3;
				else if (cfg->opts[i].type == CFGT_FUNC)
					xstate = 0;
				else
					xstate = 2;

				fp = fmemopen(buf, strlen(buf), "r");
				if (!fp) {
					/*
					 * fmemopen() on older GLIBC versions do not accept zero
					 * length buffers for some reason.  This is a workaround.
					 */
					if (strlen(buf) > 0)
						ret = STATE_ERROR;
				} else {
					cfg_scan_fp_begin(fp);

					do {
						ret = cfg_parse_internal(cfg, 1, xstate, &cfg->opts[i]);
						xstate = -1;
					} while (ret == STATE_CONTINUE);

					cfg_scan_fp_end();
					fclose(fp);
				}

				if (ret == STATE_ERROR) {
					/*
					 * If there was an error parsing the default string,
					 * the initialization of the default value could be
					 * inconsistent or empty. What to do? It's a
					 * programming error and not an end user input
					 * error. Lets print a message and abort...
					 */
					fprintf(stderr, "Parse error in default value '%s'"
						" for option '%s'\n", cfg->opts[i].def.parsed, cfg->opts[i].name);
					fprintf(stderr, "Check your initialization macros and the" " libConfuse documentation\n");
					abort();
				}
			} else {
				switch (cfg->opts[i].type) {
				case CFGT_INT:
					cfg_opt_setnint(&cfg->opts[i], cfg->opts[i].def.number, 0);
					break;

				case CFGT_FLOAT:
					cfg_opt_setnfloat(&cfg->opts[i], cfg->opts[i].def.fpnumber, 0);
					break;

				case CFGT_BOOL:
					cfg_opt_setnbool(&cfg->opts[i], cfg->opts[i].def.boolean, 0);
					break;

				case CFGT_STR:
					cfg_opt_setnstr(&cfg->opts[i], cfg->opts[i].def.string, 0);
					break;

				case CFGT_FUNC:
				case CFGT_PTR:
					break;

				default:
					cfg_error(cfg, "internal error in cfg_init_defaults(%s)", cfg->opts[i].name);
					break;
				}
			}

			/* The default value should only be returned if no value
			 * is given in the configuration file, so we set the RESET
			 * flag here. When/If cfg_setopt() is called, the value(s)
			 * will be freed and the flag unset.
			 */
			cfg->opts[i].flags |= CFGF_RESET;
			cfg->opts[i].flags &= ~CFGF_MODIFIED;
		} else if (!is_set(CFGF_MULTI, cfg->opts[i].flags)) {
			cfg_setopt(cfg, &cfg->opts[i], NULL);
			cfg->opts[i].flags |= CFGF_DEFINIT;
		}
	}
}


static char *strndup(const char *s, size_t n)
{
	char *r;

	r = malloc(n + 1);
	if (!r)
		return NULL;

	strncpy(r, s, n);
	r[n] = 0;

	return r;
}


DLLIMPORT char *cfg_opt_getnstr(cfg_opt_t *opt, unsigned int index)
{
	if (!opt || opt->type != CFGT_STR) {
		errno = EINVAL;
		return NULL;
	}

	if (opt->values && index < opt->nvalues)
		return opt->values[index]->string;
	if (opt->simple_value.string)
		return *opt->simple_value.string;

	return NULL;
}


DLLIMPORT int cfg_parse_fp(cfg_t *cfg, FILE *fp)
{
	int ret;

	if (!cfg || !fp) {
		errno = EINVAL;
		return CFG_PARSE_ERROR;
	}

	if (!cfg->filename)
		cfg->filename = strdup("FILE");
	if (!cfg->filename)
		return CFG_PARSE_ERROR;

	cfg->line = 1;
	cfg_scan_fp_begin(fp);
	ret = cfg_parse_internal(cfg, 0, -1, NULL);
	cfg_scan_fp_end();
	if (ret == STATE_ERROR)
		return CFG_PARSE_ERROR;

	return CFG_SUCCESS;
}


DLLIMPORT int cfg_opt_setcomment(cfg_opt_t *opt, char *comment)
{
	char *oldcomment, *newcomment;

	if (!opt || !comment) {
		errno = EINVAL;
		return CFG_FAIL;
	}

	oldcomment = opt->comment;
	newcomment = strdup(comment);
	if (!newcomment)
		return CFG_FAIL;

	if (oldcomment)
		free(oldcomment);
	opt->comment = newcomment;
	opt->flags |= CFGF_COMMENTS;
	opt->flags |= CFGF_MODIFIED;

	return CFG_SUCCESS;
}


DLLIMPORT int cfg_print(cfg_t *cfg, FILE *fp)
{
	return cfg_print_pff_indent(cfg, fp, NULL, 0);
}


DLLIMPORT cfg_t *cfg_getnsec(cfg_t *cfg, const char *name, unsigned int index)
{
	return cfg_opt_getnsec(cfg_getopt(cfg, name), index);
}


DLLIMPORT cfg_t *cfg_init(cfg_opt_t *opts, cfg_flag_t flags)
{
	cfg_t *cfg;

	cfg = calloc(1, sizeof(cfg_t));
	if (!cfg)
		return NULL;

	cfg->name = strdup("root");
	if (!cfg->name) {
		free(cfg);
		return NULL;
	}

	cfg->opts = cfg_dupopt_array(opts);
	if (!cfg->opts) {
		free(cfg->name);
		free(cfg);
		return NULL;
	}

	cfg->flags = flags;
	cfg->filename = NULL;
	cfg->line = 0;
	cfg->errfunc = NULL;

#if defined(ENABLE_NLS) && defined(HAVE_GETTEXT)
	bindtextdomain(PACKAGE, LOCALEDIR);
#endif

	cfg_init_defaults(cfg);

	return cfg;
}


DLLIMPORT int cfg_include(cfg_t *cfg, cfg_opt_t *opt, int argc, const char **argv)
{
	(void)opt;		/* Unused in this predefined include FUNC */

	if (!cfg || !argv) {
		errno = EINVAL;
		return CFG_FAIL;
	}

	if (argc != 1) {
		cfg_error(cfg, _("wrong number of arguments to cfg_include()"));
		return 1;
	}

	return cfg_lexer_include(cfg, argv[0]);
}


DLLIMPORT char *cfg_getcomment(cfg_t *cfg, const char *name)
{
	return cfg_opt_getcomment(cfg_getopt(cfg, name));
}


DLLIMPORT int cfg_setnbool(cfg_t *cfg, const char *name, cfg_bool_t value, unsigned int index)
{
	return cfg_opt_setnbool(cfg_getopt(cfg, name), value, index);
}


static char *parse_title(const char *name, size_t *len)
{
	const char *escapes = "'\\";
	char *title;
	char *end;
	char *ch;

	if (*name != '\'') {
		*len = strcspn(name, "|");
		if (!*len)
			return NULL;
		return strndup(name, *len);
	}

	title = strdup(name + 1);
	if (!title)
		return NULL;

	*len = 1;
	ch = title;
	end = title + strlen(title);
	while (ch < end) {
		size_t l = strcspn(ch, escapes);
		*len += l + 1;
		ch += l;
		switch (*ch) {
		case '\'':
			*ch = 0;
			return title;
		case '\\':
			if (!ch[1] || strcspn(ch + 1, escapes)) {
				free(title);
				return NULL;
			}
			memmove(ch, ch + 1, strlen(ch));
			ch++;
			(*len)++;
			break;
		default:
			free(title);
			return NULL;
		}
	}

	free(title);
	return NULL;
}


DLLIMPORT int cfg_opt_rmtsec(cfg_opt_t *opt, const char *title)
{
	unsigned int i, n;

	if (!opt || !title) {
		errno = EINVAL;
		return CFG_FAIL;
	}

	if (!is_set(CFGF_TITLE, opt->flags))
		return CFG_FAIL;

	n = cfg_opt_size(opt);
	for (i = 0; i < n; i++) {
		cfg_t *sec = cfg_opt_getnsec(opt, i);

		if (!sec || !sec->title)
			return CFG_FAIL;

		if (is_set(CFGF_NOCASE, opt->flags)) {
			if (strcasecmp(title, sec->title) == 0)
				break;
		} else {
			if (strcmp(title, sec->title) == 0)
				break;
		}
	}
	if (i == n)
		return CFG_FAIL;

	return cfg_opt_rmnsec(opt, i);
}


static cfg_value_t *cfg_addval(cfg_opt_t *opt)
{
	void *ptr;

	ptr = realloc(opt->values, (opt->nvalues + 1) * sizeof(cfg_value_t *));
	if (!ptr)
		return NULL;

	opt->values = ptr;
	opt->values[opt->nvalues] = calloc(1, sizeof(cfg_value_t));
	if (!opt->values[opt->nvalues])
		return NULL;

	opt->flags |= CFGF_MODIFIED;

	return opt->values[opt->nvalues++];
}


DLLIMPORT const char *cfg_opt_name(cfg_opt_t *opt)
{
	if (opt)
		return opt->name;
	return NULL;
}


static long int cfg_opt_gettsecidx(cfg_opt_t *opt, const char *title)
{
	unsigned int i, n;

	n = cfg_opt_size(opt);
	for (i = 0; i < n; i++) {
		cfg_t *sec = cfg_opt_getnsec(opt, i);

		if (!sec || !sec->title)
			return -1;

		if (is_set(CFGF_NOCASE, opt->flags)) {
			if (strcasecmp(title, sec->title) == 0)
				return i;
		} else {
			if (strcmp(title, sec->title) == 0)
				return i;
		}
	}

	return -1;
}


DLLIMPORT int cfg_opt_setmulti(cfg_t *cfg, cfg_opt_t *opt, unsigned int nvalues, char **values)
{
	cfg_opt_t old;
	unsigned int i;

	if (!opt || !nvalues) {
		errno = EINVAL;
		return CFG_FAIL;
	}

	old = *opt;
	opt->nvalues = 0;
	opt->values = NULL;

	for (i = 0; i < nvalues; i++) {
		if (cfg_setopt(cfg, opt, values[i]))
			continue;

		/* ouch, revert */
		cfg_free_value(opt);
		opt->nvalues = old.nvalues;
		opt->values = old.values;
		opt->flags &= ~(CFGF_RESET | CFGF_MODIFIED);
		opt->flags |= old.flags & (CFGF_RESET | CFGF_MODIFIED);

		return CFG_FAIL;
	}

	cfg_free_value(&old);
	opt->flags |= CFGF_MODIFIED;

	return CFG_SUCCESS;
}


DLLIMPORT void *cfg_getptr(cfg_t *cfg, const char *name)
{
	return cfg_getnptr(cfg, name, 0);
}


DLLIMPORT int cfg_free(cfg_t *cfg)
{
	int i;
	int isroot = 0;

	if (!cfg) {
		errno = EINVAL;
		return CFG_FAIL;
	}

	if (cfg->comment)
		free(cfg->comment);

	for (i = 0; cfg->opts[i].name; ++i)
		cfg_free_value(&cfg->opts[i]);

	cfg_free_opt_array(cfg->opts);
	cfg_free_searchpath(cfg->path);

	if (cfg->name) {
		isroot = !strcmp(cfg->name, "root");
		free(cfg->name);
	}
	if (cfg->title)
		free(cfg->title);
	if (cfg->filename)
		free(cfg->filename);

	free(cfg);
	if (isroot)
		cfg_yylex_destroy();

	return CFG_SUCCESS;
}


DLLIMPORT int cfg_opt_rmnsec(cfg_opt_t *opt, unsigned int index)
{
	unsigned int n;
	cfg_value_t *val;

	if (!opt || opt->type != CFGT_SEC) {
		errno = EINVAL;
		return CFG_FAIL;
	}

	n = cfg_opt_size(opt);
	if (index >= n)
		return CFG_FAIL;

	val = cfg_opt_getval(opt, index);
	if (!val)
		return CFG_FAIL;

	if (index + 1 != n) {
		/* not removing last, move the tail */
		memmove(&opt->values[index], &opt->values[index + 1], sizeof(opt->values[index]) * (n - index - 1));
	}
	--opt->nvalues;

	cfg_free(val->section);
	free(val);

	return CFG_SUCCESS;
}


DLLIMPORT int cfg_numopts(cfg_opt_t *opts)
{
	int n;

	for (n = 0; opts && opts[n].name; n++)
		/* do nothing */ ;
	return n;
}


DLLIMPORT unsigned int cfg_num(cfg_t *cfg)
{
	if (!cfg)
		return 0;

	return (unsigned int)cfg_numopts(cfg->opts);
}


DLLIMPORT int cfg_setint(cfg_t *cfg, const char *name, long int value)
{
	return cfg_setnint(cfg, name, value, 0);
}


static cfg_opt_t *cfg_getopt_array(cfg_opt_t *rootopts, int cfg_flags, const char *name)
{
	unsigned int i;
	cfg_opt_t *opts = rootopts;

	if (!rootopts || !name) {
		errno = EINVAL;
		return NULL;
	}

	while (name && *name) {
		cfg_t *seccfg;
		char *secname;
		size_t len = strcspn(name, "|");

		if (name[len] == 0 /*len == strlen(name) */ )
			/* no more subsections */
			break;

		if (len) {
			cfg_opt_t *secopt;

			secname = strndup(name, len);
			if (!secname)
				return NULL;

			secopt = cfg_getopt_array(opts, cfg_flags, secname);
			free(secname);
			if (!secopt) {
				/*fprintf(stderr, "section not found\n"); */
				return NULL;
			}
			if (secopt->type != CFGT_SEC) {
				/*fprintf(stderr, "not a section!\n"); */
				return NULL;
			}

			if (!is_set(CFGF_MULTI, secopt->flags) && (seccfg = cfg_opt_getnsec(secopt, 0)) != NULL)
				opts = seccfg->opts;
			else
				opts = secopt->subopts;

			if (!opts) {
				/*fprintf(stderr, "section have no subopts!?\n"); */
				return NULL;
			}
		}
		name += len;
		name += strspn(name, "|");
	}

	for (i = 0; opts[i].name; i++) {
		if (is_set(CFGF_NOCASE, cfg_flags)) {
			if (strcasecmp(opts[i].name, name) == 0)
				return &opts[i];
		} else {
			if (strcmp(opts[i].name, name) == 0)
				return &opts[i];
		}
	}

	return NULL;
}


DLLIMPORT void *cfg_getnptr(cfg_t *cfg, const char *name, unsigned int index)
{
	return cfg_opt_getnptr(cfg_getopt(cfg, name), index);
}


DLLIMPORT cfg_print_filter_func_t cfg_set_print_filter_func(cfg_t *cfg, cfg_print_filter_func_t pff)
{
	cfg_print_filter_func_t old;

	if (!cfg) {
		errno = EINVAL;
		return NULL;
	}

	old = cfg->pff;
	cfg->pff = pff;

	return old;
}


DLLIMPORT int cfg_opt_print(cfg_opt_t *opt, FILE *fp)
{
	return cfg_opt_print_pff_indent(opt, fp, NULL, 0);
}


DLLIMPORT int cfg_rmtsec(cfg_t *cfg, const char *name, const char *title)
{
	return cfg_opt_rmtsec(cfg_getopt(cfg, name), title);
}


static int cfg_free_searchpath(cfg_searchpath_t *p)
{
	if (p) {
		cfg_free_searchpath(p->next);
		free(p->dir);
		free(p);
	}

	return CFG_SUCCESS;
}


DLLIMPORT char *cfg_searchpath(cfg_searchpath_t *p, const char *file)
{
	char *fullpath;
#ifdef HAVE_SYS_STAT_H
	struct stat st;
	int err;
#endif

	if (!p || !file) {
		errno = EINVAL;
		return NULL;
	}

	if (file[0] == '/') {
		fullpath = strdup(file);
		if (!fullpath)
			return NULL;
		goto check;
	}

	if ((fullpath = cfg_searchpath(p->next, file)) != NULL)
		return fullpath;

	if ((fullpath = cfg_make_fullpath(p->dir, file)) == NULL)
		return NULL;

check:
#ifdef HAVE_SYS_STAT_H
	err = stat((const char *)fullpath, &st);
	if ((!err) && S_ISREG(st.st_mode))
		return fullpath;
#else
	/* needs an alternative check here for win32 */
#endif

	free(fullpath);
	return NULL;
}


DLLIMPORT void *cfg_opt_getnptr(cfg_opt_t *opt, unsigned int index)
{
	if (!opt || opt->type != CFGT_PTR) {
		errno = EINVAL;
		return NULL;
	}

	if (opt->values && index < opt->nvalues)
		return opt->values[index]->ptr;
	if (opt->simple_value.ptr)
		return *opt->simple_value.ptr;

	return NULL;
}


DLLIMPORT int cfg_opt_setnint(cfg_opt_t *opt, long int value, unsigned int index)
{
	cfg_value_t *val;

	if (!opt || opt->type != CFGT_INT) {
		errno = EINVAL;
		return CFG_FAIL;
	}

	val = cfg_opt_getval(opt, index);
	if (!val)
		return CFG_FAIL;

	val->number = value;
	opt->flags |= CFGF_MODIFIED;

	return CFG_SUCCESS;
}


DLLIMPORT cfg_t *cfg_addtsec(cfg_t *cfg, const char *name, const char *title)
{
	cfg_opt_t *opt;
	cfg_value_t *val;

	if (cfg_gettsec(cfg, name, title))
		return NULL;

	opt = cfg_getopt(cfg, name);
	if (!opt) {
		cfg_error(cfg, _("no such option '%s'"), name);
		return NULL;
	}
	val = cfg_setopt(cfg, opt, title);
	if (!val)
		return NULL;

	val->section->path = cfg->path; /* Remember global search path. */
	val->section->line = 1;
	val->section->errfunc = cfg->errfunc;

	return val->section;
}


DLLIMPORT int cfg_opt_setnfloat(cfg_opt_t *opt, double value, unsigned int index)
{
	cfg_value_t *val;

	if (!opt || opt->type != CFGT_FLOAT) {
		errno = EINVAL;
		return CFG_FAIL;
	}

	val = cfg_opt_getval(opt, index);
	if (!val)
		return CFG_FAIL;

	val->fpnumber = value;
	opt->flags |= CFGF_MODIFIED;

	return CFG_SUCCESS;
}


DLLIMPORT int cfg_print_indent(cfg_t *cfg, FILE *fp, int indent)
{
	return cfg_print_pff_indent(cfg, fp, NULL, indent);
}


DLLIMPORT int cfg_addlist(cfg_t *cfg, const char *name, unsigned int nvalues, ...)
{
	va_list ap;
	cfg_opt_t *opt = cfg_getopt(cfg, name);

	if (!opt || !is_set(CFGF_LIST, opt->flags)) {
		errno = EINVAL;
		return CFG_FAIL;
	}

	va_start(ap, nvalues);
	cfg_addlist_internal(opt, nvalues, ap);
	va_end(ap);

	return CFG_SUCCESS;
}


static char *cfg_make_fullpath(const char *dir, const char *file)
{
	int np;
	char *path;
	size_t len;

	if (!dir || !file) {
		errno = EINVAL;
		return NULL;
	}

	len = strlen(dir) + strlen(file) + 2;
	path = malloc(len);
	if (!path)
		return NULL;

	np = snprintf(path, len, "%s/%s", dir, file);

	/*
	 * np is the number of characters that would have
	 * been printed if there was enough room in path.
	 * if np >= n then the snprintf() was truncated
	 * (which must be a bug).
	 */
	assert(np < (int)len);

	return path;
}


DLLIMPORT int cfg_parse(cfg_t *cfg, const char *filename)
{
	int ret;
	char *fn;
	FILE *fp;

	if (!cfg || !filename) {
		errno = EINVAL;
		return CFG_FILE_ERROR;
	}

	if (cfg->path)
		fn = cfg_searchpath(cfg->path, filename);
	else
		fn = cfg_tilde_expand(filename);
	if (!fn)
		return CFG_FILE_ERROR;

	free(cfg->filename);
	cfg->filename = fn;

	fp = fopen(cfg->filename, "r");
	if (!fp)
		return CFG_FILE_ERROR;

	ret = cfg_parse_fp(cfg, fp);
	fclose(fp);

	return ret;
}


DLLIMPORT cfg_t *cfg_gettsec(cfg_t *cfg, const char *name, const char *title)
{
	return cfg_opt_gettsec(cfg_getopt(cfg, name), title);
}


DLLIMPORT signed long cfg_getint(cfg_t *cfg, const char *name)
{
	return cfg_getnint(cfg, name, 0);
}


DLLIMPORT int cfg_setnfloat(cfg_t *cfg, const char *name, double value, unsigned int index)
{
	cfg_opt_t *opt;

	opt = cfg_getopt(cfg, name);
	if (opt && opt->validcb2 && (*opt->validcb2)(cfg, opt, (void *)&value) != 0)
		return CFG_FAIL;

	return cfg_opt_setnfloat(opt, value, index);
}


DLLIMPORT char *cfg_getnstr(cfg_t *cfg, const char *name, unsigned int index)
{
	return cfg_opt_getnstr(cfg_getopt(cfg, name), index);
}


DLLIMPORT cfg_t *cfg_opt_gettsec(cfg_opt_t *opt, const char *title)
{
	long int i;

	if (!opt || !title) {
		errno = EINVAL;
		return NULL;
	}

	if (!is_set(CFGF_TITLE, opt->flags)) {
		errno = EINVAL;
		return NULL;
	}

	i = cfg_opt_gettsecidx(opt, title);
	if (i >= 0)
		return cfg_opt_getnsec(opt, i);

	errno = ENOENT;
	return NULL;
}


static int call_function(cfg_t *cfg, cfg_opt_t *opt, cfg_opt_t *funcopt)
{
	int ret;
	const char **argv;
	unsigned int i;

	if (!cfg || !opt ||!funcopt) {
		errno = EINVAL;
		return CFG_FAIL;
	}

	/*
	 * create am argv string vector and call the registered function
	 */
	argv = calloc(funcopt->nvalues, sizeof(char *));
	if (!argv)
		return CFG_FAIL;

	for (i = 0; i < funcopt->nvalues; i++)
		argv[i] = funcopt->values[i]->string;

	ret = (*opt->func) (cfg, opt, funcopt->nvalues, argv);
	cfg_free_value(funcopt);
	free(argv);

	return ret;
}


DLLIMPORT unsigned int cfg_size(cfg_t *cfg, const char *name)
{
	return cfg_opt_size(cfg_getopt(cfg, name));
}


DLLIMPORT signed long cfg_getnint(cfg_t *cfg, const char *name, unsigned int index)
{
	return cfg_opt_getnint(cfg_getopt(cfg, name), index);
}


DLLIMPORT cfg_validate_callback2_t cfg_set_validate_func2(cfg_t *cfg, const char *name, cfg_validate_callback2_t vf)
{
	cfg_opt_t *opt;
	cfg_validate_callback2_t oldvf;

	opt = cfg_getopt_array(cfg->opts, cfg->flags, name);
	if (!opt)
		return NULL;

	oldvf = opt->validcb2;
	opt->validcb2 = vf;

	return oldvf;
}


DLLIMPORT char *cfg_getstr(cfg_t *cfg, const char *name)
{
	return cfg_getnstr(cfg, name, 0);
}


DLLIMPORT cfg_errfunc_t cfg_set_error_function(cfg_t *cfg, cfg_errfunc_t errfunc)
{
	cfg_errfunc_t old;

	if (!cfg) {
		errno = EINVAL;
		return NULL;
	}

	old = cfg->errfunc;
	cfg->errfunc = errfunc;

	return old;
}


DLLIMPORT char *cfg_opt_getcomment(cfg_opt_t *opt)
{
	if (opt)
		return opt->comment;

	return NULL;
}


DLLIMPORT cfg_opt_t *cfg_getopt(cfg_t *cfg, const char *name)
{
	return cfg_getopt_secidx(cfg, name, NULL);
}


int strcasecmp(const char *s1, const char *s2)
{
	assert(s1);
	assert(s2);

	while (*s1) {
		int c1 = tolower(*(const unsigned char *)s1);
		int c2 = tolower(*(const unsigned char *)s2);

		if (c1 < c2)
			return -1;
		if (c1 > c2)
			return +1;

		++s1;
		++s2;
	}

	if (*s2 != 0)
		return -1;

	return 0;
}


DLLIMPORT cfg_t *cfg_opt_getnsec(cfg_opt_t *opt, unsigned int index)
{
	if (!opt || opt->type != CFGT_SEC) {
		errno = EINVAL;
		return NULL;
	}

	if (opt->values && index < opt->nvalues)
		return opt->values[index]->section;

	errno = ENOENT;
	return NULL;
}


DLLIMPORT signed long cfg_opt_getnint(cfg_opt_t *opt, unsigned int index)
{
	if (!opt || opt->type != CFGT_INT) {
		errno = EINVAL;
		return 0;
	}

	if (opt->values && index < opt->nvalues)
		return opt->values[index]->number;
	if (opt->simple_value.number)
		return *opt->simple_value.number;

	return 0;
}


static char *strdup(const char *str)
{
	size_t len;
	char *dup;

	len = strlen(str) + 1;
	dup = calloc(len, sizeof(char));
	if (!dup)
		return NULL;

	memcpy(dup, str, len);

	return dup;
}


DLLIMPORT int cfg_setbool(cfg_t *cfg, const char *name, cfg_bool_t value)
{
	return cfg_setnbool(cfg, name, value, 0);
}