Advertisement
nevadies

Untitled

Mar 10th, 2017
151
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 18.13 KB | None | 0 0
  1. /*
  2. * Copyright (C) 2014-2017 FireSorm-Servers
  3. *
  4. * This program is free software; you can redistribute it and/or modify it
  5. * under the terms of the GNU General Public License as published by the
  6. * Free Software Foundation; either version 2 of the License, or (at your
  7. * option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful, but WITHOUT
  10. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11. * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
  12. * more details.
  13. *
  14. * You should have received a copy of the GNU General Public License along
  15. * with this program. If not, see <http://www.gnu.org/licenses/>.
  16. */
  17.  
  18. #include "LoginRESTService.h"
  19. #include "Configuration/Config.h"
  20. #include "DatabaseEnv.h"
  21. #include "ProtobufJSON.h"
  22. #include "Realm.h"
  23. #include "SessionManager.h"
  24. #include "SHA1.h"
  25. #include "SHA256.h"
  26. #include "SslContext.h"
  27. #include "Util.h"
  28. #include "httpget.h"
  29. #include "httppost.h"
  30. #include "soapH.h"
  31.  
  32. int ns1__executeCommand(soap*, char*, char**) { return SOAP_OK; }
  33.  
  34. int32 handle_get_plugin(soap* soapClient)
  35. {
  36. return sLoginService.HandleGet(soapClient);
  37. }
  38.  
  39. int32 handle_post_plugin(soap* soapClient)
  40. {
  41. return sLoginService.HandlePost(soapClient);
  42. }
  43.  
  44. bool LoginRESTService::Start(boost::asio::io_service& ioService)
  45. {
  46. _bindIP = sConfigMgr->GetStringDefault("BindIP", "0.0.0.0");
  47. _port = sConfigMgr->GetIntDefault("LoginREST.Port", 8081);
  48. if (_port < 0 || _port > 0xFFFF)
  49. {
  50. TC_LOG_ERROR("server.rest", "Specified login service port (%d) out of allowed range (1-65535), defaulting to 8081", _port);
  51. _port = 8081;
  52. }
  53.  
  54. boost::system::error_code ec;
  55. boost::asio::ip::tcp::resolver resolver(ioService);
  56. boost::asio::ip::tcp::resolver::iterator end;
  57.  
  58. std::string configuredAddress = sConfigMgr->GetStringDefault("LoginREST.ExternalAddress", "127.0.0.1");
  59. boost::asio::ip::tcp::resolver::query externalAddressQuery(boost::asio::ip::tcp::v4(), configuredAddress, std::to_string(_port));
  60. boost::asio::ip::tcp::resolver::iterator endPoint = resolver.resolve(externalAddressQuery, ec);
  61. if (endPoint == end || ec)
  62. {
  63. TC_LOG_ERROR("server.rest", "Could not resolve LoginREST.ExternalAddress %s", configuredAddress.c_str());
  64. return false;
  65. }
  66.  
  67. _externalAddress = endPoint->endpoint();
  68.  
  69. configuredAddress = sConfigMgr->GetStringDefault("LoginREST.LocalAddress", "127.0.0.1");
  70. boost::asio::ip::tcp::resolver::query localAddressQuery(boost::asio::ip::tcp::v4(), configuredAddress, std::to_string(_port));
  71. endPoint = resolver.resolve(localAddressQuery, ec);
  72. if (endPoint == end || ec)
  73. {
  74. TC_LOG_ERROR("server.rest", "Could not resolve LoginREST.ExternalAddress %s", configuredAddress.c_str());
  75. return false;
  76. }
  77.  
  78. _localAddress = endPoint->endpoint();
  79.  
  80. // set up form inputs
  81. Battlenet::JSON::Login::FormInput* input;
  82. _formInputs.set_type(Battlenet::JSON::Login::LOGIN_FORM);
  83. input = _formInputs.add_inputs();
  84. input->set_input_id("account_name");
  85. input->set_type("text");
  86. input->set_label("E-mail");
  87. input->set_max_length(320);
  88.  
  89. input = _formInputs.add_inputs();
  90. input->set_input_id("password");
  91. input->set_type("password");
  92. input->set_label("Password");
  93. input->set_max_length(16);
  94.  
  95. input = _formInputs.add_inputs();
  96. input->set_input_id("log_in_submit");
  97. input->set_type("submit");
  98. input->set_label("Log In");
  99.  
  100. _loginTicketCleanupTimer = new boost::asio::deadline_timer(ioService);
  101. _loginTicketCleanupTimer->expires_from_now(boost::posix_time::seconds(10));
  102. _loginTicketCleanupTimer->async_wait(std::bind(&LoginRESTService::CleanupLoginTickets, this, std::placeholders::_1));
  103.  
  104. _thread = std::thread(std::bind(&LoginRESTService::Run, this));
  105. return true;
  106. }
  107.  
  108. void LoginRESTService::Stop()
  109. {
  110. _stopped = true;
  111. _loginTicketCleanupTimer->cancel();
  112. _thread.join();
  113. }
  114.  
  115. boost::asio::ip::tcp::endpoint const& LoginRESTService::GetAddressForClient(boost::asio::ip::address const& address) const
  116. {
  117. if (address.is_loopback())
  118. return _localAddress;
  119. else if (_localAddress.address().is_loopback())
  120. return _externalAddress;
  121.  
  122. boost::asio::ip::address_v4 netmask = boost::asio::ip::address_v4::netmask(_localAddress.address().to_v4());
  123. if ((netmask.to_ulong() & address.to_v4().to_ulong()) == (netmask.to_ulong() & _localAddress.address().to_v4().to_ulong()))
  124. return _localAddress;
  125.  
  126. return _externalAddress;
  127. }
  128.  
  129. void LoginRESTService::Run()
  130. {
  131. soap soapServer(SOAP_C_UTFSTRING, SOAP_C_UTFSTRING);
  132.  
  133. // check every 3 seconds if world ended
  134. soapServer.accept_timeout = 3;
  135. soapServer.recv_timeout = 5;
  136. soapServer.send_timeout = 5;
  137. if (!soap_valid_socket(soap_bind(&soapServer, _bindIP.c_str(), _port, 100)))
  138. {
  139. TC_LOG_ERROR("server.rest", "Couldn't bind to %s:%d", _bindIP.c_str(), _port);
  140. return;
  141. }
  142.  
  143. TC_LOG_INFO("server.rest", "Login service bound to http://%s:%d", _bindIP.c_str(), _port);
  144.  
  145. http_post_handlers handlers[] =
  146. {
  147. { "application/json;charset=utf-8", handle_post_plugin },
  148. { "application/json", handle_post_plugin },
  149. { nullptr, nullptr }
  150. };
  151.  
  152. soap_register_plugin_arg(&soapServer, &http_get, (void*)&handle_get_plugin);
  153. soap_register_plugin_arg(&soapServer, &http_post, handlers);
  154. soap_register_plugin_arg(&soapServer, &ContentTypePlugin::Init, (void*)"application/json;charset=utf-8");
  155.  
  156. // Use our already ready ssl context
  157. soapServer.ctx = Battlenet::SslContext::instance().native_handle();
  158. soapServer.ssl_flags = SOAP_SSL_RSA;
  159.  
  160. while (!_stopped)
  161. {
  162. if (!soap_valid_socket(soap_accept(&soapServer)))
  163. continue; // ran into an accept timeout
  164.  
  165. std::shared_ptr<soap> soapClient = std::make_shared<soap>(soapServer);
  166. boost::asio::ip::address_v4 address(soapClient->ip);
  167. if (soap_ssl_accept(soapClient.get()) != SOAP_OK)
  168. {
  169. TC_LOG_DEBUG("server.rest", "Failed SSL handshake from IP=%s", address.to_string().c_str());
  170. continue;
  171. }
  172.  
  173. TC_LOG_DEBUG("server.rest", "Accepted connection from IP=%s", address.to_string().c_str());
  174.  
  175. std::thread([soapClient]
  176. {
  177. soap_serve(soapClient.get());
  178. }).detach();
  179. }
  180.  
  181. // and release the context handle here - soap does not own it so it should not free it on exit
  182. soapServer.ctx = nullptr;
  183.  
  184. TC_LOG_INFO("server.rest", "Login service exiting...");
  185. }
  186.  
  187. int32 LoginRESTService::HandleGet(soap* soapClient)
  188. {
  189. boost::asio::ip::address_v4 address(soapClient->ip);
  190. std::string ip_address = address.to_string();
  191.  
  192. TC_LOG_DEBUG("server.rest", "[%s:%d] Handling GET request path=\"%s\"", ip_address.c_str(), soapClient->port, soapClient->path);
  193.  
  194. static std::string const expectedPath = "/bnetserver/login/";
  195. if (strstr(soapClient->path, expectedPath.c_str()) != &soapClient->path[0])
  196. return 404;
  197.  
  198. return SendResponse(soapClient, _formInputs);
  199. }
  200.  
  201. int32 LoginRESTService::HandlePost(soap* soapClient)
  202. {
  203. boost::asio::ip::address_v4 address(soapClient->ip);
  204. std::string ip_address = address.to_string();
  205.  
  206. TC_LOG_DEBUG("server.rest", "[%s:%d] Handling POST request path=\"%s\"", ip_address.c_str(), soapClient->port, soapClient->path);
  207.  
  208. static std::string const expectedPath = "/bnetserver/login/";
  209. if (strstr(soapClient->path, expectedPath.c_str()) != &soapClient->path[0])
  210. return 404;
  211.  
  212. char *buf;
  213. size_t len;
  214. soap_http_body(soapClient, &buf, &len);
  215.  
  216. Battlenet::JSON::Login::LoginForm loginForm;
  217. Battlenet::JSON::Login::LoginResult loginResult;
  218. if (!JSON::Deserialize(buf, &loginForm))
  219. {
  220. if (soap_register_plugin_arg(soapClient, &ResponseCodePlugin::Init, nullptr) != SOAP_OK)
  221. return 500;
  222.  
  223. ResponseCodePlugin* responseCode = reinterpret_cast<ResponseCodePlugin*>(soap_lookup_plugin(soapClient, ResponseCodePlugin::PluginId));
  224. ASSERT(responseCode);
  225.  
  226. responseCode->ErrorCode = 400;
  227.  
  228. loginResult.set_authentication_state(Battlenet::JSON::Login::LOGIN);
  229. loginResult.set_error_code("UNABLE_TO_DECODE");
  230. loginResult.set_error_message("There was an internal error while connecting to Battle.net. Please try again later.");
  231. return SendResponse(soapClient, loginResult);
  232. }
  233.  
  234. std::string login;
  235. std::string password;
  236.  
  237. for (int32 i = 0; i < loginForm.inputs_size(); ++i)
  238. {
  239. if (loginForm.inputs(i).input_id() == "account_name")
  240. login = loginForm.inputs(i).value();
  241. else if (loginForm.inputs(i).input_id() == "password")
  242. password = loginForm.inputs(i).value();
  243. }
  244.  
  245. Utf8ToUpperOnlyLatin(login);
  246. Utf8ToUpperOnlyLatin(password);
  247.  
  248. PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BNET_ACCOUNT_INFO);
  249. stmt->setString(0, login);
  250.  
  251. if (PreparedQueryResult result = LoginDatabase.Query(stmt))
  252. {
  253. std::string pass_hash = result->Fetch()[13].GetString();
  254.  
  255. std::unique_ptr<Battlenet::Session::AccountInfo> accountInfo = Trinity::make_unique<Battlenet::Session::AccountInfo>();
  256. accountInfo->LoadResult(result);
  257.  
  258. if (CalculateShaPassHash(login, std::move(password)) == pass_hash)
  259. {
  260. stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BNET_CHARACTER_COUNTS_BY_BNET_ID);
  261. stmt->setUInt32(0, accountInfo->Id);
  262. if (PreparedQueryResult characterCountsResult = LoginDatabase.Query(stmt))
  263. {
  264. do
  265. {
  266. Field* fields = characterCountsResult->Fetch();
  267. accountInfo->GameAccounts[fields[0].GetUInt32()]
  268. .CharacterCounts[Battlenet::RealmHandle{ fields[3].GetUInt8(), fields[4].GetUInt8(), fields[2].GetUInt32() }.GetAddress()] = fields[1].GetUInt8();
  269.  
  270. } while (characterCountsResult->NextRow());
  271. }
  272.  
  273. stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BNET_LAST_PLAYER_CHARACTERS);
  274. stmt->setUInt32(0, accountInfo->Id);
  275. if (PreparedQueryResult lastPlayerCharactersResult = LoginDatabase.Query(stmt))
  276. {
  277. Field* fields = lastPlayerCharactersResult->Fetch();
  278. Battlenet::RealmHandle realmId{ fields[1].GetUInt8(), fields[2].GetUInt8(), fields[3].GetUInt32() };
  279. Battlenet::Session::LastPlayedCharacterInfo& lastPlayedCharacter = accountInfo->GameAccounts[fields[0].GetUInt32()]
  280. .LastPlayedCharacters[realmId.GetSubRegionAddress()];
  281.  
  282. lastPlayedCharacter.RealmId = realmId;
  283. lastPlayedCharacter.CharacterName = fields[4].GetString();
  284. lastPlayedCharacter.CharacterGUID = fields[5].GetUInt64();
  285. lastPlayedCharacter.LastPlayedTime = fields[6].GetUInt32();
  286. }
  287.  
  288. BigNumber ticket;
  289. ticket.SetRand(20 * 8);
  290.  
  291. loginResult.set_login_ticket("TC-" + ByteArrayToHexStr(ticket.AsByteArray(20).get(), 20));
  292.  
  293. AddLoginTicket(loginResult.login_ticket(), std::move(accountInfo));
  294. }
  295. else if (!accountInfo->IsBanned)
  296. {
  297. uint32 maxWrongPassword = uint32(sConfigMgr->GetIntDefault("WrongPass.MaxCount", 0));
  298.  
  299. if (sConfigMgr->GetBoolDefault("WrongPass.Logging", false))
  300. TC_LOG_DEBUG("server.rest", "[%s, Account %s, Id %u] Attempted to connect with wrong password!", ip_address.c_str(), login.c_str(), accountInfo->Id);
  301.  
  302. if (maxWrongPassword)
  303. {
  304. SQLTransaction trans = LoginDatabase.BeginTransaction();
  305. stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_BNET_FAILED_LOGINS);
  306. stmt->setUInt32(0, accountInfo->Id);
  307. trans->Append(stmt);
  308.  
  309. ++accountInfo->FailedLogins;
  310.  
  311. TC_LOG_DEBUG("server.rest", "MaxWrongPass : %u, failed_login : %u", maxWrongPassword, accountInfo->Id);
  312.  
  313. if (accountInfo->FailedLogins >= maxWrongPassword)
  314. {
  315. BanMode banType = BanMode(sConfigMgr->GetIntDefault("WrongPass.BanType", uint16(BanMode::BAN_IP)));
  316. int32 banTime = sConfigMgr->GetIntDefault("WrongPass.BanTime", 600);
  317.  
  318. if (banType == BanMode::BAN_ACCOUNT)
  319. {
  320. stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_BNET_ACCOUNT_AUTO_BANNED);
  321. stmt->setUInt32(0, accountInfo->Id);
  322. }
  323. else
  324. {
  325. stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_IP_AUTO_BANNED);
  326. stmt->setString(0, ip_address);
  327. }
  328.  
  329. stmt->setUInt32(1, banTime);
  330. trans->Append(stmt);
  331.  
  332. stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_BNET_RESET_FAILED_LOGINS);
  333. stmt->setUInt32(0, accountInfo->Id);
  334. trans->Append(stmt);
  335. }
  336.  
  337. LoginDatabase.CommitTransaction(trans);
  338. }
  339. }
  340. }
  341.  
  342. loginResult.set_authentication_state(Battlenet::JSON::Login::DONE);
  343. return SendResponse(soapClient, loginResult);
  344. }
  345.  
  346. int32 LoginRESTService::SendResponse(soap* soapClient, google::protobuf::Message const& response)
  347. {
  348. std::string jsonResponse = JSON::Serialize(response);
  349.  
  350. soap_response(soapClient, SOAP_FILE);
  351. soap_send_raw(soapClient, jsonResponse.c_str(), jsonResponse.length());
  352. return soap_end_send(soapClient);
  353. }
  354.  
  355. std::string LoginRESTService::CalculateShaPassHash(std::string const& name, std::string const& password)
  356. {
  357. SHA256Hash email;
  358. email.UpdateData(name);
  359. email.Finalize();
  360.  
  361. SHA256Hash sha;
  362. sha.UpdateData(ByteArrayToHexStr(email.GetDigest(), email.GetLength()));
  363. sha.UpdateData(":");
  364. sha.UpdateData(password);
  365. sha.Finalize();
  366.  
  367. return ByteArrayToHexStr(sha.GetDigest(), sha.GetLength(), true);
  368. }
  369.  
  370. std::unique_ptr<Battlenet::Session::AccountInfo> LoginRESTService::VerifyLoginTicket(std::string const& id)
  371. {
  372. std::unique_lock<std::mutex> lock(_loginTicketMutex);
  373.  
  374. auto itr = _validLoginTickets.find(id);
  375. if (itr != _validLoginTickets.end())
  376. {
  377. if (itr->second.ExpiryTime > time(nullptr))
  378. {
  379. std::unique_ptr<Battlenet::Session::AccountInfo> accountInfo = std::move(itr->second.Account);
  380. _validLoginTickets.erase(itr);
  381. return accountInfo;
  382. }
  383. }
  384.  
  385. return std::unique_ptr<Battlenet::Session::AccountInfo>();
  386. }
  387.  
  388. void LoginRESTService::AddLoginTicket(std::string const& id, std::unique_ptr<Battlenet::Session::AccountInfo> accountInfo)
  389. {
  390. std::unique_lock<std::mutex> lock(_loginTicketMutex);
  391.  
  392. _validLoginTickets[id] = { id, std::move(accountInfo), time(nullptr) + 10 };
  393. }
  394.  
  395. void LoginRESTService::CleanupLoginTickets(boost::system::error_code const& error)
  396. {
  397. if (error)
  398. return;
  399.  
  400. time_t now = time(nullptr);
  401.  
  402. {
  403. std::unique_lock<std::mutex> lock(_loginTicketMutex);
  404. for (auto itr = _validLoginTickets.begin(); itr != _validLoginTickets.end();)
  405. {
  406. if (itr->second.ExpiryTime < now)
  407. itr = _validLoginTickets.erase(itr);
  408. else
  409. ++itr;
  410. }
  411. }
  412.  
  413. _loginTicketCleanupTimer->expires_from_now(boost::posix_time::seconds(10));
  414. _loginTicketCleanupTimer->async_wait(std::bind(&LoginRESTService::CleanupLoginTickets, this, std::placeholders::_1));
  415. }
  416.  
  417. LoginRESTService::LoginTicket& LoginRESTService::LoginTicket::operator=(LoginTicket&& right)
  418. {
  419. if (this != &right)
  420. {
  421. Id = std::move(right.Id);
  422. Account = std::move(right.Account);
  423. ExpiryTime = right.ExpiryTime;
  424. }
  425.  
  426. return *this;
  427. }
  428.  
  429. Namespace namespaces[] =
  430. {
  431. { NULL, NULL, NULL, NULL }
  432. };
  433.  
  434. LoginRESTService& LoginRESTService::Instance()
  435. {
  436. static LoginRESTService instance;
  437. return instance;
  438. }
  439.  
  440. char const* const LoginRESTService::ResponseCodePlugin::PluginId = "bnet-error-code";
  441.  
  442. int32 LoginRESTService::ResponseCodePlugin::Init(soap* s, soap_plugin* p, void* /*arg*/)
  443. {
  444. ResponseCodePlugin* data = new ResponseCodePlugin();
  445. data->fresponse = s->fresponse;
  446.  
  447. p->id = PluginId;
  448. p->fdelete = &Destroy;
  449. p->data = data;
  450.  
  451. s->fresponse = &ChangeResponse;
  452. return SOAP_OK;
  453. }
  454.  
  455. void LoginRESTService::ResponseCodePlugin::Destroy(soap* s, soap_plugin* p)
  456. {
  457. ResponseCodePlugin* data = reinterpret_cast<ResponseCodePlugin*>(p->data);
  458. s->fresponse = data->fresponse;
  459. delete data;
  460. }
  461.  
  462. int32 LoginRESTService::ResponseCodePlugin::ChangeResponse(soap* s, int32 originalResponse, size_t contentLength)
  463. {
  464. ResponseCodePlugin* self = reinterpret_cast<ResponseCodePlugin*>(soap_lookup_plugin(s, PluginId));
  465. return self->fresponse(s, self->ErrorCode && originalResponse == SOAP_FILE ? self->ErrorCode : originalResponse, contentLength);
  466. }
  467.  
  468. char const* const LoginRESTService::ContentTypePlugin::PluginId = "bnet-content-type";
  469.  
  470. int32 LoginRESTService::ContentTypePlugin::Init(soap* s, soap_plugin* p, void* arg)
  471. {
  472. ContentTypePlugin* data = new ContentTypePlugin();
  473. data->fposthdr = s->fposthdr;
  474. data->ContentType = reinterpret_cast<char const*>(arg);
  475.  
  476. p->id = PluginId;
  477. p->fdelete = &Destroy;
  478. p->data = data;
  479.  
  480. s->fposthdr = &OnSetHeader;
  481. return SOAP_OK;
  482. }
  483.  
  484. void LoginRESTService::ContentTypePlugin::Destroy(soap* s, soap_plugin* p)
  485. {
  486. ContentTypePlugin* data = reinterpret_cast<ContentTypePlugin*>(p->data);
  487. s->fposthdr = data->fposthdr;
  488. delete data;
  489. }
  490.  
  491. int32 LoginRESTService::ContentTypePlugin::OnSetHeader(soap* s, char const* key, char const* value)
  492. {
  493. ContentTypePlugin* self = reinterpret_cast<ContentTypePlugin*>(soap_lookup_plugin(s, PluginId));
  494. if (key && !strcmp("Content-Type", key))
  495. value = self->ContentType;
  496.  
  497. return self->fposthdr(s, key, value);
  498. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement