#include "WebServer.h" #include #include #include #include #include #include #include #include #include #include #include #include "../version.h" #ifdef WORLD #include "../../WorldServer/WorldDatabase.h" extern WorldDatabase database; #endif #ifdef LOGIN #include "../../LoginServer/LoginDatabase.h" extern LoginDatabase database; #endif #ifdef WIN32 #include #define strncasecmp _strnicmp #define strcasecmp _stricmp #include #else #include #include "../unix.h" #endif ThreadReturnType RunWebServer (void* tmp); static std::string keypasswd = ""; void web_handle_version(const http::request& req, http::response& res) { res.set(http::field::content_type, "application/json"); boost::property_tree::ptree pt; // Add key-value pairs to the property tree pt.put("eq2emu_process", std::string(EQ2EMU_MODULE)); pt.put("version", std::string(CURRENT_VERSION)); pt.put("compile_date", std::string(COMPILE_DATE)); pt.put("compile_time", std::string(COMPILE_TIME)); // Create an output string stream to hold the JSON string std::ostringstream oss; // Write the property tree to the output string stream as JSON boost::property_tree::write_json(oss, pt); // Get the JSON string from the output string stream std::string json = oss.str(); res.body() = json; res.prepare_payload(); } void web_handle_root(const http::request& req, http::response& res) { res.set(http::field::content_type, "text/html"); res.body() = "Hello!"; res.prepare_payload(); } // this function is called to obtain password info about an encrypted key std::string WebServer::my_password_callback( std::size_t max_length, // the maximum length for a password ssl::context::password_purpose purpose ) // for_reading or for_writing { return keypasswd; } //void handle_root(const http::request& req, http::response& res); WebServer::WebServer(const std::string& address, unsigned short port, const std::string& cert_file, const std::string& key_file, const std::string& key_password, const std::string& hardcode_user, const std::string& hardcode_password) : ioc_(1), ssl_ctx_(ssl::context::tlsv13_server), acceptor_(ioc_, {boost_net::ip::make_address(address), port}) { keypasswd = key_password; // Initialize SSL context if(cert_file.size() < 1 || key_file.size() < 1) { is_ssl = false; } else { ssl_ctx_.set_password_callback(my_password_callback); ssl_ctx_.use_certificate_chain_file(cert_file); ssl_ctx_.use_private_key_file(key_file, ssl::context::file_format::pem); is_ssl = true; } keypasswd = ""; // reset no longer needed // Initialize some test credentials if(hardcode_user.size() > 0 && hardcode_password.size() > 0) credentials_[hardcode_user] = hardcode_password; register_route("/", web_handle_root); register_route("/version", web_handle_version); } WebServer::~WebServer() { ioc_.stop(); } ThreadReturnType RunWebServer (void* tmp) { if(tmp == nullptr) { THREAD_RETURN(NULL); } WebServer* ws = (WebServer*)tmp; ws->start(); THREAD_RETURN(NULL); } void WebServer::start() { do_accept(); ioc_.run(); } void WebServer::run() { pthread_t thread; pthread_create(&thread, NULL, RunWebServer, this); pthread_detach(thread); } void WebServer::register_route(const std::string& uri, std::function&, http::response&)> handler, bool auth_req) { int32 status = database.NoAuthRoute((char*)uri.c_str()); // overrides the default hardcode settings via DB if(status == 0) { auth_req = false; } if(auth_req) { routes_[uri] = handler; } else { noauth_routes_[uri] = handler; } route_required_status_[uri] = status; } void WebServer::do_accept() { acceptor_.async_accept( [this](beast::error_code ec, tcp::socket socket) { this->on_accept(ec, std::move(socket)); }); } void WebServer::on_accept(beast::error_code ec, tcp::socket socket) { if (!ec) { if(is_ssl) { std::thread(&WebServer::do_session_ssl, this, std::move(socket)).detach(); } else { std::thread(&WebServer::do_session, this, std::move(socket)).detach(); } } do_accept(); } void WebServer::do_session_ssl(tcp::socket socket) { try { ssl::stream stream(std::move(socket), ssl_ctx_); stream.handshake(ssl::stream_base::server); bool close = false; beast::flat_buffer buffer; while (!close) { http::request req; http::read(stream, buffer, req); // Send the response handle_request(std::move(req), [&](auto&& response) { if (response.need_eof()) { close = true; } http::write(stream, response); }); if (close) break; } beast::error_code ec; socket.shutdown(tcp::socket::shutdown_send, ec); } catch (const std::exception& e) { // irrelevant spam for now really } } void WebServer::do_session(tcp::socket socket) { try { bool close = false; beast::flat_buffer buffer; while (!close) { http::request req; http::read(socket, buffer, req); // Send the response handle_request(std::move(req), [&](auto&& response) { if (response.need_eof()) { close = true; } http::write(socket, response); }); if (close) break; } beast::error_code ec; socket.shutdown(tcp::socket::shutdown_send, ec); } catch (const std::exception& e) { // irrelevant spam for now really } } template void WebServer::handle_request(http::request>&& req, std::function&&)> send) { auto it = noauth_routes_.find(req.target().to_string()); if (it != noauth_routes_.end()) { http::response res{http::status::ok, req.version()}; res.set(http::field::server, BOOST_BEAST_VERSION_STRING); it->second(req, res); return send(std::move(res)); } int32 user_status = 0; std::string session_id = authenticate(req, &user_status); if (session_id.size() < 1) { http::response res{http::status::unauthorized, req.version()}; res.set(http::field::server, BOOST_BEAST_VERSION_STRING); res.set(http::field::www_authenticate, "Basic realm=\"example\""); res.body() = "Unauthorized"; res.prepare_payload(); return send(std::move(res)); } auto status_it = route_required_status_.find(req.target().to_string()); if (status_it != route_required_status_.end()) { if(status_it->second > 0 && status_it->second != 0xFFFFFFFF && status_it->second > user_status) { http::response res{http::status::unauthorized, req.version()}; res.set(http::field::server, BOOST_BEAST_VERSION_STRING); res.body() = "Unauthorized status"; res.prepare_payload(); return send(std::move(res)); } } it = routes_.find(req.target().to_string()); if (it != routes_.end()) { http::response res{http::status::ok, req.version()}; res.set(http::field::set_cookie, "session_id=" + session_id); res.set(http::field::server, BOOST_BEAST_VERSION_STRING); it->second(req, res); return send(std::move(res)); } /* http::response res{http::status::not_found, req.version()}; res.set(http::field::server, BOOST_BEAST_VERSION_STRING); res.body() = "Not Found"; res.prepare_payload(); return send(std::move(res)); */ return send(http::response{http::status::bad_request, req.version()}); } std::string WebServer::authenticate(const http::request& req, int32* user_status) { auto it = req.find(http::field::cookie); if (it != req.end()) { std::istringstream cookie_stream(it->value().to_string()); std::string session_id; std::getline(cookie_stream, session_id, '='); if (session_id == "session_id") { std::string id; std::getline(cookie_stream, id); if (sessions_.find(id) != sessions_.end()) { if(sessions_status_.find(id) != sessions_status_.end()) { *user_status = sessions_status_[id]; } return id; } } } it = req.find(http::field::authorization); if (it != req.end()) { std::string auth_header = it->value().to_string(); if (auth_header.substr(0, 6) == "Basic ") { std::string encoded_credentials = auth_header.substr(6); std::string decoded_credentials; decoded_credentials.resize(boost::beast::detail::base64::decoded_size(encoded_credentials.size())); auto result = boost::beast::detail::base64::decode( &decoded_credentials[0], encoded_credentials.data(), encoded_credentials.size() ); decoded_credentials.resize(result.first); std::istringstream credentials_stream(decoded_credentials); std::string username, password; std::getline(credentials_stream, username, ':'); std::getline(credentials_stream, password); int32 out_status = 0; if ((credentials_.find(username) != credentials_.end() && credentials_[username] == password) || (database.AuthenticateWebUser((char*)username.c_str(),(char*)password.c_str(), &out_status) > 0)) { std::string session_id = generate_session_id(); sessions_[session_id] = username; sessions_status_[session_id] = out_status; *user_status = out_status; return session_id; } } } return std::string(""); } std::string WebServer::generate_session_id() { static std::mt19937 rng{std::random_device{}()}; static std::uniform_int_distribution<> dist(0, 15); std::string session_id; for (int i = 0; i < 32; ++i) { session_id += "0123456789abcdef"[dist(rng)]; } return session_id; } // Explicit template instantiation template void WebServer::handle_request>( http::request>>&&, std::function&&)> );