1 /*
2 Minetest
3 Copyright (C) 2017 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
4 
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
14 
15 You should have received a copy of the GNU Lesser 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 <cassert>
21 #include <json/json.h>
22 #include "convert_json.h"
23 #include "database-files.h"
24 #include "remoteplayer.h"
25 #include "settings.h"
26 #include "porting.h"
27 #include "filesys.h"
28 #include "server/player_sao.h"
29 #include "util/string.h"
30 
31 // !!! WARNING !!!
32 // This backend is intended to be used on Minetest 0.4.16 only for the transition backend
33 // for player files
34 
PlayerDatabaseFiles(const std::string & savedir)35 PlayerDatabaseFiles::PlayerDatabaseFiles(const std::string &savedir) : m_savedir(savedir)
36 {
37 	fs::CreateDir(m_savedir);
38 }
39 
deSerialize(RemotePlayer * p,std::istream & is,const std::string & playername,PlayerSAO * sao)40 void PlayerDatabaseFiles::deSerialize(RemotePlayer *p, std::istream &is,
41 	 const std::string &playername, PlayerSAO *sao)
42 {
43 	Settings args("PlayerArgsEnd");
44 
45 	if (!args.parseConfigLines(is)) {
46 		throw SerializationError("PlayerArgsEnd of player " + playername + " not found!");
47 	}
48 
49 	p->m_dirty = true;
50 	//args.getS32("version"); // Version field value not used
51 	const std::string &name = args.get("name");
52 	strlcpy(p->m_name, name.c_str(), PLAYERNAME_SIZE);
53 
54 	if (sao) {
55 		try {
56 			sao->setHPRaw(args.getU16("hp"));
57 		} catch(SettingNotFoundException &e) {
58 			sao->setHPRaw(PLAYER_MAX_HP_DEFAULT);
59 		}
60 
61 		try {
62 			sao->setBasePosition(args.getV3F("position"));
63 		} catch (SettingNotFoundException &e) {}
64 
65 		try {
66 			sao->setLookPitch(args.getFloat("pitch"));
67 		} catch (SettingNotFoundException &e) {}
68 		try {
69 			sao->setPlayerYaw(args.getFloat("yaw"));
70 		} catch (SettingNotFoundException &e) {}
71 
72 		try {
73 			sao->setBreath(args.getU16("breath"), false);
74 		} catch (SettingNotFoundException &e) {}
75 
76 		try {
77 			const std::string &extended_attributes = args.get("extended_attributes");
78 			std::istringstream iss(extended_attributes);
79 			Json::CharReaderBuilder builder;
80 			builder.settings_["collectComments"] = false;
81 			std::string errs;
82 
83 			Json::Value attr_root;
84 			Json::parseFromStream(builder, iss, &attr_root, &errs);
85 
86 			const Json::Value::Members attr_list = attr_root.getMemberNames();
87 			for (const auto &it : attr_list) {
88 				Json::Value attr_value = attr_root[it];
89 				sao->getMeta().setString(it, attr_value.asString());
90 			}
91 			sao->getMeta().setModified(false);
92 		} catch (SettingNotFoundException &e) {}
93 	}
94 
95 	try {
96 		p->inventory.deSerialize(is);
97 	} catch (SerializationError &e) {
98 		errorstream << "Failed to deserialize player inventory. player_name="
99 			<< name << " " << e.what() << std::endl;
100 	}
101 
102 	if (!p->inventory.getList("craftpreview") && p->inventory.getList("craftresult")) {
103 		// Convert players without craftpreview
104 		p->inventory.addList("craftpreview", 1);
105 
106 		bool craftresult_is_preview = true;
107 		if(args.exists("craftresult_is_preview"))
108 			craftresult_is_preview = args.getBool("craftresult_is_preview");
109 		if(craftresult_is_preview)
110 		{
111 			// Clear craftresult
112 			p->inventory.getList("craftresult")->changeItem(0, ItemStack());
113 		}
114 	}
115 }
116 
serialize(RemotePlayer * p,std::ostream & os)117 void PlayerDatabaseFiles::serialize(RemotePlayer *p, std::ostream &os)
118 {
119 	// Utilize a Settings object for storing values
120 	Settings args("PlayerArgsEnd");
121 	args.setS32("version", 1);
122 	args.set("name", p->m_name);
123 
124 	PlayerSAO *sao = p->getPlayerSAO();
125 	// This should not happen
126 	sanity_check(sao);
127 	args.setU16("hp", sao->getHP());
128 	args.setV3F("position", sao->getBasePosition());
129 	args.setFloat("pitch", sao->getLookPitch());
130 	args.setFloat("yaw", sao->getRotation().Y);
131 	args.setU16("breath", sao->getBreath());
132 
133 	std::string extended_attrs;
134 	{
135 		// serializeExtraAttributes
136 		Json::Value json_root;
137 
138 		const StringMap &attrs = sao->getMeta().getStrings();
139 		for (const auto &attr : attrs) {
140 			json_root[attr.first] = attr.second;
141 		}
142 
143 		extended_attrs = fastWriteJson(json_root);
144 	}
145 	args.set("extended_attributes", extended_attrs);
146 
147 	args.writeLines(os);
148 
149 	p->inventory.serialize(os);
150 }
151 
savePlayer(RemotePlayer * player)152 void PlayerDatabaseFiles::savePlayer(RemotePlayer *player)
153 {
154 	fs::CreateDir(m_savedir);
155 
156 	std::string savedir = m_savedir + DIR_DELIM;
157 	std::string path = savedir + player->getName();
158 	bool path_found = false;
159 	RemotePlayer testplayer("", NULL);
160 
161 	for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES && !path_found; i++) {
162 		if (!fs::PathExists(path)) {
163 			path_found = true;
164 			continue;
165 		}
166 
167 		// Open and deserialize file to check player name
168 		std::ifstream is(path.c_str(), std::ios_base::binary);
169 		if (!is.good()) {
170 			errorstream << "Failed to open " << path << std::endl;
171 			return;
172 		}
173 
174 		deSerialize(&testplayer, is, path, NULL);
175 		is.close();
176 		if (strcmp(testplayer.getName(), player->getName()) == 0) {
177 			path_found = true;
178 			continue;
179 		}
180 
181 		path = savedir + player->getName() + itos(i);
182 	}
183 
184 	if (!path_found) {
185 		errorstream << "Didn't find free file for player " << player->getName()
186 				<< std::endl;
187 		return;
188 	}
189 
190 	// Open and serialize file
191 	std::ostringstream ss(std::ios_base::binary);
192 	serialize(player, ss);
193 	if (!fs::safeWriteToFile(path, ss.str())) {
194 		infostream << "Failed to write " << path << std::endl;
195 	}
196 
197 	player->onSuccessfulSave();
198 }
199 
removePlayer(const std::string & name)200 bool PlayerDatabaseFiles::removePlayer(const std::string &name)
201 {
202 	std::string players_path = m_savedir + DIR_DELIM;
203 	std::string path = players_path + name;
204 
205 	RemotePlayer temp_player("", NULL);
206 	for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) {
207 		// Open file and deserialize
208 		std::ifstream is(path.c_str(), std::ios_base::binary);
209 		if (!is.good())
210 			continue;
211 
212 		deSerialize(&temp_player, is, path, NULL);
213 		is.close();
214 
215 		if (temp_player.getName() == name) {
216 			fs::DeleteSingleFileOrEmptyDirectory(path);
217 			return true;
218 		}
219 
220 		path = players_path + name + itos(i);
221 	}
222 
223 	return false;
224 }
225 
loadPlayer(RemotePlayer * player,PlayerSAO * sao)226 bool PlayerDatabaseFiles::loadPlayer(RemotePlayer *player, PlayerSAO *sao)
227 {
228 	std::string players_path = m_savedir + DIR_DELIM;
229 	std::string path = players_path + player->getName();
230 
231 	const std::string player_to_load = player->getName();
232 	for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) {
233 		// Open file and deserialize
234 		std::ifstream is(path.c_str(), std::ios_base::binary);
235 		if (!is.good())
236 			continue;
237 
238 		deSerialize(player, is, path, sao);
239 		is.close();
240 
241 		if (player->getName() == player_to_load)
242 			return true;
243 
244 		path = players_path + player_to_load + itos(i);
245 	}
246 
247 	infostream << "Player file for player " << player_to_load << " not found" << std::endl;
248 	return false;
249 }
250 
listPlayers(std::vector<std::string> & res)251 void PlayerDatabaseFiles::listPlayers(std::vector<std::string> &res)
252 {
253 	std::vector<fs::DirListNode> files = fs::GetDirListing(m_savedir);
254 	// list files into players directory
255 	for (std::vector<fs::DirListNode>::const_iterator it = files.begin(); it !=
256 		files.end(); ++it) {
257 		// Ignore directories
258 		if (it->dir)
259 			continue;
260 
261 		const std::string &filename = it->name;
262 		std::string full_path = m_savedir + DIR_DELIM + filename;
263 		std::ifstream is(full_path.c_str(), std::ios_base::binary);
264 		if (!is.good())
265 			continue;
266 
267 		RemotePlayer player(filename.c_str(), NULL);
268 		// Null env & dummy peer_id
269 		PlayerSAO playerSAO(NULL, &player, 15789, false);
270 
271 		deSerialize(&player, is, "", &playerSAO);
272 		is.close();
273 
274 		res.emplace_back(player.getName());
275 	}
276 }
277 
AuthDatabaseFiles(const std::string & savedir)278 AuthDatabaseFiles::AuthDatabaseFiles(const std::string &savedir) : m_savedir(savedir)
279 {
280 	readAuthFile();
281 }
282 
getAuth(const std::string & name,AuthEntry & res)283 bool AuthDatabaseFiles::getAuth(const std::string &name, AuthEntry &res)
284 {
285 	const auto res_i = m_auth_list.find(name);
286 	if (res_i == m_auth_list.end()) {
287 		return false;
288 	}
289 	res = res_i->second;
290 	return true;
291 }
292 
saveAuth(const AuthEntry & authEntry)293 bool AuthDatabaseFiles::saveAuth(const AuthEntry &authEntry)
294 {
295 	m_auth_list[authEntry.name] = authEntry;
296 
297 	// save entire file
298 	return writeAuthFile();
299 }
300 
createAuth(AuthEntry & authEntry)301 bool AuthDatabaseFiles::createAuth(AuthEntry &authEntry)
302 {
303 	m_auth_list[authEntry.name] = authEntry;
304 
305 	// save entire file
306 	return writeAuthFile();
307 }
308 
deleteAuth(const std::string & name)309 bool AuthDatabaseFiles::deleteAuth(const std::string &name)
310 {
311 	if (!m_auth_list.erase(name)) {
312 		// did not delete anything -> hadn't existed
313 		return false;
314 	}
315 	return writeAuthFile();
316 }
317 
listNames(std::vector<std::string> & res)318 void AuthDatabaseFiles::listNames(std::vector<std::string> &res)
319 {
320 	res.clear();
321 	res.reserve(m_auth_list.size());
322 	for (const auto &res_pair : m_auth_list) {
323 		res.push_back(res_pair.first);
324 	}
325 }
326 
reload()327 void AuthDatabaseFiles::reload()
328 {
329 	readAuthFile();
330 }
331 
readAuthFile()332 bool AuthDatabaseFiles::readAuthFile()
333 {
334 	std::string path = m_savedir + DIR_DELIM + "auth.txt";
335 	std::ifstream file(path, std::ios::binary);
336 	if (!file.good()) {
337 		return false;
338 	}
339 	m_auth_list.clear();
340 	while (file.good()) {
341 		std::string line;
342 		std::getline(file, line);
343 		std::vector<std::string> parts = str_split(line, ':');
344 		if (parts.size() < 3) // also: empty line at end
345 			continue;
346 		const std::string &name = parts[0];
347 		const std::string &password = parts[1];
348 		std::vector<std::string> privileges = str_split(parts[2], ',');
349 		s64 last_login = parts.size() > 3 ? atol(parts[3].c_str()) : 0;
350 
351 		m_auth_list[name] = {
352 				1,
353 				name,
354 				password,
355 				privileges,
356 				last_login,
357 		};
358 	}
359 	return true;
360 }
361 
writeAuthFile()362 bool AuthDatabaseFiles::writeAuthFile()
363 {
364 	std::string path = m_savedir + DIR_DELIM + "auth.txt";
365 	std::ostringstream output(std::ios_base::binary);
366 	for (const auto &auth_i : m_auth_list) {
367 		const AuthEntry &authEntry = auth_i.second;
368 		output << authEntry.name << ":" << authEntry.password << ":";
369 		output << str_join(authEntry.privileges, ",");
370 		output << ":" << authEntry.last_login;
371 		output << std::endl;
372 	}
373 	if (!fs::safeWriteToFile(path, output.str())) {
374 		infostream << "Failed to write " << path << std::endl;
375 		return false;
376 	}
377 	return true;
378 }
379