1/**
2 * The Forgotten Server - a free and open-source MMORPG server emulator
3 * Copyright (C) 2019 Mark Samman <mark.samman@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20#include "otpch.h"
21
22#include "protocollogin.h"
23
24#include "outputmessage.h"
25#include "tasks.h"
26
27#include "configmanager.h"
28#include "iologindata.h"
29#include "ban.h"
30#include "game.h"
31
32extern ConfigManager g_config;
33extern Game g_game;
34
35void ProtocolLogin::disconnectClient(const std::string& message, uint16_t version)
36{
37 auto output = OutputMessagePool::getOutputMessage();
38
39 output->addByte(version >= 1076 ? 0x0B : 0x0A);
40 output->addString(message);
41 send(output);
42
43 disconnect();
44}
45
46void ProtocolLogin::getCharacterList(const std::string& accountName, const std::string& password, const std::string& token, uint16_t version)
47{
48 Account account;
49 if (!IOLoginData::loginserverAuthentication(accountName, password, account)) {
50 disconnectClient("Account name or password is not correct.", version);
51 return;
52 }
53
54 uint32_t ticks = time(nullptr) / AUTHENTICATOR_PERIOD;
55
56 auto output = OutputMessagePool::getOutputMessage();
57 if (!account.key.empty()) {
58 if (token.empty() || !(token == generateToken(account.key, ticks) || token == generateToken(account.key, ticks - 1) || token == generateToken(account.key, ticks + 1))) {
59 output->addByte(0x0D);
60 output->addByte(0);
61 send(output);
62 disconnect();
63 return;
64 }
65 output->addByte(0x0C);
66 output->addByte(0);
67 }
68
69 //Update premium days
70 Game::updatePremium(account);
71
72 const std::string& motd = g_config.getString(ConfigManager::MOTD);
73 if (!motd.empty()) {
74 //Add MOTD
75 output->addByte(0x14);
76
77 std::ostringstream ss;
78 ss << g_game.getMotdNum() << "\n" << motd;
79 output->addString(ss.str());
80 }
81
82 //Add session key
83 output->addByte(0x28);
84 output->addString(accountName + "\n" + password + "\n" + token + "\n" + std::to_string(ticks));
85
86 //Add char list
87 output->addByte(0x64);
88
89/* uint8_t size = std::min<size_t>(std::numeric_limits<uint8_t>::max(), account.characters.size()); */
90 uint8_t characters = std::min<size_t>(std::numeric_limits<uint8_t>::max() - 1, account.characters.size());
91
92 if (g_config.getBoolean(ConfigManager::ONLINE_OFFLINE_CHARLIST)) {
93 output->addByte(2); // number of worlds
94
95 for (uint8_t i = 0; i < 2; i++) {
96 output->addByte(i); // world id
97 output->addString(i == 0 ? "Offline" : "Online");
98 output->addString(g_config.getString(ConfigManager::IP));
99 output->add<uint16_t>(g_config.getNumber(ConfigManager::GAME_PORT));
100 output->addByte(0);
101 }
102 } else {
103 output->addByte(1); // number of worlds
104 output->addByte(0); // world id
105 output->addString(g_config.getString(ConfigManager::SERVER_NAME));
106 output->addString(g_config.getString(ConfigManager::IP));
107 output->add<uint16_t>(g_config.getNumber(ConfigManager::GAME_PORT));
108 output->addByte(0);
109 }
110
111/* output->addByte(size);
112 for (uint8_t i = 0; i < size; i++) {
113 const std::string& character = account.characters[i];
114 if (g_config.getBoolean(ConfigManager::ONLINE_OFFLINE_CHARLIST)) {
115 output->addByte(g_game.getPlayerByName(character) ? 1 : 0);
116 } else {
117 output->addByte(0);
118 }
119 output->addString(character);
120 } */
121
122 output->addByte(characters);
123 for (std::vector<Character>::iterator it = account.characters.begin(); it != account.characters.end(); it++) {
124 output->addByte(0x00);
125 output->addString(it->name);
126 output->add<uint32_t>(it->level);
127 output->add<uint16_t>(it->vocation);
128 output->addOutfit(it->outfit);
129 }
130
131 //Add premium days
132 output->addByte(0);
133 if (g_config.getBoolean(ConfigManager::FREE_PREMIUM)) {
134 output->addByte(1);
135 output->add<uint32_t>(0);
136 } else {
137 output->addByte(account.premiumDays > 0 ? 1 : 0);
138 output->add<uint32_t>(time(nullptr) + (account.premiumDays * 86400));
139 }
140
141 send(output);
142
143 disconnect();
144}
145
146void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg)
147{
148 if (g_game.getGameState() == GAME_STATE_SHUTDOWN) {
149 disconnect();
150 return;
151 }
152
153 msg.skipBytes(2); // client OS
154
155 uint16_t version = msg.get<uint16_t>();
156 if (version >= 971) {
157 msg.skipBytes(17);
158 } else {
159 msg.skipBytes(12);
160 }
161 /*
162 * Skipped bytes:
163 * 4 bytes: protocolVersion
164 * 12 bytes: dat, spr, pic signatures (4 bytes each)
165 * 1 byte: 0
166 */
167
168 if (version <= 760) {
169 std::ostringstream ss;
170 ss << "Only clients with protocol " << CLIENT_VERSION_STR << " allowed!";
171 disconnectClient(ss.str(), version);
172 return;
173 }
174
175 if (!Protocol::RSA_decrypt(msg)) {
176 disconnect();
177 return;
178 }
179
180 xtea::key key;
181 key[0] = msg.get<uint32_t>();
182 key[1] = msg.get<uint32_t>();
183 key[2] = msg.get<uint32_t>();
184 key[3] = msg.get<uint32_t>();
185 enableXTEAEncryption();
186 setXTEAKey(std::move(key));
187
188 if (version < CLIENT_VERSION_MIN || version > CLIENT_VERSION_MAX) {
189 std::ostringstream ss;
190 ss << "Only clients with protocol " << CLIENT_VERSION_STR << " allowed!";
191 disconnectClient(ss.str(), version);
192 return;
193 }
194
195 if (g_game.getGameState() == GAME_STATE_STARTUP) {
196 disconnectClient("Gameworld is starting up. Please wait.", version);
197 return;
198 }
199
200 if (g_game.getGameState() == GAME_STATE_MAINTAIN) {
201 disconnectClient("Gameworld is under maintenance.\nPlease re-connect in a while.", version);
202 return;
203 }
204
205 BanInfo banInfo;
206 auto connection = getConnection();
207 if (!connection) {
208 return;
209 }
210
211 if (IOBan::isIpBanned(connection->getIP(), banInfo)) {
212 if (banInfo.reason.empty()) {
213 banInfo.reason = "(none)";
214 }
215
216 std::ostringstream ss;
217 ss << "Your IP has been banned until " << formatDateShort(banInfo.expiresAt) << " by " << banInfo.bannedBy << ".\n\nReason specified:\n" << banInfo.reason;
218 disconnectClient(ss.str(), version);
219 return;
220 }
221
222 std::string accountName = msg.getString();
223 if (accountName.empty()) {
224 disconnectClient("Invalid account name.", version);
225 return;
226 }
227
228 std::string password = msg.getString();
229 if (password.empty()) {
230 disconnectClient("Invalid password.", version);
231 return;
232 }
233
234 // OTCv8 version detection
235 uint16_t otclientV8 = 0;
236 uint16_t otcV8StringLength = msg.get<uint16_t>();
237 if(otcV8StringLength == 5 && msg.getString(5) == "OTCv8") {
238 otclientV8 = msg.get<uint16_t>(); // 253, 260, 261, ...
239 }
240
241 // read authenticator token and stay logged in flag from last 128 bytes
242 msg.skipBytes((msg.getLength() - 128) - msg.getBufferPosition());
243 if (!Protocol::RSA_decrypt(msg)) {
244 disconnectClient("Invalid authentification token.", version);
245 return;
246 }
247
248 std::string authToken = msg.getString();
249
250 auto thisPtr = std::static_pointer_cast<ProtocolLogin>(shared_from_this());
251 g_dispatcher.addTask(createTask(std::bind(&ProtocolLogin::getCharacterList, thisPtr, accountName, password, authToken, version)));
252}
253