My goal is to be able to do load multiple PEM files in boost so it can do a correct SSL handshake with a client depending on the SNI sent in TLS which means a single ip address can host multiple https sites. I am looking through the boost document SSL section, but nothing there about SNI. Because my project depends on boost::asio and I don’t know if it is possible and how to refer the example code, s_server.c, in openssl directory ( https://github.com/openssl/openssl/blob/master/apps/s_server.c#L127 ) THANK YOU for any hints.
And my https server code is here:
template<class service_pool_policy = io_service_pool>
class httpTLS_server_ : private noncopyable {
public:
template<class... Args>
explicit httpTLS_server_(Args&&... args) : io_service_pool_(std::forward<Args>(args)...)
, ctx_(boost::asio::ssl::context::tls_server)
{
http_cache::get().set_cache_max_age(86400);
init_conn_callback();
}
void enable_http_cache(bool b) {
http_cache::get().enable_cache(b);
}
template<typename F>
void init_ssl_context(bool ssl_enable_v3, F&& f, std::string certificate_chain_file,
std::string private_key_file, std::string tmp_dh_file) {
unsigned long ssl_options = boost::asio::ssl::context::default_workarounds
| boost::asio::ssl::context::no_sslv2
| boost::asio::ssl::context::no_sslv3
| boost::asio::ssl::context::no_tlsv1
| boost::asio::ssl::context::single_dh_use;
ctx_.set_options(ssl_options);
ctx_.set_password_callback(std::forward<F>(f));
ctx_.use_certificate_chain_file(std::move(certificate_chain_file));
ctx_.use_private_key_file(std::move(private_key_file), boost::asio::ssl::context::pem);
}
//address :
// "0.0.0.0" : ipv4. use 'https://localhost/' to visit
// "::1" : ipv6. use 'https://[::1]/' to visit
// "" : ipv4 & ipv6.
bool listen(std::string_view address, std::string_view port) {
boost::asio::ip::tcp::resolver::query query(address.data(), port.data());
return listen(query);
}
//support ipv6 & ipv4
bool listen(std::string_view port) {
boost::asio::ip::tcp::resolver::query query(port.data());
return listen(query);
}
bool listen(const boost::asio::ip::tcp::resolver::query & query) {
boost::asio::ip::tcp::resolver resolver(io_service_pool_.get_io_service());
boost::asio::ip::tcp::resolver::iterator endpoints = resolver.resolve(query);
bool r = false;
for (; endpoints != boost::asio::ip::tcp::resolver::iterator(); ++endpoints) {
boost::asio::ip::tcp::endpoint endpoint = *endpoints;
auto acceptor = std::make_shared<boost::asio::ip::tcp::acceptor>(io_service_pool_.get_io_service());
acceptor->open(endpoint.protocol());
acceptor->set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
try {
acceptor->bind(endpoint);
acceptor->listen();
start_accept(acceptor);
r = true;
}
catch (const std::exception& ex) {
std::cout << ex.what() << "\n";
//LOG_INFO << e.what();
}
}
return r;
}
void stop() {
io_service_pool_.stop();
}
void run() {
if (!fs::exists(public_root_path_.data())) {
fs::create_directories(public_root_path_.data());
}
if (!fs::exists(static_dir_.data())) {
fs::create_directories(static_dir_.data());
}
io_service_pool_.run();
}
intptr_t run_one() {
return io_service_pool_.run_one();
}
intptr_t poll() {
return io_service_pool_.poll();
}
intptr_t poll_one() {
return io_service_pool_.poll_one();
}
void set_static_dir(std::string&& path) {
static_dir_ = public_root_path_+std::move(path)+"/";
}
const std::string& static_dir() const {
return static_dir_;
}
//xM
void set_max_req_buf_size(std::size_t max_buf_size) {
max_req_buf_size_ = max_buf_size;
}
void set_keep_alive_timeout(long seconds) {
keep_alive_timeout_ = seconds;
}
template<typename T>
bool need_cache(T&& t) {
if constexpr(std::is_same_v<T, enable_cache<bool>>) {
return t.value;
}
else {
return false;
}
}
//set http handlers
template<http_method... Is, typename Function, typename... AP>
void set_http_handler(std::string_view name, Function&& f, AP&&... ap) {
if constexpr(has_type<enable_cache<bool>, std::tuple<std::decay_t<AP>...>>::value) {//for cache
bool b = false;
((!b&&(b = need_cache(std::forward<AP>(ap)))),...);
if (!b) {
http_cache::get().add_skip(name);
}else{
http_cache::get().add_single_cache(name);
}
auto tp = filter<enable_cache<bool>>(std::forward<AP>(ap)...);
auto lm = [this, name, f = std::move(f)](auto... ap) {
https_router_.register_handler<Is...>(name, std::move(f), std::move(ap)...);
};
std::apply(lm, std::move(tp));
}
else {
https_router_.register_handler<Is...>(name, std::forward<Function>(f), std::forward<AP>(ap)...);
}
}
void set_base_path(const std::string& key,const std::string& path)
{
base_path_[0] = std::move(key);
base_path_[1] = std::move(path);
}
void set_res_cache_max_age(std::time_t seconds)
{
static_res_cache_max_age_ = seconds;
}
std::time_t get_res_cache_max_age()
{
return static_res_cache_max_age_;
}
void set_cache_max_age(std::time_t seconds)
{
http_cache::get().set_cache_max_age(seconds);
}
std::time_t get_cache_max_age()
{
return http_cache::get().get_cache_max_age();
}
//don't begin with "./" or "/", not absolutely path
void set_public_root_directory(const std::string& name)
{
if(!name.empty()){
public_root_path_ = "./"+name+"/";
}
else {
public_root_path_ = "./";
}
}
std::string get_public_root_directory()
{
return public_root_path_;
}
void set_download_check(std::function<bool(request_ssl& req, response& res)> checker) {
download_check_ = std::move(checker);
}
//should be called before listen
void set_upload_check(std::function<bool(request_ssl& req, response& res)> checker) {
upload_check_ = std::move(checker);
}
void mapping_to_root_path(std::string relate_path) {
relate_paths_.emplace_back("."+std::move(relate_path));
}
private:
void start_accept(std::shared_ptr<boost::asio::ip::tcp::acceptor> const& acceptor) {
auto new_conn = std::make_shared<connection_ssl<Socket_ssl>>(
io_service_pool_.get_io_service(), max_req_buf_size_, keep_alive_timeout_, https_handler_, static_dir_,
upload_check_?&upload_check_ : nullptr
, ctx_
);
acceptor->async_accept(new_conn->socket(), [this, new_conn, acceptor](const boost::system::error_code& e) {
if (!e) {
new_conn->socket().set_option(boost::asio::ip::tcp::no_delay(true));
new_conn->start();
}
else {
std::cout << "server::handle_accept: " << e.message();
//LOG_INFO << "server::handle_accept: " << e.message();
}
start_accept(acceptor);
});
}
void init_conn_callback() {
set_static_res_handler();
https_handler_ = [this](request_ssl& req, response& res) {
res.set_base_path(this->base_path_[0],this->base_path_[1]);
res.set_url(req.get_url());
try {
bool success = https_router_.route(req.get_method(), req.get_url(), req, res);
if (!success) {
//updated by neo
//res.set_status_and_content(status_type::bad_request, "the url is not right");
res.redirect("/");
//updated end
}
}
catch (const std::exception& ex) {
res.set_status_and_content(status_type::internal_server_error, ex.what()+std::string(" exception in business function"));
}
catch (...) {
res.set_status_and_content(status_type::internal_server_error, "unknown exception in business function");
}
};
}
service_pool_policy io_service_pool_;
std::size_t max_req_buf_size_ = 3 * 1024 * 1024; //max request buffer size 3M
long keep_alive_timeout_ = 60; //max request timeout 60s
https_router https_router_;
std::string static_dir_ = "./public/static/"; //default
std::string base_path_[2] = {"base_path","/"};
std::time_t static_res_cache_max_age_ = 0;
std::string public_root_path_ = "./";
boost::asio::ssl::context ctx_;
//SSL_CTX ctx_ ;
//SSL_CTX *ctx2 = NULL;
https_handler https_handler_ = nullptr;
std::function<bool(request_ssl& req, response& res)> download_check_;
std::vector<std::string> relate_paths_;
std::function<bool(request_ssl& req, response& res)> upload_check_ = nullptr;
};
using httpTLS_server = httpTLS_server_<io_service_pool>;