WebServer.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. #include "WebServer.h"
  2. #include <iostream>
  3. #include <sstream>
  4. #include <random>
  5. #include <chrono>
  6. #include <boost/beast/core/detail/base64.hpp>
  7. #include <boost/algorithm/string.hpp>
  8. #include <boost/property_tree/ptree.hpp>
  9. #include <boost/property_tree/json_parser.hpp>
  10. #include <sstream>
  11. #include <string>
  12. #include <iostream>
  13. #include "../version.h"
  14. #ifdef WORLD
  15. #include "../../WorldServer/WorldDatabase.h"
  16. extern WorldDatabase database;
  17. #endif
  18. #ifdef LOGIN
  19. #include "../../LoginServer/LoginDatabase.h"
  20. extern LoginDatabase database;
  21. #endif
  22. #ifdef WIN32
  23. #include <process.h>
  24. #define strncasecmp _strnicmp
  25. #define strcasecmp _stricmp
  26. #include <conio.h>
  27. #else
  28. #include <pthread.h>
  29. #include "../unix.h"
  30. #endif
  31. ThreadReturnType RunWebServer (void* tmp);
  32. static std::string keypasswd = "";
  33. void web_handle_version(const http::request<http::string_body>& req, http::response<http::string_body>& res) {
  34. res.set(http::field::content_type, "application/json");
  35. boost::property_tree::ptree pt;
  36. // Add key-value pairs to the property tree
  37. pt.put("eq2emu_process", std::string(EQ2EMU_MODULE));
  38. pt.put("version", std::string(CURRENT_VERSION));
  39. pt.put("compile_date", std::string(COMPILE_DATE));
  40. pt.put("compile_time", std::string(COMPILE_TIME));
  41. // Create an output string stream to hold the JSON string
  42. std::ostringstream oss;
  43. // Write the property tree to the output string stream as JSON
  44. boost::property_tree::write_json(oss, pt);
  45. // Get the JSON string from the output string stream
  46. std::string json = oss.str();
  47. res.body() = json;
  48. res.prepare_payload();
  49. }
  50. void web_handle_root(const http::request<http::string_body>& req, http::response<http::string_body>& res) {
  51. res.set(http::field::content_type, "text/html");
  52. res.body() = "Hello!";
  53. res.prepare_payload();
  54. }
  55. // this function is called to obtain password info about an encrypted key
  56. std::string WebServer::my_password_callback(
  57. std::size_t max_length, // the maximum length for a password
  58. ssl::context::password_purpose purpose ) // for_reading or for_writing
  59. {
  60. return keypasswd;
  61. }
  62. //void handle_root(const http::request<http::string_body>& req, http::response<http::string_body>& res);
  63. 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)
  64. : ioc_(1),
  65. ssl_ctx_(ssl::context::tlsv13_server),
  66. acceptor_(ioc_, {boost_net::ip::make_address(address), port}) {
  67. keypasswd = key_password;
  68. // Initialize SSL context
  69. if(cert_file.size() < 1 || key_file.size() < 1) {
  70. is_ssl = false;
  71. }
  72. else {
  73. ssl_ctx_.set_password_callback(my_password_callback);
  74. ssl_ctx_.use_certificate_chain_file(cert_file);
  75. ssl_ctx_.use_private_key_file(key_file, ssl::context::file_format::pem);
  76. is_ssl = true;
  77. }
  78. keypasswd = ""; // reset no longer needed
  79. // Initialize some test credentials
  80. if(hardcode_user.size() > 0 && hardcode_password.size() > 0)
  81. credentials_[hardcode_user] = hardcode_password;
  82. register_route("/", web_handle_root);
  83. register_route("/version", web_handle_version);
  84. }
  85. WebServer::~WebServer() {
  86. ioc_.stop();
  87. }
  88. ThreadReturnType RunWebServer (void* tmp) {
  89. if(tmp == nullptr) {
  90. THREAD_RETURN(NULL);
  91. }
  92. WebServer* ws = (WebServer*)tmp;
  93. ws->start();
  94. THREAD_RETURN(NULL);
  95. }
  96. void WebServer::start() {
  97. do_accept();
  98. ioc_.run();
  99. }
  100. void WebServer::run() {
  101. pthread_t thread;
  102. pthread_create(&thread, NULL, RunWebServer, this);
  103. pthread_detach(thread);
  104. }
  105. void WebServer::register_route(const std::string& uri, std::function<void(const http::request<http::string_body>&, http::response<http::string_body>&)> handler, bool auth_req) {
  106. int32 status = database.NoAuthRoute((char*)uri.c_str()); // overrides the default hardcode settings via DB
  107. if(status == 0) {
  108. auth_req = false;
  109. }
  110. if(auth_req) {
  111. routes_[uri] = handler;
  112. }
  113. else {
  114. noauth_routes_[uri] = handler;
  115. }
  116. route_required_status_[uri] = status;
  117. }
  118. void WebServer::do_accept() {
  119. acceptor_.async_accept(
  120. [this](beast::error_code ec, tcp::socket socket) {
  121. this->on_accept(ec, std::move(socket));
  122. });
  123. }
  124. void WebServer::on_accept(beast::error_code ec, tcp::socket socket) {
  125. if (!ec) {
  126. if(is_ssl) {
  127. std::thread(&WebServer::do_session_ssl, this, std::move(socket)).detach();
  128. }
  129. else {
  130. std::thread(&WebServer::do_session, this, std::move(socket)).detach();
  131. }
  132. }
  133. do_accept();
  134. }
  135. void WebServer::do_session_ssl(tcp::socket socket) {
  136. try {
  137. ssl::stream<tcp::socket> stream(std::move(socket), ssl_ctx_);
  138. stream.handshake(ssl::stream_base::server);
  139. bool close = false;
  140. beast::flat_buffer buffer;
  141. while (!close) {
  142. http::request<http::string_body> req;
  143. http::read(stream, buffer, req);
  144. // Send the response
  145. handle_request(std::move(req), [&](auto&& response) {
  146. if (response.need_eof()) {
  147. close = true;
  148. }
  149. http::write(stream, response);
  150. });
  151. if (close) break;
  152. }
  153. beast::error_code ec;
  154. socket.shutdown(tcp::socket::shutdown_send, ec);
  155. }
  156. catch (const std::exception& e) {
  157. // irrelevant spam for now really
  158. }
  159. }
  160. void WebServer::do_session(tcp::socket socket) {
  161. try {
  162. bool close = false;
  163. beast::flat_buffer buffer;
  164. while (!close) {
  165. http::request<http::string_body> req;
  166. http::read(socket, buffer, req);
  167. // Send the response
  168. handle_request(std::move(req), [&](auto&& response) {
  169. if (response.need_eof()) {
  170. close = true;
  171. }
  172. http::write(socket, response);
  173. });
  174. if (close) break;
  175. }
  176. beast::error_code ec;
  177. socket.shutdown(tcp::socket::shutdown_send, ec);
  178. }
  179. catch (const std::exception& e) {
  180. // irrelevant spam for now really
  181. }
  182. }
  183. template <class Body, class Allocator>
  184. void WebServer::handle_request(http::request<Body, http::basic_fields<Allocator>>&& req, std::function<void(http::response<http::string_body>&&)> send) {
  185. auto it = noauth_routes_.find(req.target().to_string());
  186. if (it != noauth_routes_.end()) {
  187. http::response<http::string_body> res{http::status::ok, req.version()};
  188. res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
  189. it->second(req, res);
  190. return send(std::move(res));
  191. }
  192. int32 user_status = 0;
  193. std::string session_id = authenticate(req, &user_status);
  194. if (session_id.size() < 1) {
  195. http::response<http::string_body> res{http::status::unauthorized, req.version()};
  196. res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
  197. res.set(http::field::www_authenticate, "Basic realm=\"example\"");
  198. res.body() = "Unauthorized";
  199. res.prepare_payload();
  200. return send(std::move(res));
  201. }
  202. auto status_it = route_required_status_.find(req.target().to_string());
  203. if (status_it != route_required_status_.end()) {
  204. if(status_it->second > 0 && status_it->second != 0xFFFFFFFF && status_it->second > user_status) {
  205. http::response<http::string_body> res{http::status::unauthorized, req.version()};
  206. res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
  207. res.body() = "Unauthorized status";
  208. res.prepare_payload();
  209. return send(std::move(res));
  210. }
  211. }
  212. it = routes_.find(req.target().to_string());
  213. if (it != routes_.end()) {
  214. http::response<http::string_body> res{http::status::ok, req.version()};
  215. res.set(http::field::set_cookie, "session_id=" + session_id);
  216. res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
  217. it->second(req, res);
  218. return send(std::move(res));
  219. }
  220. /*
  221. http::response<http::string_body> res{http::status::not_found, req.version()};
  222. res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
  223. res.body() = "Not Found";
  224. res.prepare_payload();
  225. return send(std::move(res));
  226. */
  227. return send(http::response<http::string_body>{http::status::bad_request, req.version()});
  228. }
  229. std::string WebServer::authenticate(const http::request<http::string_body>& req, int32* user_status) {
  230. auto it = req.find(http::field::cookie);
  231. if (it != req.end()) {
  232. std::istringstream cookie_stream(it->value().to_string());
  233. std::string session_id;
  234. std::getline(cookie_stream, session_id, '=');
  235. if (session_id == "session_id") {
  236. std::string id;
  237. std::getline(cookie_stream, id);
  238. if (sessions_.find(id) != sessions_.end()) {
  239. if(sessions_status_.find(id) != sessions_status_.end()) {
  240. *user_status = sessions_status_[id];
  241. }
  242. return id;
  243. }
  244. }
  245. }
  246. it = req.find(http::field::authorization);
  247. if (it != req.end()) {
  248. std::string auth_header = it->value().to_string();
  249. if (auth_header.substr(0, 6) == "Basic ") {
  250. std::string encoded_credentials = auth_header.substr(6);
  251. std::string decoded_credentials;
  252. decoded_credentials.resize(boost::beast::detail::base64::decoded_size(encoded_credentials.size()));
  253. auto result = boost::beast::detail::base64::decode(
  254. &decoded_credentials[0],
  255. encoded_credentials.data(),
  256. encoded_credentials.size()
  257. );
  258. decoded_credentials.resize(result.first);
  259. std::istringstream credentials_stream(decoded_credentials);
  260. std::string username, password;
  261. std::getline(credentials_stream, username, ':');
  262. std::getline(credentials_stream, password);
  263. int32 out_status = 0;
  264. if ((credentials_.find(username) != credentials_.end() && credentials_[username] == password) || (database.AuthenticateWebUser((char*)username.c_str(),(char*)password.c_str(), &out_status) > 0)) {
  265. std::string session_id = generate_session_id();
  266. sessions_[session_id] = username;
  267. sessions_status_[session_id] = out_status;
  268. *user_status = out_status;
  269. return session_id;
  270. }
  271. }
  272. }
  273. return std::string("");
  274. }
  275. std::string WebServer::generate_session_id() {
  276. static std::mt19937 rng{std::random_device{}()};
  277. static std::uniform_int_distribution<> dist(0, 15);
  278. std::string session_id;
  279. for (int i = 0; i < 32; ++i) {
  280. session_id += "0123456789abcdef"[dist(rng)];
  281. }
  282. return session_id;
  283. }
  284. // Explicit template instantiation
  285. template void WebServer::handle_request<http::string_body, std::allocator<char>>(
  286. http::request<http::string_body, http::basic_fields<std::allocator<char>>>&&,
  287. std::function<void(http::response<http::string_body>&&)>
  288. );